From dd5d56aa3db92f53f9f687cf1649560ab0956b3d Mon Sep 17 00:00:00 2001 From: VIVEK PATEL Date: Tue, 19 Nov 2024 02:00:20 -0500 Subject: [PATCH] Init --- .changeset/README.md | 8 ++ .changeset/config.json | 11 +++ .gitignore | 14 ++++ .npmrc | 2 + .prettierrc | 7 ++ CHANGELOG.md | 7 ++ LICENSE.md | 11 +++ bun.lockb | Bin 0 -> 111787 bytes package.json | 78 +++++++++++++++++ readme.md | 83 ++++++++++++++++++ src/index.ts | 2 + src/voyage-embedding-model.test.ts | 130 +++++++++++++++++++++++++++++ src/voyage-embedding-model.ts | 102 ++++++++++++++++++++++ src/voyage-embedding-settings.ts | 38 +++++++++ src/voyage-error.ts | 18 ++++ src/voyage-provider.ts | 128 ++++++++++++++++++++++++++++ tsconfig.json | 29 +++++++ tsup.config.ts | 11 +++ vitest.edge.config.js | 12 +++ vitest.node.config.js | 12 +++ 20 files changed, 703 insertions(+) create mode 100644 .changeset/README.md create mode 100644 .changeset/config.json create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .prettierrc create mode 100644 CHANGELOG.md create mode 100644 LICENSE.md create mode 100644 bun.lockb create mode 100644 package.json create mode 100644 readme.md create mode 100644 src/index.ts create mode 100644 src/voyage-embedding-model.test.ts create mode 100644 src/voyage-embedding-model.ts create mode 100644 src/voyage-embedding-settings.ts create mode 100644 src/voyage-error.ts create mode 100644 src/voyage-provider.ts create mode 100644 tsconfig.json create mode 100644 tsup.config.ts create mode 100644 vitest.edge.config.js create mode 100644 vitest.node.config.js diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 0000000..e5b6d8d --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 0000000..5b42be8 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,11 @@ +{ + "$schema": "https://unpkg.com/@changesets/config@3.0.3/schema.json", + "changelog": "@changesets/cli/changelog", + "commit": true, + "fixed": [], + "linked": [], + "access": "public", + "baseBranch": "master", + "updateInternalDependencies": "patch", + "ignore": [] +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84c1263 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.DS_Store +node_modules +.turbo +*.log +.next +dist +dist-ssr +*.local +.env +.cache +server/dist +public/dist +.turbo +test-results \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..60ddfd0 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +auto-install-peers = true +strict-peer-dependencies = false diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..c9ae50d --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": true, + "singleQuote": true, + "trailingComma": "all", + "printWidth": 80, + "tabWidth": 2 +} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dfe5686 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# voyage-ai-provider + +## 0.0.1 + +### Patch Changes + +- First public release diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..51fca54 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,11 @@ +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/bun.lockb b/bun.lockb new file mode 100644 index 0000000000000000000000000000000000000000..f8c2a9a1478497eff0043e7e77f2acee596a259d GIT binary patch literal 111787 zcmeFac|4Wf9zMLWlL(pTG9@#ak}^+a%9wc`GS5O0nG#8g2qhU3(jY^Hq>>?_GG(5U zsey=6y=&R~KIcBq?>SH8{p0<;pR+#C?b>VI>$<+*^GdztB-pQbG&{^YRvr1^ejDqsAv}waaU^Dl*&Y z%}c#^oZuxUJAL%g%mT|j0#FNs;l=@mWHpER7lz~Wv>f;ft9g11Cj2P)1aS%=6+lm0 zPcIh-45l08sX^We;5LBNzzs5h$GpHzkdC_eJNo%!Ft$V(3>_%{gukx?@-!fS5{toX z0qEy)#KqIe3nK^0q0VHG{DI{Tj&=b@0N=w8s^HoQ(aF#8sH3O9pSPEvzrCLyCLD-CKaT<=1&GAW(a$cx z#oYm;1RBEt$^`+!_|oGMHgYxIfDEw%AdKH*fV2Q_;_(t5qw(m2M@u{&#G@o0IRV1B zQ{r(21R3Th@z@0r_UAJ^mf|rRkBNBn^>TL)@CJPaoM!{|zW^s$0lo#u46qs?EYAhV z1TY5Rc7S#OxdAExWCzHNFDC%V1@eQSk(>Z)0qy{pjmJoUkbeXqv~vJoE{M-l0fghk z-qs#=1EvArU$Ec3y}aBZItDq~`}sQvfr*4M0Cixy{r&8{Je};_j<|c-`8oz+4uW#n zPyT+ebr?(pJFee@0K#%E4qV>J!NnKe_Y1K1we@qv>;z83e(`qkwFM&{<8ch$#bB_Y zQE)tg(d+5r6pWbx^OF4k(A?*q;~2YXk^}T6j9X z+>Q@t*9aig)8WU}=Ku)Xzl_HZ;64}%;c)`E@f!vZ+Pe$xL-Z5G@#_FW-tTn23E}+s zs}3265A^p7h&!Aowj#Lk)CdsfwE%*y5BFrp*(nvpA@Oe9IQI9IkOE=Ccn5d}+j}`U zE`$59U57z^=)W>RI1Vz!aCTh1Ks8Sc2AQA8d_d+6GXIeIfy_e1%Xsqqd&&c(_y(f5cW z80+%jKGbpX6Ixw*PHN+HL_j%Qcbshf{C$95zK#KY&_!Y$T)UqGgyVwd5H8L;@b$r% zS```u<}M?loV`SVG>{Jv_Oma(-cduG-Y~v?E}>XJOIHG6Rr-B2_Ol;?Et+3{2jpa93xJyKp_=OIvNeP{Lu9?V#7ia?Ko)Jx{+bQ#rc*lxVpm9QlyjlyiuoytrFBDun&10?9Rjn@e(ssDh?) z7@p8`{}74L42$H^s|(W^_8NR>C1*ug_2whV;120Xoes|^c7wwK31@~|%in#{9MR!3 za}^vQ)jqE@0E$T9xyr?OVLSW%{2dpvRHOV{q%9RA?d=p zprMD&T0a65uDUleEx9k8jl@U{GdI=e-h0|n)|GgG7wfW3eap9?%5xD6(H~+5ENovk z&1jLF6zItDZFjqC;dlDL&wF{?AxPY|amgwNZl14b&)&xNly~xwyUn#(d^zxC$-%d-#)Ixd{F0^BvfGh_HDO*layn_X5Cb^_a1d5RB{591j#RA zvBl2TR5C%=Zk%xaK(h0ds8YIh1u-R|l-7&#?DO}IJswXgpkYw|I&sl|E?a)_q09aK z1ro#_?*;}QT`m%<-mB~0dw?=eF)7wJV^@FdL2}BG_T7Rj%_|X_S#HxeNVV9G9U>SH z=?ItKtmot#+vs38@F@G#my^urPqLbxIJ&^QXV*<>vF~ka?-X~EEGVlyU9!4#LL#&2 zSA61+%nB~5Dd|tg4^eD8)n2+2Ybp9Znugf_m4o}sdBRSyhrbf$j0yD8KYUQQ+Wxcb z*UTI#bJNa;wDi=LiI*ZiR1yCSn4*+hN}vy-Z4BJUh#8!Vv~!d8zR1nA(6Th)z;1EJ zJ7!`Q+ppq`eNOe>RSRS9mV9U=`l-&);w&9+*3NK2foMm&3r$UN#anom_|r?yM%AndRlaDzId|0`f)CTevCK{y2dUtGlVJ{TyWSsDU z*;GfhaBJt!U&vr=i}NT`yky5uXdSPm>AS zK{#Ti_u+%L>7x&uG6!aPjrHDWTh28O*D~)UerRnoR1o~rOZ+N};+4Yl)AV$&qK#h9 za3Ay9X3EAn%vHOE^S&8DX0O`UEbkxg1h>68Ro)hR?Y`&!!T!pOgPBqP{#q3sk-BS4 zO;2d;e7I#5$S9@TO$yr%e#sjDosMa}l>U^)^2}d#Iw|&gQqyT3%%H27VveqI3;tZz z-78;p!seho8Ru!Ex5GbMN_Zv6)5c~VFxRsGXs2R&mde;B-I>cj5Fc=mPeaCOxcgFB zlro>3-RV6Pn=D`6;@o%1@Xq6zCA)rW8WM>^%TLOxuy*M|tS)WCu~u{QWJjaeX_wC< z^Anj5$h<-3A2L6Xc_>B|dU*JOz8x~Jka>g5Ph@^0^Awr)$b3iUAu@lF`G(A6WIaIU zDY70Q^BY+wka>>G(?0po%gfP^rH<6bS+dBBJmB1s%EZR0AJ5hmX`VLN5G%ZiDBUoh zo~O@BRR6BP{e&bDvjmOY}mHJ5t3lR1QAj_%<2Ve<3bqusu& zX<6kOI~FW;&;0Of;y-CDz>r^N=~S{?YHu}1Uxj68p`_`Ft(+@e8icBbFJq-Onm&kS zFb8QqDd>?w*PU)tSkye0R*!EitOGC5}%ox;^m z1KYnJ_2zC6-*NNkMH`6~&x#v7?-SbVUMLcgt+>vVP^dXhNxX=d5qCe}O5o$!6HNb; zQ}xlU*JrmDwzbJ%Go6t*A^uRjbD>}^)ALggp~<$h8oqv5uUW@-+r8&qT>Cdi2cL?$ z(9YuIrX96CQthnu{fCsjl}RCkN>>BViEF-FERW>de6}Z^ayh4%@X3YyH*FQMgM^eH zIu6$j)L%G!o!~$oLDkuAn=m%dUvFNHRePtMT^QUgpSv?8l8SlS<7tM;gkCeUF4fX@ z6F+z|u|syg_D<6_NoTBym|+WX;WK`b11n4Bv~yqbsWS=BJDs}{^H90q2St8hl+!!m zLjjVcmxtd*Z5|bWB$)AbpAn-%;busU{LLo?nX`A*f_#zDWQ&w86vK0255J)Gh26J(y!uW#&j%y*r=yoHv}G2vwu z^~A18LESQBoQX5@lq?p9Pa<*Y{P`i>OlNzte%GC+SK1D9I**udD{YG472m^KSXeHa zn^eM0dVF_P2>Z*YlAm`ys8|+vbtbevFlQrp#ojJf6l~FZ5OW}&Ex zl{&_gwpSzfC^a&6=MwB{esGn&gj+4@+k?$^Qc^}mt+$35)cJpE?DWc^Hh5sn|K9(H z3^f7RyKN*9;>l+Kf-P(~l3%MPQvW+3fGzVXfAd<&pdP~42Mv(`{7rZ+$r?2fehlD4 z|B2SfL#iSCSAeeo`0I@)!e;{?z!p0k>SE!aH70~_4nBzB{Ri*e|2i@JoeaWH1|MMj zU>>#siQ`)NMIdlU|NRMn0q|k_As6xKcedeuq^>v!8hG_t^?yBmi16b8Uk30In@Bm5 z|8I!YdkXmAAY;}3f2;d<9;49$ye-b|#&|yk=|G|3^dir2n3Gku+aP0hv|4%mP|5mW* zDdFwIxUO{!Bk?l_d~hVR8h^+K?yM;x{EL8(jNdR7uK$11f3bj%wEuTLqwgdB*8sjW-afpKl%wnZ-DjlUBH+XI59Xo&@LEek_!8hy zM-9)1y!G;r0Y0++u9XYzAoiaD{zm2>1vnIf{zKbv{Q=A2ni68)5b)vrg*-S0!Ejtt zLijO&F9`TB4}Cz!@$W~v z@VfUerr_`x?jQb?e;x3(Ho%|XAYUFFwr<3J{08}5fUmKE_H%$k?E@R&yKInuXM_CZ z4e}Mimk%3h|LG0#+c(H(qTZPQwtx@U|39988|2S!kS`9t+}Mc!!GN!_f%aE#kWWCn zG5e|;wJ0lpMy^hW$o0(|h* zT=<{HZw&A)H^5f}U+TGRfL{pswj1D+F>lO&XTU$Q0sF0h@3;ZJ1PcaZy#fA(4f5vz ze;%;{`CXCA>bo^Be+)kyLaN=zhLbCBz}p25BnebzE<1N zH>CY7cs?@r*6IVom*Byj-ywMfp^bk^NIj>&@c)T}zK8JB0ACHXAIYQZ{L^Qo{xsk# z0sdOsjc6i#py3Xl;2#G3weo<8)r7SFE8xTR6Nx`^|96P+CBefRS%25#1Humn zd?jEX$*(tlF909*ALPLc#&InPu}30=8$Yo9Yqbsa5xx@OL;J}3u@>8C{s}xEj=w*# z{{Zk6fPJ{`KtAFZ`1p?rsYfFG@AEUH>>r8vn@C+Pz=!=0*FRW>l>bfxsTYUm!}i0n zwZ;J9Hvm3de__8v?t1xz;N?{V@Bwvo?yYzIJ_h)QP<(Klv!;ajKZLgr=gyz>A3u0` zfX@$vyWaMD1HLlefAkt)=jd-M4DMB*n|gMF54-%=Mj`%JgE|`-|BPVssRisK^A^$l ztG&PGk$Mh*4_sJnKdiIf_9p{Ae1C>|NFV&w*5Bn3`!4`r3Ge@UoR;BTb;X@I|x{_oqMeQvP$ZzO)+ zfWMLWl>6izlu3`E~X$d_?zmJ|Ok#0Ux&iPoMt`;P68U@S#1ZxmF#-z6;>P_QSX#7u5P) zLipK$51J584G1*gQurMae;?sb0KOu={mkIrdj01Gn@?yT`i^L>#Ra79F~EoO5BC3B z`yT2e{1(6mO$dj17I4A$ptU4~p9x-`p#LxrueJJs@H+t?zJDS4wb+JrkovUX@D1{j zd9&6%EW+2v^I^ZO)pxZ048R9l$Z*JoKCD#(vEKstaQ%fmqzvl+E+KVk6maurt#%Pz zgl`1+u>X+pv)=KWhUX*p5naU2e?z3+E5L{AAIz^O1_+-{5x0Mb{Z0%nWE_Bx|CkW| zLBKZw_F-jMhLpqnKP9AIHsB-vuhllxMfhES5691XY@_*GlrR`@gz(4XXAj`R^%I#l z1pm}%)IG%i7{FJ+#}9Ima#Yp7{fN}72YfIE!r}OXWn_O_`~NIN_?wjf^ZECG*8KYt zgf9#DaQ?x*TWi~)BEnAqd~gI34$ENTz`<|-BK&8759c3Z8!7*t08;N4;3MDv!Wc6C zj)=dH@C{XP{Ri`Z(tpW-563^0MaB^P{JVtMZvuQc{$L&pE+h_X$M06ar3Dvwomo>t_@wH%{wD#fwc3UT5WW)N!~R>(*g^Bp0zQl% z9R?a5a9APYVC6AtC;Ag3UWT ze@5)CHI4}13h==ZP&j;UA{uKj|F7|P9q{4!fqH+!9|wG-|Do@QU%&GKsb{E(Yd_Ne zB)=o#?<4$kfDi4%JhZ>oHX!^azz0W&{~EvRjXw!Ee1QG88E<>N_VocD&Oc=RST8>d z@L~I*{q-cI{XKvW+Yjx+K3K1Pa&6rBf&N1-q6d^msB z8h1nw;dAQZ<`486`hb)p_5K?o^$r6*eE&oI|8MpGT8{8z0Uxg4@cy6J?*M#c{|(3f zddEMx9*&R19q|kL^G^w>rwaJcf8u|t{5SUyek9=K>YX{%6}9g{C>cP>j%6E zeOND_U;p3nTPqjZLhRcCKJxzmC;ViRtnUu!OCyKfmLk?<4$gfREmPA?4`0fA<-wCuR8W_#tI~SO2eNNZlB~ zKY)t=TA$+x|0Uof`yXgyz40e8!u|dg89NC6)!)D75&IT^uK@f<)-MGATK}(kgr5oc z7Jv`)>lp(Ge+lrd03TUD*6aV_!~g#N64CstuYZ?E>gEEz39yg!+j^g0`;2kVe`sT^ zTv!*e?*sU7{J?gvwe4vBeZW@&{PnDTX#RJ=w*h=uwjTSi{#N`2+f4-vK>9m`n)zL^ux4fD86x0=^w+gzd}5mqUc(E(f272zj~Sf@?|$iXlCWx@S4Q4<>BdpgBE{Lzd1rtPAzXM#byc1k7 zL4@T!Fay#*A*|O2F6id~xL`sf)ExvDtTzHKm>|OPQJ4Yg?-0)G_p5jR6B%I{B*XOo z1B6W=z_$Y;j3W^~k49L8#g{{bA2;Fi5LrQ<1D}Tob-3_(h%o*<0AbNCJo3T}NDyIt zetiC)5LOYu*Fz)J7si*P5f+JnKk&X7K$sVYfA9!DO5pQog#Jk3%h3qO|9(&o*9irH zu!5aCBlJX+z=8Ww_tMp$Hnzi*4b4-x9w|< zj=%qR2=zU|eYlz~H};QI3fm;K-V2*lxk`y<>w39jq^+aKYcn~M17B^v*?KLY>T zHy|!BG=H}*f=dc4|NrOy=qKe*{Fs34{C_2_#uBHCmc~{_4H=&Ejp_TKsC~(NHsW+g z(k0RhdNRj&F$0f8DqWb~v6uVU+tpQ!GyTZ0=PA6m`|GFkW0bN9Wi(9D1QfZvC|$VL zBMCd1C#O!fy_KRvy>+5#?!xn@d?sP4H|seqgI&qaEC;5vVI^G4DrVI7eq!Ozh!0}( z?>)SInET5RxqiH6Z(*DPN*B%rBw@Ad4n6s(XWV7yC2Py3H7;aPwI}+AP6%f)$+W?t zEf3C~lqg_tF>dWSAJ(5jU1VjPnpX8rno9&0-UX}oZivt`xNWvb{y=SqL zGo95)S+eql$&ElZ>Jz(YrY~z>QSo==_}aLY*M%7D-qQ*=iyg-d*?7YH0eGF!U9l_a)wZ`gKTL?buc}wbQcn;(G@> zDay2;v#h*Jf3}F}Q!Z;gY?!+!S3r;wx*`ewH`o8|FWiG8345$HNIdv8$9Jrp0F}#P z?vi(NNWcUyqtU>^-eWm^nt|gSu9;6$u37|)92e>?xtMV0M)&ypvcoZ0_nDEiQ`K0M zE^@vLwXqhgp#(w3=eyq&2HrR+9W}me!5c##|LxszMdrj0Ca;WVD{WlA?N55Re0kXA zLwx~d=ez#gwqhGK%KEAfR(ZB5lr9P4EYy`hZGY#jXA5mB*D{NQY%%#2y7XOFZn9-A zzIf`C*7tpoY2n?+%fV+RW`$Ul3T-p;t!{W-InsG|Pqd6=M+tlV3`%zkT6g%u?Q-pm z0#B1^S~a%j0Y|f+Q+*CQN~MlZw$cq;z0XfR9VYV2sIBd$Owg3ws|+K9A2-dv9WaRC zm5Vo7aSFw~1L4L4DO&gOFcEc0&-mE${nA18lHcbCv)DeLkgtCqt9#?A*-nrOV8t&O$9e2HnP4?wv zQwz<-gmQz}zAAqzt|Z}sD22AuWsS4P$J0AM-QTlQpHpnl&|OQ+(qR|g&+|_6C|&se zgd}W--xiwGwj7iC9r^OJ?|TDX5z2^qK7g5q&@3S8mh8{rkE) z{Z-t>+9iq1f?kK1+~f&jrSvC0%XM#7mG)rPXTdIMw_=VDA5gj!h$ukpODECM``Mvg z6SfbtlgP#{NC{*I2&Y%kZ*|c+n`(F~_Hm$3&uPosGu)dKXTD>wm4?%YpPpr^i0Q0I zoZ)LCIg8SzMC(%BA{ltP)#%s?4^2?(@UOrZpK_*6Cc7^4X8I^H`)~{PkxRpoa~3+s9vNZwZsuL)4jwmNxV&}HSY=D3p>W1+W>YOu zJCrUwD@GEw*H=3CuzK3&eEpCW)0AFY+BiFTi4NVjQomGmMmc8UUig)H8W|QWXW0pA zPsE4H86 zv{Aew-_y!9J$JfD(7yEq$+y(rq$BtBm`{6M&?;{_#X5Mt-;X+C>>~nNH}cWzB>YYTNmzo@y`BDI_K6+OQoDJufXL7H_M(>>^FkR%$3wL0eAdRaaeuVzzbttoK*XM2n= zo5hSW>%hK=j=cnt%^pp9s%kmfJwB1MR%XMwY!`Al4Wx8O4r*`WR}xI4mwd&!2c^q| zI13P)Wh^?U?eKa#UpRTq!m*dZ1{=RbItFQnP=*QET>l=Oa-F8sGYNWwb1IfZ!= zndgwP`rHo}3~ITyr;@pXkHsh0T!Lb^`s8JSt(`N~`&9`zypGEKxX$?bNbB-LzlPZU zhFYa<=Xp!yQM&L<7fIMEokl*^D9u9}ag%TFpO^@AkX@-6k2x)U*ZzlU@{=IBt%qMO zQd};SF!CFG?KggNlcDN8Z1OQOh3=A)k}GFM;8`KEFJVJO0b);Gk{{RcXFsoceNXe0 zUv)x$$)%;2KYk>fx%J+LEi!R$<@HxT`{iBDF1j9ZR&X!uwS%O`AWN zC8&DHu~T4s+cR>5nKD|TUOkaoC5rd@L2|zci%JeW+_`+aOmEC+zBv`8%Z1jZuD_0W&J$tH*(b#DLiAXTO8%F!EjcASGdA1&FRcD<}o zLLu4~wEtCjAw1JX#u+zS_r0m#{d>(09*sS>S`68D1|6_}S>jwzFz38ewlVsn_d}8F z!>4#|%}mfWOfPiF7x~TZ@WD*0U*>vLc3idJ!5|r>yA!S3$?{gN!BbY#Czzb$j^0?v)O7XZXM@0YLlOVN-xlE75{0C<>E1sYEb`Y-% z_v$=o-3ex*(6B6#inj!N-$pnl=H2nA+tL29EMK&#kk#SAn9#>6HpcCho0iy2E?r(4 zHP=eK^ld;}s>`wEYNq%_hCyZUvVz26*MI01ekLuc{}K3j=paK;J9}1AMB#h-s@?W3 zpI+)vvx%~hbfp!_~guvy%`Eg{tQU8*Qg_teMz_BAm=s5tPVb@c+y@Ke8h zte^4Gi$wnor8Psh!E|vT&#eC4M{~nbpSZWyRO(zOo8QXH)lQ->Qo7CWVZ-gHlPu44 z=SPT?nXMI2x_oHeXU|W)Z5%GE(ONVqq-czKawYHhiEo#r9H_!ZsUEdIZTCG!<($NDxsD*tTH&N@zvMlSu5S{ zT{pfNBp(oIH_Sg-wAyd0|G%88zmGtqu^a_2(>}&=->mSIJL+$kK+-JUL9{@TBjZQ- z=21YI^42>SIO&6*zdhl*cOv!U0oN!2k{6cx?~|SdHxYD>S2x1%P{4oi{|EJ7zqf$e z*zQ2%tVj??tW zSG~Cr6|vxLdxE}7%k1l8O}R3r+q`H>G-W0kdBQD@p^$fCGFJ^hu6B>T1x ze%yN&784FM#n8G(4p3+hEFWH&YATtyYaQ51ob4l_6H)Ympq@zo(Jh%@!rN)|`DpGM z43wQGQ=&Ak9*axl<0jqlP*JDxh~{yHPpkf7NZ5fU@;e$Rk7X^@elqBDcO~Gho}vLM z%_mi*I|A(zl0H&tJ+q|~U(fc=5guw8?ri9=rLVtovt?KRJ{IXnFO?q|jZ(pa*GqG7 zx`c3?NkBn-!oHl|Tgp0xJ@+*@#Jfg3pm7V;v2)~+RI(R|F221sk^YGD+|$XJ14CA` z>gw7WL;5yzY!2HLY76!-DRoY(c?Z2&)eT=AzwmcHNWykfb^N$OK{YR$TUu3erLM+A zks@e@%=_lOl!hqAnlX~)TcZSzgttdfeA-g+=qGLQp)s$m1P@DFJP5yO_IP7Q@%;w< zl|n=TVo%?_mBAyu4uZVe1Awsr}d)g7Q8MTztU)3%JM{2 zRdTj%-aA-7t9=WZC>+)GS2Z`htblE@2+b}a=O(zg5Ow3)Yx^6v^qHwDpV{4iIXQNw zl-ne3N^|DP)mqiXEk5Ai#QZB^tGGV4@J{ac;p}XdNcuZj;^ zw@aVy{I$adi389C{|5eF32PQxzB{Dr%ri2dI#Ds-zIYz>5${;BlyihF(qFQ-i_K?B zU^?m(Xt7b0p?r5Xdtc<6I1)iIW16a2RUw`?D2OmHDXWr zy+!Aed`;!J-r+e3)e{!Pw6v_xlI?Nl8W@srpb3B1h9qp+=CF>5#)sl{Q@5jQHJi&C z9zAWh(7v?m(s9{OhlxULrE7mFB^!m#g~Z(w(=eRp@ZUT7qSDfUi(%;dHq6o|_?r#z zABh4GgujnN5_bAMSDKl1vx2%o@??IG@Tu04C&AZCmIRmIBr5t-JPOS9jO(upd2Oh+ zROl+B0I~{!7FlvxQHLG3*)PRhyuiRmb2+(-g+H$*W2<0G3#^-p_2b} zxJ+juo8)PSowwq|BD)8SAF9k6ipYOAertA}a*hm@}va7m;tLuXz zTGxGX8FSRbV^r-7XUdTd;sSTxPpZPhbyL%iWydH2`7IP=nUWjh7b&y-?<>iS2Ng4p zEJV>PUy^x$xsvz0+B5G}-SE}#`IOMQ9qdCp^7Uo++C&QZa@~sbJWx8%I?0+uN2nKu zY4Kp4n+Z$4aLvoDuKVc-bvT2^rqF;EEwd=wkkgXR0n9vBxZg71&gGQRx~z0nUyJp| zeDbepvc&r;QuL&>I42Us-S|jyW8#3la!I56{%@Tj1)^VFAL@QI@-n*tO!CJ!U0V z^Ah99=if(2cUz2cw{d-?j*RHqgFP;mdJ3hhiq=i{p*R&AHLP;pr&gz|X_Vp8==ZGo zj2sIo`d3t60%iL7p0bKipU^c@?W>#-l~cPx(SB(v`AaYLd)XYX*Twtz@VaoFR72}B z9Gr^1H|#TaOO?cwMx4ZAjCe>rb=o9<+Q>x2 zW_7yX^L$y)y)+tEV!UoR&{Rk3vO3wQ=$RBZwf2iS7G>>HxA2|c-Neygb+IILwlsV=jhls=*<|U9pb+)2ttnm?&f^1UUEW!~J(+>u-Z^!o z3~+p!dfYsr+<|2r++v-P72~{ltZM0jbdTtk&&s?^1bSD}jIJ+VRBAKi&3~9OVwTU? z%z=Jx9z^SIkKwmGnlCjr6}az8n|XUq@l{G-M+3#?U7_Vl?Y^^> z$pm_bs=}X=6F<0x(uKeGMG}^eHhKq#ylS8B8}2L8;YRFR_3!CEit)BFs=r36!fYrK zWwX!Vc=Y$y48?O6Nmm(ZncI$8QQTbY&wXIpHBD&o9P8kMa1ksGU5MVzPq@u8r6fKilx7FPM= zpgOXjgmKVC>)IJxI_~8D4dFlE~o;V9f`?TEqQ`@sv{l%`vR}ZZllD$)N=deXYXnsMOT0!v%{)QZ< z;5>YxcDI5C0>Vv^ZOYun;8gf&iKJgT z5}x|4HKnC8?>H_7#b6I4&3s!p{dzlrc-N{f?m2WAt?PAd=Y!VFe&&9PL#LzeCKGI) ze@XaMUih}pvGcRPJ|EY^>U<}X&pxZ-`HFtDc(RUY{4~EW-6Nr+=GGVc=Hz;OP`cos z8T>0@Uk|;h$vb0oLo=QJ!NnSG%7$>!o9Zo=^3FHv%A9^Y8mR2p+q&}DeCDf^7xQi< zi7t%BbYp;x?!?EQxP*&lF~?B4CjU|e^Foi_B#w?nH4noxU>Dv zO?PQ((iD|>L<|mSPX1Bqatcj5% z+vv1xv6N@cA1&#ADpBr4`HTF%Z#52;zBXQ%t+nme@9btO?$Qf(OOCQBB~4vTzW7TZ z&^i8;_teeUDh!YK;{HzcTg@$>zwO0I9kB7*8?blU(p&xA2SVrGty!nM9<+s^l z#&Wl#q*PYJ+xzUCI__`Vq(E`1EElC~gVq&MF3K0-XHHi8HE+2T=3k|IVt`}f{;c0Y zj(myHh*Ns;yB@sZv!LrfK5{lqO;IR0osQ`^w)!yHXPZ+twjbWk9BQ% zyE*ipBzXEANBL`q))l|4V4UmdC+zvnvp{YeB|V|R3z7ospC{Y)`&R9BUbL1w6x_`B zpzkI5MIo)yz_Xg4nX`0X#K`Z-b#LzKu5hSB>Dr@p`Gd5q>dwnvQi!?olT(sg^?ah& zsW*+!`6wz>Eh{S(s$I|9+AgY^yzbdt;xsed680ou5TzOMAa-Ih}ivztG0~T+VfO!Msq3 z?ZIo$a+CC0+vK7o7*YN@qIK;Ti>mMcEUEg+&6)Q_nA@=Hbtqr?Pg}k*!_qNLrUOK3 zvV6KF$p^+>Qu12o^Z4w2D}MH?@`UWJJ^IzX+FK>UQMyiO-8N6@;hEou@UCN4)}WA0>sskobdQhGV=i`fY+Z(kt;fyO{`yk zp4lE$&EXa;>|xsHUuQL2x-fqX_Z-6MI-_+}`gC`Ei=F=-SWeeSxmmDGxw2)dU^+3m z`B$-|q2lxCiSF^Ad$XM7-=EHGcg@#xb8|8@8i_b~v%tu?;!u1_8Y&JhXkFFA`W#jV zSS&YR2)usn?eHU6nuW;+ejJa56@S?fd-7ioiDn{=-c`qVin2S8u}%5&<-S@8ri67S!(C$>u!#ONEK{)YZJt?+m6xYbh43k+3eLPA(%ZK>vYB_IbluRp#c4E3!L*s7?hKS-hw}6dLY@pf z%YRtJ6cxYS{+aQTgJ<8*=o#}G?zZ=q%R8wLH}B-@>oeNMW?bo`W1Bc+p^wsaN9%s% z^G%s%Ql9VV?H=B?H&Acs{bb7baQ@S`l8)~$obv1cIJ@$y$t{356Kl{|t9N{V(f*Hx z^rKNFx=s{lDn5_l-iL7g=7H9oVOdeJLb+1qhtG?Yk1{z?%lQ8fPf+PqHUl?p+`uMc&E2{$-u&D1vKdv8EX5#fk&L zO~s>hud-W{T>N=1cWGmn^(vm#p#1el>w4|K)bVlulh=glfIt)j(wVs?znW+p7ZHdjQT~AGGf2ZLQpS zl!nU6v+b?>n#SBGTd%XvoveLU<~$i}uM{alFYnsrN7wv$AGhk*&i7M~yFGvEroWA7 zaqUX-40u^Tjq=wQt&6?8N5|5@-0x#*_0B>53i}eyCbDNDL>j3QA+fk z_j?K5z4OCF&&AAaeR8Ngolh2ONIWK$Xc&S2)&lu{$PcZn5~3xX<(QdW&&D*gKwzuF z#bUFWF8@UJU56Oon}dVJ_9u-F>o6`AUhCDm?>Z~M^=U^I2f0cm2Qin6Mk)7pbietd zbX^MA?@l zNHTrcChrwlc1Jaf-eS?|8I5N2zn5*Qm0EE@zKM0^m^uskxp@?=du{KPJsOw43vre1 z7Ro-m|CaJVQL4A-L6T&yl1U2dQB&*lFBmF|OiCDebNgGpqVq7M_`}C{Y zN|i}ed;`(CmL=p*te$DM@hRwsKCm0vDa>2!{BAb$lYSNRrN`$g{YxnYY!@Dok{4&% zw)HveE40COJvsTzin!2pkC6HI1y+=95L%bJP2BO>H0gkpq1^|2S@DpqPeRBVO+CXy zPb+WQ8fkp4E;%9pT`iBmXzad+dlv#{)SjKXuAPHPZ+Cg!&b05?L6mMVTDM@dRMVJ1 z+iBAf)k>&w;#Qioi~Js9KFL&>)}4agonr4@ZvC2HS$r&Xyd$)ZnmPB0 z>c}pQ`haq!woGB}&Xh-=LU=L=+cudoD&o#daO2@PTGz^(q?&TO_$1+E9@+C^VI@Jj zyCF02U4iDT9erkpwQMB6Se18bv3b_A(&i2EDfXDXsJV3Znuz)4Lv&HJf;Z9QECj7P z5Oqa&mS(rr!>E3ROJy?xXVP!z%%0^K_o7vHm{M=ct6tibI($#TB@~mz;$OjbrX<etUo1Nk2?O86Qa9bQQtHSSl=t1rafM2Y z{NXmbcP3ZQA9s&Y)NHeGb)^5AUtF|2DeHDsd`x#UD!wPsy5Ef)6575fP`75c7wuFp zbiK1;NGPH()Zj%DYIR{*leajmoL7l#3-7$@wZeEM^BN<{St9|C-P`l7&hPqs=&~G2 zHw>-2uc0p?zsuG5EK6J4>me#jCq?b$)?s^vqJ7u6OU6^rVlt@W9?cyUk7`Y1>>k|v zc+2<478W0TJv<)wMK|7YMZaf-qjhcdViR4M)9zh(PLXgY?fqtkS9OhF8lPL;JtBR5 zOseChpWM5EeCvcTI?JcuEw?Wx7>>zU@(>Sg5^5TJ7JqvIJ>O2Eb?w9GWI6f@72Qaa zjwttjI2c^`V0t;*FE01Ug~{xqTgUb=wJM|>e!$J}=D^T(FRB;^>qoERNM;IK@2ixS zsT+==;t+w>EgaFcGJK_LwY885MKZd=ea@7brtca+<& zafjcJiwDIF=pUV!l79Ci@0H_jMN-_k25$UDqIGMFby@6sf>elzMOk0o=%i=A%F z*VCz_c-3P*#LT4V{OWGloF{f{?q}KYvxWDE=H&aH=5{tZidJ(^$MN{V-&G^)dK6l> zl`nr{y5_b3wK@Bbg3c=mmQU!Hl=vDojtX`=rwFrB?op}i{or519B4`6bL$n89N*c; zqFdZeM=JH+%TQURDxq|v(Ym{NyIYStB&GsqL4OAh{v(G(&@8w zq>1fvTc*1{$5gz}9N;u)p~OD)?-#eCcH7dSZySX>=fcGy2Cdt2JG7Wn;gDJH*`3N> z&aS+AEjCp)<%h`iSWWw4yeWN-N%fgqCKF!qzxL+-?xpbe(XYqpFPHajQV@)_a!978 zM)`XRt*c5!DA({k?&D{#bW+>qO>F!wv^_J4KacJ;Z0D;Hmrvhiy-9UY(&>^6x%Wgp z*-#QiY7_YR_3%9VV7>a5?45il-B`4)|4dCE*~R{ww-c{OV!0YjKU%6LGk~;T=&dwh7tkB&ldZoFgOy{2{VBV7L zkF3_-g3^sc>&BH_G18YA@!zIR&);375_#n4eVt}%6N6o3n}^Rduw`)6n=qxfe&TpZ zcU6KW*{h*iUjUnZ?>&FdmCO8(Djm@0JMn1U8UNH_EX$AppD*X6p@o?0hadYw+&RuY z5mY(asYs<eK>yDC3|e=)@5?Q0 z9~0{LdrXlFcX1ubny3#=KVgrZGI67*$hf9)#x*O|W4tQsO;_%xmp9tu-d!gdi2A~q z+c}MSxUC_j02PM>v@TaGjcrQiLQonTt@N+Jy(Q9v5eJ4vc9vfpj*T9UQZ3be|88I7 z{tQ?D8{|jL=7l?Sf0?B1VRW0gTXc%~1Lu?pO7|>USHzmgIfLQgL8ry za(1OxR2h*itDU^+p;3IqS@Wa4yPnR+hZ+~ zARe#yDB->0OQISb4^X=2(7F;M`SXkpFG%JYb`ZS%se|$V`7m8iu_bxS&6J|mQ=8y}*eICbMhPQH>**6nAUDb+$-yI8IcF4pc_8o6Gik=Ob;9lh?H zN9$Jg$T(|>xx1M?cDcK<@+s5i%V`VGo3gV-#o+>4*MpimoRdr5L%yu+ysSMo{>nDPFQzn1!jwG+MaD^< zDcyXmuQ0!-29jCJG3Tn4mg7v{@(G}HlhL~B4P>fPR!x`pG`Y9_ zx;B#fqw3y~7#eAx&YQU(wVRk66u68mtL7N|J--;HO63xX_O;1yUt5l$^6acLZfl&t z{k{aZPNtxBd-<+TMJeXJ8*maa)a_+gZZ$u}CaL{&EbUEw=Ig3Cz6+P`U2Q%+_&{dJ zP*KC|0?jrX$u|mPIs@+t-o2LXzRHgB_aa)?RP&)4xpGP5zHG93)A?(dn`K!w#F1l5 z$BdpTo~DTDiw|}_O&=K}B-T+U~K?p|!AlwcUeM zai^JOn8Aqf&04OOve--Fm&fzClZgn;Y3ENpMaAI~TDLm>xUQ_>g0oClm|)aHO_{F> z4?hy!78yx4S1}CS{C(?XAAW5b4@@Z6<@-O+BvF|~)u`H+EoAFV@Euky`&LDd(oIL} zvP!11G!%;+S7@XPuDDIx~3V#<n~TfMZuLO?S#L@QlZQzF7m}|XWUJWpma0Qx>vs&h{tU&ew1wGe&TD$ zO-F-}9_DfT6-qs8lA!&nx5T(CMxJ$EI6-ZfDD*I+D*JV}clm3&84`lUBSUeIO1+<*rj2i6~c$87zX?iTy>+;zb3!TB|*vc3? zuF=UBzL}!bS?=4(Y<39{ZHdxR2u` zZ{}G6>TA&wK9e_oK2BACDM@+$x#;$H+6MWz75j+jPYv)lPlQ$3h;27~gKgvuBG0G& zDUf)B5cj)^aG-e=tveIGNv`shLWCQ0-GdVr>@)37wanCqI08$o8o~}}>l)II@EP1z zZhr4}=KdkYkmwXcx1=j0oJUp8kWEO)^j6{KG46ZQY_x9mri%CZ&M9-k{<~-{=T4^G zlNvf&rndhoal(%u*T%$Mc+RTkJJ@a+bNP{)^}Rx}cGQ8GGdhEP*Up><=g?`I_2*}|;Y^t${t9STzrPoDar}q-Qifr(5q*TD z#Fn3;VqXWfcJNFSE&tT==jVyvez_9UYtvka1 z#U(+T>A(Rp6U(cD(_J6TM}u#F?M--WG0b^W$KIcnhUm#Y@8kaudv5{{<=6fJ57}C* zDWM`HrLl)(OC_XDQ7URM7=~eHtSQQp7KIX(78NaMC0cAzp(rgxCA61RDwV{0oqJ}? zt)Jyr&-1+R`~Q4QpKtea-PiYA=Q{hj&wVU^P*ia^>G>nMnrD91M91l$R;4#|g+0$^ ztuyB%Al_6wuZD^D?dhU3FO1(LDEz9T^#-%$`E5Rhh~oAK@+zxCYF&qa`Sv3tBX9S^ zAyqHr&zIAt1RcITrp8U-{XOIG;lWjoYwM`!NO)YuPlLpM-mXWAbMJ+XrC zo=DEgw6qyE%t;sgw-nTSn0cZ z`#QH4*-p-D*E}_+qU{Q+o+M!F`;Oyz$t4EuM_r0-$GSMTW~gi$Z@Iz4#3ow4!{AuZ ztCSbauGrlPLTkx~4hE!{lHCp8)#g{d{K)w9<7`{iZA#unsd|>Z*!$xXcwVZ-T4q_- zCZ=Z4xGf8bvutkNiLojj+h$eTvRBXcJKyBSbjQf9e17de56O!p^LRvDn7|8RGzPvY^Zif_To?5qx&qtXwEmS zu;yX1(kHhtwkD{hj#)Tvg5cpgg|xcE@!Fw+`7c@h2iE$BbUbgw#?y{oCQn6w{1AID zl$dz@x_N9&MpL+?j8WIN3bD#DLOc`G$Fzi+uPUtccGH!sEA-VFvYOPPdShlwr29zE z**HIB;Cb(QYGj6YeLbE(bUx25soRQ>p;uefo2=i|yo>w(S;h0UKkFacM-t_rrVxK7lo*({- zrrGQ(y=%*gmnL_VKXiSu(S8>^eq!pj4+|vQ%$f~empaRKwcYh-S?{gGnzv`Y_dkW_ zwbnmk#H&vK?iQG{FNs)<9^f61t^Kan%J%B8hz_47wM zXV+Uzf7Ea}X2G1Vde5pcds*u>PUCsQ;-j}ZDVlFLe@*eF-3SoXl}ePFy~CLtKXu`` zV>>K&PEdR0VJvIkFiQ34)}7mSOI`o8?tnpR`xj?H51HW)-mvCN2tsH*!5KWSsBh=> zab1;3?yGMoH4@`Wjs}MX%@y|hJiYT^SIx@NPb+4Nm5yB3>1&t!<#=i%S+S&OcUAlt z{;ey}Z=-{UJ)udkim=H2?0`0PZ{Jhr_s2|>uh^X3$#=li-uN6kn*^-_!1 zq2=J@){{w!M)RHIRVG*da8v#HIIoyY%TEcH82e>KW;xM1J^uWOoJj4x&lNUNh`RIH z_9EVFJg;3@?zby*v@b7_ZyWaY{E{`vk)10=Up*0H*q)tOJM-=Gt6tSsOYSGNs&y(% z{=QSiwj(uYl1Jr~!bQ~aKY8+RvfiV!8s!UkUXL?G>-HIMT0`Y0OWD7hH}v-G!kOii z=DkgyRp-1Qi$(JCSU#3JukSrl?E7uo(8f8_;~lK;N$JQ`7tyvmc-XMso3hpo=HPi( z+}$r(`QrTc&#@`jmT7CZZn18@IQGnzbslT(^M#Gxk~^bn=KU{n#<_A{)Hy6*VxcBTatq<*2N%I%05W`o^r|nxHcGb8v^KNU! zWEJ}xCvRRzO4Z$y7NlJle0b&8I3quYV(DZ1Zl-!iUD5mGUSEdey@=;Mc;ndOMG>N{ z33G<7pe^@&e2wC}ME!cTbCP-9tMvTF=p(yb_(Z*OR+S{}@SK;M{;o-nccJ!ttzAPF zzy0~JSos!?_Y$7BR9m}EG=Z{b3V%ew5d)q{0vjTAYmfK|lXxoT3eS2nbCF=Vpy1Z{ z&O0I_KI~D`+`Y%VJbCkihw14?tsm)%ohb792{0f=%4kEs-d1>d4O)LmLr@Coa{+z`1UFADw zC8ru&U-GS(Huab&H44X@hv(huIZH=a)w9$!)g2z|B*P}d_3>B1Fj#bn-gm-#A5v6!Ob%W3 z-a7%uTY%?PUbJn-s}!c$u#<~@cOI6r3zM{S*M5I(#x0Y^ul~$*z2j#myj+y9ph8JK zFCxi4h<}xObjZ2*3EvtE=8bZF8+;VUTZreasv1=$ajSE2?#BI#J2p@E`uw76mDD}! zIYsqWK2`1aJQ@6%Mu!~VcxgpgTP(isUi%^blK!5Dqdy2Ky3w^p#%SYsi}1Y5geOw> zr{;~C!YF7yoN;LJy6iXp9Si+7xILY5WUBU?!ya>`G%Rh;oUX~&J*m5|`cujoqdnEt zI(6dCNArTMritTtui|;rWA^^qU3_rlo1yu3@!px5HkuonjE1+>Uti0Z-w-K~#pj=t zVRphWcEXEv30NjK%tw&Qq<@w|nZ;#)UW%FO6m)p5l_ympv$ zh)`vVW~Hr0{@s&GI=8$#Cnb3P7%3*Qq3)sLlGiEs_iIM4TK9dCwvvg4WZB7?`1k%L zc-|cwt;EY7TWOMyZsX{eQJ;9nE@$%lZ(p7C@$+Zb@Vr-j+FTZ<{!qNy`BviY*ZPrnzy7pVb1&bL(D93K zP37rTYH8L6`w6SYjaz(5?E*1zT3zeAYJqRt9h-`T)?AI1mB#tuI-WOGUu;tR#;PYN zzB|U3=2=uOTIi(nymWn=!{~*{SGHWLNaO$PY*z7ncUsvJvV~fjXQ{x_oVyEXfuVbx zwH}2&eU9V3f#;Qza3FbPYify6_rEHhJLO!_YwHKP@6M%W%!qQcO-dBIn?29UX5?OF zcfT6iJy+7FYjazakJMPaxtwLV_x!AQe7Pva^NKs%ta%}}+(Y%zlC%TuimL0=qC|I= zZVcZP7|D0YJu2Tfh(19_O0zX`|Hj!R;-VYG7B6RJc^|*=;E)28kY9U9wY`^+oSCjIhU*W5rSKPXnw2!BK!({w> zkz07){Ux8rIacJCNXKo;7WRHj)p}XAg)!cB&dY+P(qFavhg(MP8&i^_I!ws#|Zucu{{M&rH;R^)I*ZO;4{( z3hy!$+=#RHHlDYu%P`3{ev;h8lBpk=1Rm@7HtmTUYG?;0Y`--kFaG$&u(+mOZ5fO6 zJmbssUp}@~J}N#UciH1u-YMd#=8FthzQgg}!Sl}De4A%!;S$qzqU%Ft=C@4j`somL zhcRV>#WC6t32}>u6Gl6Z|FGQl5T$6^h+n?0i6!r&Ys_{IfB#JL(T77B27Wl+Dm-tF zVRKyGBiXAT){y3OuDO?%`RwTS*l&V*Q(=H8kjqK6Hc)!>)Z*u|d&zUgS8H|IH{DwnH5|u#56_z+;iwp!$ZLPDI$`}g z$7;d5!A}+>`exi1>u|8Px@_xssmcWYD#;vj?iWcX5uxFA&%2&8tA|$BrY$t~I7^OO36#d@8Cb zR5Fc!&c53t87<>Vw%H~>J(Fv`^o?AAmkKrCX1S?j@vfBJ=`q)F_EzJ0BTDp#uA@{> zeVKbf%k}WB8SPPe!ptJoU#1#6LZ>|~T~X<-6rUpXcBIY0>dF-dPi=m_+w7gQV<9a~ zb%(RQ>LUEQ!5TbojM(Pg(dGO1*ZDnKVxVVnGQ!)?Y{QiYkEOSqpOH~b4jmSfGCA3G zddzqEiNYn@(ocOfmYpc`<>sp~O7=JZ!Mm; zfFZR%c5HsgnYd|uuA8HIZj#q#EpvFafBLwu#Y5NHtvbG{MQm$zdfudC)A*0AUHW*g zkeT}8k5gl^saE^bOiVIxymfe9(q28)ig?j1r*b0S{n?_2wLZ64Ii@jV74K>poxKrm zTjTKV(TlqlEp>FgHA70=#HhZP^KQkBpFfs1EZHn~*99E!BRuaO69wV&#(Tlx(vq%+ zgj13v+^+8-YszJHS*2DQ>=9Rs@t+v(yN6e2sD8#L`>&$YPkXlw72G_~J31H&bSFc;}&j{T)l!Rz3cb?kBdg%a&fd>GSxE58mR@X0cT&QMse}LtjPx zn6+y8yph`r)1R#tjDM7(GwV&E_AvicoV`!*yj>Y4!@U;nTQq}`l(}pB{H?z>zh1mZ zuQYpD$G2E8M&RSOwOzU^%l(#|ynSZlN|SF}y2{d)+K8ElJfVx|Il33%cxz!2W3MPLTOg@Zz2)`v=ZCz%?0K|1U|j8jgy^88uz)WQv}@<( zS165{sd3D1N7Wro9Pd*+@A@6FQ7`n4FKnAXL~KvC^@*bf;;y%%)Cl~}oioBl+^^dq z(41v(X7aN5%GWaQHU@eTm=|=`$7R|Z>F0%1-*5Ve<88q6?zOm>E88fieI+hR;l|gv zo8-h##G+S%wwAYNy*?)Sa;fmfC2i&PA-|U0%;n8(8@hOviu>6eGRHiIRv%aP^xd@- z$J>bKmDJh3QR>(@{hfzvHvZxx7kK1|jI8YpPqda%$ov{}W6{|s+M|5Pv025+GfPg> zTAh#9MBSDXO>g@ik}~unCHp*%w+YYdx4CirxJk<4atF+*uPJE}_LB%Mp6b&pFYkH$ zP5*tJcwj1X%NOtbFzi&pYYXyxW&9 z=&ww5bJLR6DtVy$h+O*S!1inI_2-#8J;MY&CpFYo%C(FdpOmTlefXOik9jQtCHn0b z8V^OLk_7JK`w!3Yyyeo1!bH-No0h7)6JNAae&T)Je6tjW2{9q=;#f(!IWMJeZr@&* zGv-Iq-n*U-DW~pfw|i$! z)DT>^%W!nihK8bCBK0zBUW4V|S9so>T%Wdh;}t*8=p=mHnfzdUa*9GmL*3CA@iq%W zEfY#y_O11$)Gj|jzTrJBx^Ak0*4u;LGQ0SsNmt%`SWj8v(TelKYdr7V0(-Y#8bc(a zE(ed%TGg>b{kgZSaHiVb$p>zi%q$8!uwIIiIm+vxs+&mG7(X9>C9~_FM>&>$w-TS+ zl9XdLtQp7q2G2WHM}6sxACvQ$2ZO3DKtKEq&}~52wAb5I2p$xASlDyi3C;nW@({Fx#YL&xFUPJ3D?~V(YLd zOk$>O;_4}x(`0?E3M`x~zn;2kFh76OxwBW3#y8mQ^jUq_*GTiR++h`KoW1Yxym{$` znxiViM$MNx(JE%S^|6Lk&BM5(dMb+i`RDnJhn_ur`J(gX^!3V?ruU0J21jO69ugZO z5^S#BEH%h!tXWlv<88+C=E~{Bim&XfdGhZ1gM`Msg>g}5oS!M5E40}aWE8I&Azez5 zykl;#f1;|;lvz7oHwcZ&{W<3CiqC6>rL0bS~;6OKlH9`)65aK8;0E@%zi&s?Rnd{qG9{BKSWIn zc6U3OAs>47y!$b;j7l%Xv#GlpaJ(PzyhnDq-%Ot`8?m)x$x)#p!E^PSJj`t#?V7h{ z#bd2UmM3x?K8{LH7Ck6yyf(UiA#K5lE8?dUlf>?fH)QUiCaitU`VN~_E{2`oij`Hmvs|EPGq=ykn_n4?TalM1I@bn@iWG z4BZ$ID6#1bVf$yznzu9F2y73sPZIn5t0XA3OZ8Vt;EGdOXA0ij**rF8CZ7pk&X^UY zH*x;`jOR^Rm!8YBsHm&*l!@|(^$%Yh^A#SFHI=aTmzwoKvHjL}o-`fUa9&~5xh_ky z!jWD&CqJfC9latnE}OilI`i7o>=`)TFL+*~5IKR-YInuVOhktqoN2sVSN8p}Z+;37 zmLJa<>)Fw;X-=bIVM2(>KB)VtdOi=8Zdat30qZk^ZFN ziirxbIF`BX`lY=;Mw>(ke~_Q?!6^-`ii^L`_%tWNm%?ene8{mY<9A4jWBA+&U%4IAT;5eqGRa zJny@mKerkuzM7!=;MUlUUo^eWX}q`83v0f}+~no9r8uwYZB&k>NmEltNN|oqmX3Mn z!!_sjH*8a$a595%)uPt$EY98^c;07=4zIT`SX&CQN}l}eWjgP()|SjR z{$BIe&8<+lV7^X6$-HOGVTRMKz80O*W0UU2Tp11YX1BoF+S8fc+ zzVX0BUH;y^RkH*fXgsq|N)_1eQBl-?)y2FnI()&#Sed9TB;Qc*tj#1j zt15nh0gm@4o|n)hZ%H_irm(#5SE0G@*`En+3*K+y-^8Fge}B>RvAX@+t#Kb*a?S-k zp$>oO=2e{h?Mu40;`G^HRAZ^%DeCt4^#mPw-fM4cJSW_Enzm--1-q!_ms7pIM^slN z79S~yPuOcVt4Yj3bS>$V-t)FLnKS2&_xNph6?fT?_$eam*l9k`#y2+jbt}K{yq4$o z5o(3s&3~{py2Q29i1@-%YrU52aM_Avl?*E_3DwLqPhL&jv~tG!^XtVozu_5j^rgjU z)At8XS{Efo-UKPDbcnx*BwbHUIGt*=5Ndu3#QWVTj6{yt^M zGu>e__GUi3t#S&DaW4GoQZHnVju0^Hp#A**=)G_hg&? zcN{N2o_B>+^|7kBhSaAjuG;z+cjl6v9?Xdh9j^D`;xFIIpw8-ck+DWqIA64UuAgSYpqt(7A3wb7o{ojbH$ELkWx1fP19(7 z?&GSZV>ZcsN;}S8w1$QA;|sq(EfU_Rt>12`>O-yPfjNTOPZkHg5fr?5eQ`(7p4#lihnDTrUqs8=1-7w<2?L#%`gAHyabAf>J#dU2ixz z%h;XYzjp4-=jD?Qnzm|XO(ZoY^1hN==1vz33e(=f{`&P|!DQp3t}9 z0Tp}a+}wWG+D!P0Noho?L9OytlHv5aY3j#qHt#JmC?S8|lq7qmYG_(lbWmlSN5u7G z`o+?^L>#Xqp7$YN`;OOAYkzqMr&YgctyNagc)VI+m6KPFb$I6P4G{vfWmnW~Hng%| z7;Sunw}K!w-E<36L~=x_j89723*QTo-?Xdw5EY{-k<^2L5B;gFPOh+%%cT29j%BxeI72w4llY@1^0d%IK})e6bfMb|T?-@b4)tGb z;AMB>zEfH-&JXCFB#q`{Vt$8m-gxa58^XTl8 zuomICMKb9P=0k2tGTKjjf1Q8+$t(Q(EVRag^W!@wC}?i6!TDisg)7fhTRN}ih_*uQ z^V`NO<6Y|SU##Y5{$vEfLpfV(!NQM~&ohr!x;#D2H~Z(otLFsGrG}nej4v1C@Vo~O z^B(w-UgZA0n92-!ZM(yTPr7#LWq#Ab+37ah=c-TJrnsWy#l(Z>3djfF7N1xXJV$s> z*%9e+b0t5tic`#N@cT53$MaGdXTRk}Du!>|74hx)C8heSriB$>ZL>%Gd_75Hl;4|g zE3?L|acUWNy`03i`U}r)YAMgHQw*KbWr{BatgDkRT?8&pd6UEQKDf4V=SymYrNmNm z&wXxpcPu{NdR_fG|L{hUj-Ph+C*B^s`0lp#51pr>t&8_R2`#kJigr1f^0d-OQ2*`q zJL$_7;&@RR;r#eIH9tvid#xGZVWLwvERI3oPf447#N|_r+>*T9^W3LchsC8-w&sgXEO5|7!nNk66(Xr;}e+^ zTX~`myc2hnIpKG5bbeSuu=`%S(kNGCbqsq)Jhp%khH( zo;UNQ(tI;ha={v#m;UBo&2&f2^%uD?JE-iv6Zz9m>#dud+>Y2Kly0vW64IJ5!NRPh z=!a!gy^86KFH)w~i2_MkI9^3O?q){R8D8%PV?2K0g z-qf|vU7yc5EBQTT#E4B6D^J9IEox9nc{MvmRyNa3VCtlEwm2@15B}r`E5QifBySqU zg+Q1CT*B~Yl|dD;eU{akj}sPp)fdQVfY9FK?K4;_s9$zwrP{UpkFS_49$h=POAdi2e`q)&IuwLDs+Z07`Qv!g*{g z*LRxpu>;^7(Iq-F*Ba>Dxm>k;|XjY@Mu_Sq|8--(2w z^Z(`#g(0KN>OUyKQEafR{x{t_$TjGJ0X=}q;9!4fKsOCO@P{5i`O1e*W-=*cy8Vp* zP2G;dC^2NU(@qrc|H)Vqg%RLRadwAetjYg-;{_BhgY4x`rbFLh=6^ok0=mK9pa=f7 z2T=a=U=V{yUQ~jfC2Q=A`mZo#{y)`^{nrs5eDJ^S0hEV0ZNT+KeUHoj>j4iE{-5^1 ze>esHe>(1i#t(X6&;x@W81%rP2L?Sb=z&2G40>SD1A`tI^uVA820bw7fk6)pdSK84 zgB}?4z@P^PJuv8jK@SXiV9*1D9vJk%pa%v$FzA6n4-9%>&;x@W81%rP2L?Sb=z&2G z40>SD1A`tI^uVA820bw7fk6)pdSK84gB}?4z@P{In;uxs-n37Qy&2vxLk8VhjpEH< zlBiTQD$Uu`l|m(}Ev1vm#`0Pk@(fBandUlGUPqoprMP+1TwxC|^y}f(G1S|v~4zv%o6dchWKJXbU_CtGCA{E#Qt^4oKvi_zgwx3P+-;PClMIt`f zXN*t;KtHq}Bhp3RQlr1wilQ#heh&6kBNPMB5A9coo`ZeM2qowS{E&OmJ=oWZfc}ml z;#FebgZ*;|oWB(~k$q1b?v(=2PZ{oEzJqi0H&RjfD(vTwpU~gWLg7zl--GS52=@Vq zcMAL7C^#<%AYRs9#{_~YJIy~%f`{NY%+OC;%NUZjk3>dKAv;i-A^-9NP#PegBOfE5 zBA=kRp|nT)YNI`{(SFrvZ)vm-G}=!V?XN5hK>HS>J%-VKz-aGYls8abK>OFCy=KwA zuxJlgwBIV)8x`$iiqa3I7fK(r?XaZ2$O$DHRO;OsS z;)~@sI1&K!;qV&p2JjZ}4$usE4`=~=0DJ^|0-(R^@fq+1@D+gm4h8y~5$Nwbpufd{ z{%!*L8waTTqVkH$C)$($8Gs4!1NZ|10D*uYKrmn}AOx@uupST!2m@>YYy^Y@A^?$q zD8ME_J>V!H6_5rv1~?8l0XPXr0UQR*0?Y=?0hj~k0u}+R0mc9=fB}FAPy?(0pP)UL z0|0&iv=8bcz+!+6UH?vH;nDbU+3m6R-*a^g{vYZ}_17dEEeL-%vZiGQe!W8~`fYs4SymiOPNx=spG% z0j>fH0H**BAVd3rlK@VD)c|uqF~~{)*8w*Gs2(W<a!U;tJC@&OkBIe=?`i-1djYCt960RYu=Wq@+P6#%-2faLPXk?<>3 zV;u*o-%tyN{96b>Vdnu*+)x}>0)zm90Db^sL`Ocr5CAU#=?lOS)e{o|iU4r{iib49xx0b3P5#^3}6&M0w4lF_2^Il;_dB+bcF#U04N;9gPs>-Un5RQ0D2DH zM`5C8Mgur}QgAK}KsF;edKT%)0mcKy0T9PnfE@rIF9o<(0?Y!a0ww{J0UYkWj#J>7 z$p96A8DJ*B6kq}{1{eVh0W$ywfaw5zfF3{>Fb$vs&<1D$Gyxg_b$}WGmHnvz6gOm# zEnq2N3BU%h7_bPi5U>C+A7Blb2e1NI0xSS?0p@@?fY|_)o+!_+0&wDX8P1VkP~OZ1 zpl1#O4ggRblL7kx$VUkPR4!0nAOY3@I6R!AGu(4x-;aZ1EC7`gR9;Xyas`k99spF1 z@Z~B9uGayA0RaFSz#HHT@B#P(m;g>0^MiAw69`xf2myow)&sTy!U0hz9HcAbq5}m3`a}#~8paKs-PUu#s_pbmh- z-vIaocn)X;yal`gGyq-z(EVosBx?fTd0)ZxOTcRYvKfW<9?%SU2lxPJ0el3YKAs?; z9nSgT_!*9^06sYHgyT;Dl6?WR0losh0lou%06GA_06g$~7aUOqi*$JbLjVE*ojd@=my=h% zv)eO6)VYD*FvFEvx(1-B-(0PNXHnshJz$deMO9Nw$XGU zo@sDrDBR)f8T8mu!{(go#TZOSTTL77@{0ELg%V$0z9nO*@^_Hvt7)r&q9xoN2D%3) zhD}wCBs&3{+B7vCxVr=}5+r<2X1nv<5PuCiI-sM)-NW3dN@tVi90?DQXsKx;w%=ol zS)iq@rjON|C_aiRl*6)K)9&)}IBK$d;1A)928nH&{ucU+v-Kc>c%qoPf~2PoTLmj& z5}?zX$@^LT%Ml{zuszDzw|ezLIVFXw)muR_4cPQqB~Ki5&beL4aq?0&w4w9(wQN-pBZ4YEy z06H#YCqFlkY!4^sG|H4;2MO}19<&Z5raKuVYT=ia>=hcMK%xmA1z$6mE=01oKfy(Q z)mfjYSMNar?nD+aDc(WkK(e#XZYAlj72jWhL<86~SrQ)_jXH+#v^CtxQy*i~K$233 z56UUuV@3x`mIcfJiI&=Q#Kzg9{Ayss<(t;O{CIiTNlK_=`#?@Ve8B_n2zjpL1zXZE zck|kD4f@EV$ZBBw$d(+mkUH)}F|x)aSnmA^k`Z9R?hEsd2&5)sgO`Eg#;g2}cNu0(+?`K7*bUTC=TVy;AkExC~KP(LC>zJx0= z28kH3*&Hs<+39IX<4QRD>yK5IIlazv+DFVgkguS(06L>Ur?yk|^9MhxotOm6@g58p znir&8`i55*;;P-IN%BmAEA;aR9pvjZVrqViZyMa?N& zTasrpJc~+UG8|D&A+97ha%pnMYp%{j2n)rBQ&Vum={pq|6gP^uD~%9pZ!)AJ@nb9& zAFRx6;A2@WV7!?e&#xlN%LBJjM(ih#_MN8x^cyEvh45p2-mo9yWY12WialTf_!IfM z@3PQ$4x!Tgd{Frj4}5z>s(7>#ut7;cX7}xbzHRF}$M+pmH1H6HRJa8Gka@!!{RwI z>~KKhOmaqUBeYx*==6IlY77!xH9a*=o$kMk!0{+M9MJijy0;68FDi4Uhp1)J->F}L z4dqd29K9Hr0feJjB@eP|c35DxX`vI0@332$FpwZN)dxrDRi5Wo zVG^idU<3rgOMs-lzF>d4R^D_zsKvkl=%J*rC6tSX$8`j}UUDVp*^*bgnXM@%ZI`){ zYivo;i@4(>rRIL)N@~~=UF%yHQsT3Mxso>^83`6>nC)GiBOuDlm2|Krhfl2cXc3xt zmn#v4$RM3)z6|%(;s$|Si2_?9ByOyl%d_hiS27JGlAu#6xG2&5wbxay#F8x;)84LW zQ?5(qN*vgdtAv)h%5Qj0xe{NtD zPO^l@zfhrEOtyAxCX@awQKyf?C=GvKp7}y>VW_mAnNB>L=P5 z-%%YqPW%{Gf;tCCA}lrg&4E+?H@K3~Y)QH~#er@zDVZyo%9c>q9PX-)yEv08F$0Md zuvMjqjVn=kvydzKXRVC-qq#W%&T!|S-2{{YZe4Mcvw|_7V+zHt< zY+OzF%A}{I(ZI&;IrQB&I=j<6DJ~?sTj3T5Srhe3paV5Lsx9F!fG}uO65+AQy8S;Q zi*zyDpq=Tj)$F}BLrsPUEeHr<24c8>guLXBc#IA6P9HTnSTGCXgKEBoKZ>)j@Hy=T z9bJ}fb|8TYGpzZ$)05RBg>*o|j%n{LEwaEHbdcYkH(!z|l>b%5%j3jmVfZ=INenXK z!Zn}a`{s|LW437nVLzj(KsK8;^Ri^s=t)072O1O9f7=KW)We^$_f+iBVCq_sKw6_- z=T?x60m&^D*~a%)F()DA^whK=N`xb9oyb+CYc^O9gBH*c;*Wm6_X0qIdI6eR{}`i7 z@)#TGV4Y=G7Yel6Fq<(~|Jj+UXK0+I3p!wd;slm$wriTZ+G9y=AVI0!iv+^amJbP$ zchva;I;iH`H!A0?Oym_$ET-6qe>hYID5ljS)4e4|&9h{)LBERgw@zG_#k9!SFPH-o zO>7vdAqhI-pcDK^PVAO5F$hVZPp%8Sr(k3=%&*wokNmXUZx%?jp#O&Yd=xqf{yrKZ zwB=opq1EVv>~O$C6M+r2b8i^smA`=m z8jUcU&DE#IFSi=TW#fY?4C(NHb9w&u*=7x{4vfRSDXu|;*3TJlj;}6?;pLeR&tf^S z&v3jb-V`6`buk2D*EuH{pbCxY!#In!IO;Hriltr4oJj6A}t|7U8*R0pUnn-y8#lY$ihyJsP62V z^#GL;_Ebp_3=a|8qV?1(kI$aZ010>+`8t9v$+w{t^LE}tV?cI&fJR_MPjb**MnUHT z8^fX8aE2?hd&HxjCK(h6tpf>ru7^jhy#!7ARDJ-4u?Q7kyF5i{P%hL|eq7pR$ zB&bFjr_iWaI8@;x=%7>p@0fxF+2&$jeD#3nA%0#SRk#DqZ`cAj4uzxby=J9gnwULi z8!~|42@>Qt&0(M3O);0`%xMQw3D z0220`9b+mhK2E+$MyA6>(R>v<<*tGRSx~k7Z|2Vd~=tVeYSl#*Uzs>_N`FEQ=g zYIZ905>s~j(@RX*I=#enI;-|?C$iGi{<^NsTdN<<5Fb_+C0MJo(zda);*^Sxa1uyR zivfNkfdutxf<9$Gd}MNkRlh;qt3&8@rUcSXgHQ%K$fIj4orf!IwnaG+)h>{7ed*{B z&Vv+rR9yO6;+Q-7XpGLT4}O3I<*Lfl3N!XBJzE449agQ(r0YPf4etV78z1h;jzIM_ zgo3OV07!t&(JA#?{VXS2VLDj(@$vJ9Zh#AUva!=@|^S;ARIkf3=N2))Rc*D(_&>2+j;NqQX}Vv=6RS(v2PaU&+_ zbtH~SdL1ES5-aGfq83m~e4Sn6B^xJf)P_lV9fe|&UdQ#Aq}TB&Ch2t~j!Al*3&13F zGRXxxYCZ~biPGZ~Y|uLixR3Jauo_^) zwLe2?hS%RN-g_D9#A#|;(Cs5Q8M1nvyKjfo#PGE9@WL1nD{=%OBi7jLD9@yumk!CF zf^bl&p9b&eK?2>rFs*O9cF(>#W&=o2%?CB?T#%p|iF7Dj&#Y|BL$+;DO4fh`mHIkG z-F3Aa8qR}+J(i)fB_C?69{IGi4dLoUfJ7d2td+kQ9a6U61`<>sKxsS*5;OvMf9<1OkaOg^10^&m1zm!=w3>2uNV=3)M(uZ(lzOolKX@^xwTkr^6d0Xa*1@ zBw$0{5glntA5kz8%|Wtje^&;P9LS`T9M+wjX1{&}iYdFz@}grEEa8-zps~zD`q-p{7p&w!!fws_7|#Kl9V9i%qyHSW zasD>bD9aai!tqMaFx#dfA4IdgQ~c1BH%IR4Jj?>D=dcqbsKq$FOUYuX{7Dx~f@SPp zdbg-A1{UlECQWk9xOY8L}-1miAKMk3hF3o~f1z znSfeAc3IeA%8F^U-`j#G>&+ZNf^@)YA0m?;1X*>%GF7RxYzJ4acRU#I2Rvbo@Vw&V z5A+-S>p_Cz1L4rjSouxgs=P&OpG+Vo!OC1PNKpE8)L8M(Sz3wa51?H2rUU(6lt-cc zhxZF~7c!GjKmMn`k5Vbh*BY!m8a|8FrzksZe92|0A-o|337UQ)Fui=hW>}pxOVWSr z`h92&2$rd7YZI=34ocHnXOWEXN~bKWXNSeqVm8aRAHoXRqf8HdJjKjKuoC?9z4F@wVZJ@tUl;F z?*z_e*=D!+<^qyf)(r@UJy#RXmdp(qnd0QwcnpgvmPeu9p*h2tzedqeFfwRU6eht^ zq3;^0k9-a3!}-fw+iSOq{y37s3vAk~I^G8j>fxt7?`7MC@X3N~3n1^5Sg>+pZd}My zvqJ%B9E@rTNFSIh^MRKiWM0vh2X272w{a?}~l6m7@k!>*gL2VZFCx|F-GfJ*Kx|#3+r762@+zA$-lBH>KbYe)jxC|!2 z>Z)Xrp!TQod7e+w_OHllcAs2s9;ay$XmAW#P1S^f|Y)Qe?eRVTz$DkfQs`;S4 zHUSA@I}mkw*708=X&_;bgL}0aYqP9C2aP`7d6n#2{%Y|}&_RBKw%&ClZ9G9@Ifb=&FFLC*>bUI$OmZN1thbrStXcnxz^GOfs@xT+pwO) zZjd1FOi{P!_`XsS)xGTT4~2m_ln@Z%`ciG7-!zc0OYnyIEUSm@Z>)X1?m4*od6B${6c=q7ZArblxr|&+}-}L#UZCJguxKm2Q)@oh}0Si$31HSItg1%$wLxk28UWmFktrR<7 zzEK5>4^|`f-QV#cdXaok?V_i0^5g}LqnHj>hxBbhuXU9s%GiB}(|7;6Z;x6pVdaCP zJ1Vc9;N5|G(J1dg?(L&ALf+}SFSY~NMnbOo#QdZhx1wE|czwyrBjOY5!Rejsmw-5TxRULM$H>;PHO?l1y zWgvl-1+1>>yXO1v#%KI8)||tXPOFKuBN9-8Ib+(s^MNYph(X!bwN1Z$x>0H_=)n3E zRP1SfOcz+8LQpb~Xul-rgx(!OlZ7O|uSNw4yIuIJ(II=SZ8Z7-27W!ilVqil+MGbB zx4lW!B}_WS+s#;Ble-*OpSv6v<_w)Nj>4aksq|g3lJGZ>)zLlC`Xh!d<9shM8L$`z z{(`W>J^2kbuJgXf!Q=pHeIxXz-Qo!p>HK{)Z7s-Aoml=k(P({9(1&KYMlJ6*z1XzL zY0}zX;Tn1Gx))Q=Up>6;_qUL{VFCvAq#oI>J-RGiUOEM?QFt4qY(=^xb?P(W8tGq~ zzIus?zd;T3K2f)naLdg2($Bdo3qvef^3pBmI*J!9PTL08s3v~K+(;2~v3#@}u91DI zo-4Py&Yd|nm+ixmSsY)Ywac2lEnUX^Mcf0yo3OQ@xx2w`-ywlR{;rOwx{o`2(=^ytXFw}v9 zz)&V*Epmdtx7`md>lxr;oez)l@2&~EmE%+bIMoY-i<0#uOm}-#X{RGQ;2y|H|kOXMe9%!Sr zlTKuBXLl_3p$uuteil|#k^_Cn&P*c8qUbp~iQ?@<3-BVjQJjg8(V#xzJdEW-&IL=2 zbL9o!YeUIkvqE9;=3X(pNj?mB8Z*E>hzMg16g(;}-Z*Bg$_yg9(&%1LB1k?yK}1+x z%%St6pfUw{7rm`FHbRRE)igLlCps+vGH^d`;XK(R5G-}!ug!A;2-u#j-@F}$;<6QCd3?q`6k`y4VFj8O*2Xzaz%NRPEF8UjXo z5O6=?aoovCR;12O;eX{se9i)0{3Te8t%ye!n0nADP_YJ(sZ@0aR#m}BgNWX&IrnE= z-&)Ado;;9hsFLYFy4b30(8Z3Rfl|7E4UU6Yr2te}C;!Sh99^uafhulyJ6UBHR9Pq7(4fYF-4}=fBr25cew^OpIpFDWjn%!{ScR{b}>3XQvKoS1uMy8(tQ9)*Du+K1?|7|j}Dneu@t0L{Lb^8esEm;Q*bbxk_om8-p zhD>Ktpamw==`=btGR~f?(|(vxwuHsvK*m0U7!O>AIm^;PjdO~%w*$o)XA@?Aw}qIW zH3vwY-y#SMzum!XABY9lUI7E{3M-sgaqe#B{zZd{wMRhIt;5YSWCqcT=Hf>s6REJ0 zr~eYr<4I&!PhG64(HX!$*z!uy#g0Vm3#kD#ycqDf7XvdHD`Tj$g<{fwR|`GCi83tf zhb0v#9N1RHorzjySdx#{(i6#EPGlDsvP(bNkF)+4*f^(P3|he6e;vtQ*8pnlGa|O| zUk|DIP@HMbG%5{N-?O@?sN(Op*e|#HaVYxhToG+l5Y}z|aY2 z73Ut_3Rh@key8h>56*tpz24%@(4MXl*liunhwS}3AIm(j1jomYYtNe;1AE@(nAr0! z%OEO^=Iw@~W!4dfve0i+|6?wyK+qef?Kl z?09!`v9JG-tA}TStH<>pa`i|%;OcSxhg|r~0aWJNEX z*$68#z=ON`D;}&kcJp9Yf5n59tZp9c>JNE1IR~m1;K5z}ArB|luy}A+f5`)J0ed(+ zaP@~g+?o|=xEFuO!mYA_g?sUbEUesuYI$JcUi={ow~+xB?!_Onuw21PrS9`Tr#s_ud4Ci92jsuy{NIE4P5d7O zge!u-2Z9s*RRF)$09^Xt1b;=&ay4okxHS+rgx;k8VK7i$SoK_Qp>zxXDv;l+V{Qn) zOa6+!+Zm{p>7|}ww?82HkLi1EhCaQ(umZ&>8qX8GUOkE}e%jyg2GT<+f` zf8`Wh!ePB|c0ZxV?Y|<%_cXXR)0XTQST*e%>z7aif#5?-w}Jn^YPsB0x@{xY5b*(YZgF=t`$~p;z1*L?<$nq^7~0 zwM3bRwSF6W0pm%bQcb-m&U6}s=E_tj`A}G&IXE+EbW=uvv$`{^qV3Ne=q(N+LAG+v zv1!qP=KHyZAk}WG(5QkvX^iGd;hn9sdq3fKKLaG)=a8cYenW{q^9Ch!fC38@9w!H~ zSCX&~Sh}ISfxqs@AogRx%u#?@TK20z{Ch_?vNu}a=!53OX}3{>;*}`uL^p5967fa7A)Vx!pQ#1F#FSS%(vJ_9j5R^+(#51HYG#^VFLqeArnkf8$+-qWD%55N=;SQEhTA(K4O2jf`zL$Cjc3|PfO z4}xVBWH&ku>LJLF6sn64JWPT$O4vdaRO=8?9oLUPdRG7|dKW;v25#7XHwxH(zXh!W zJFQSv#~{LLuK;h92T4?@@F@LRjC%@*aaUOC4b%+cf&*>b6%iE>p$!xE6~7>n8rX+`OQw`jGmTrSvUg7Eabp` z%4I6qYw(Uj3&4$>ACmUG{G&!e`T?Sru(OqKMidg9-q8=qyva#<$ zAiMjJAnVh-`$?DIo&>tz?qErcImwAcC3!oOU7$7Vzp3u|AfoJ{iIwz$It|@*6R3Be zLwa-O5Gf22$r)QZ=|?g3@pSWGK(2rli15`>f2-Sme-zle)lf@`Dxv->X;Xa2pbi~C zKMHi**yWJ5*o_!Kabdc{;z|SjwJ9w7g#wGeMTGaj=-LN50}KjBfpukytt{5$iqXRX zbQ1U3?h8}aXE@#0=*fN(z56+^zxy1krm)x}Q()Cz5bToI4++;UVBuas7mHnwV>AQR z3*3i*g?oW5BpIk=qd=iH#eB`SfpZBpHd;XdNlEsic~GDu4b>B@8+7%fQnBZ;MPNi9 z8uYsQiDMrc5Lh3Qe^ry=Oadm{6_$+#%7D1k;kb;IRuE@S%ZC;|pn(D`!|pG7*a8X8 zb3oSPnp^N$x4lVT{Z)e8hq{T72C8JC(Dq-Zy7M5YcAtYY1}=@M*g5`#kW#&TRg*{tV-U`@{(*hp!h*x<4eBH-qFXcgd# zwgKYPcc#(>-GK^X_qYZ^?mqulUEV$hO2C5~HbMMhw`~*y{Nu269#)0WNkRCV@ZCNP zpxDE$emvf%4usr;0}EU0AAxg*-+pw;zYGPZ0b$^-uujZC>4UeoJL168eGcVp;B?|n z4j@(Tx+wev0&;VJJBba898n9C+~9BetO2;~FewkC6Mx zEu1`o23J6Uzr>oufogzWW{FTFXxR3+@S%A3+!42lrc$V^>7;(l>V5`Aiu3%x%CxM# z3_*=`g6Rz8e$HC79!u9yI-}Ch5pve8U{lcjnBJ|xnwRe7H9wpA-*d2+E#O|mKo1YCVZF9t z#jE@LuQ>Wx;`9ezW`)mwAqbJ`as5{|^|4&)k8SFAS>PYD^|O@kkGXnX9Q4O5oczf0 z_}}ElZq4p=1C~EbGtT=qR&K;y{Ur}4Rvf0ki5K@JK17Ur@rM@S-<)xI{>C28yFHeV za94k55l55bqaN3P&jpdgG5skIXPq@-;hg_F4rrUv*2193K7%HdJ*@4g#o}yLjMUh> z7Gpi7ftp^w<7z6!sH=9?t<^k89*P_9Pd)tKobm zt=9j92j?*$y<$RcnG$G;?*nA9oIoMBp@Q(ov{t}!6o6ljx zi+%`x|2h~*{JpTe!0s2}KORPV>%dl!{{2qg^kn3!U=HHwL+Z0f;N2f9_h)4HL)f$& zFrjy&F!cO?nmfDYWNsJ?UlE;YJJac5ZY2cBmnnp=u(XrA@1xhUtz9-2ob?3k#ggTJ ztt7wjhfLDm!tc1lj$asM2!FeBdlZi*@1jqWo!Nf0H{H&Q^wjY-tff;Qx2QOe$8g4@NYyW@uWZis2)1smKhHUYZ_ zh=t~Jx%gV-J{dHB?q)InU$%W#4~H8-O!$S<#9qjgIB&^!=G(9&AU z+Gc)U_cyLZGwHv_0%&Dm(bB3svr{-YA9S8#%tP3PBTWpA)1{1c-~NRC5_`fQ=|274 zoHzO1Z@F9!I!ChRIEtFFDF}}=C-(#kZBR5oENIDOV+OJ$<@uBuCo5f%Mj)w4ddaQs z>}PL#%-ov;uZ5ffmn(mDe)t-Ub}Rw2QIMiSfg^+NYh{q2r5{=>T`Wj@ zhWhC^*~|%d(E1_V;rHw7fz`0R9rzlE6N|w%X8T$k&~vsLP$i_ELY&-oRe@F^t4V4z z{b9Uchhq%Vkkf{hsb>!SYO)pxScW{0s#`rz;SrBl#?SVAgn!w-ZudvKes9n$f^jiy zY=b%LC$q39FX!{f+0v?Qg{iZpRbe*e=dwWVQnC8cB1LWI1h5&e9QYMZW-!<*rH$)7 z58DXx(OgrP(HL+_XBuwZ$E-^nfA=B(kAr~Lo+N~>>8u4A6NPE|p%4J`|4k#Sb0`JB zkb7u53e`a!a{c{hFc1~U(<*jM&)$*&T~rpOJ;FfW4^hu&GoD>OP#ve9joLEF-dk%t1m*jx4SW6Qe#1$XHb^9CBCm^ETF zcv(~QY8HIb3sGOsXuvWgs1{cWX-m_{T=_2mtdNz2roG08W8%2E7}VEbqVz)6M~#AQ z-Q$y{bBSO`In4{gZT>)ek(QTXDBXZ&7^xDi418Kznr1fU1;ZAyl3=>#hesG_2t!V< zZkeWzd`40v6|tF%H3a;GW@yg_L(mHq7EYlVo+&ZQDGLWCvrS$a{#(J`ie;{ESQCdj zrSNZ!jp5+J+nX^FiwmJd5{AXDP1FW$^kxwZq6$JBbWVj;woY|~Wi8FDd4g6Jk>TIiyJSWY#gtD^E35)wk4% zTYm?j;DB#0RHSr@rzd1Z+S#C2mNG3}1}@j~vDVl#Th^+x1LmvG z6}j_N%Y`!Qo)YVTnD4XY!G5@R+nAttz8tBDQW}<>Xy!=6rjtS}zgh84Y*;WT_%&u~ zdc902*AYVRWa^>6HOlp|S@(j{*+pNv+`<{XA`VX|!B^18! H|M25K`tL`l literal 0 HcmV?d00001 diff --git a/package.json b/package.json new file mode 100644 index 0000000..9701207 --- /dev/null +++ b/package.json @@ -0,0 +1,78 @@ +{ + "name": "voyage-ai-provider", + "version": "0.0.1", + "description": "Voyage AI Provider for running Voyage AI models with Vercel AI SDK", + "author": "Vivek Patel ", + "license": "Apache-2.0", + "keywords": [ + "ai", + "vercel-ai", + "voyage", + "embeddings" + ], + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "files": [ + "dist/**/*", + "CHANGELOG.md" + ], + "scripts": { + "build": "tsup", + "clean": "rm -rf dist", + "check-exports": "attw --pack .", + "dev": "tsup --watch", + "lint": "eslint \"./**/*.ts*\"", + "type-check": "tsc --noEmit", + "format": "prettier --write .", + "check-format": "prettier --check .", + "test": "bun test:node && bun test:edge", + "test:edge": "vitest --config vitest.edge.config.js --run", + "test:node": "vitest --config vitest.node.config.js --run", + "ci": "bun run build && bun run check-format && bun run test", + "local-release": "changeset version && changeset publish", + "prepublishOnly": "npm run ci" + }, + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.mjs", + "require": "./dist/index.js" + } + }, + "dependencies": { + "@ai-sdk/provider": "^1.0.0", + "@ai-sdk/provider-utils": "^2.0.0" + }, + "devDependencies": { + "@changesets/cli": "^2.27.9", + "@edge-runtime/vm": "^3.2.0", + "@types/node": "^18.19.64", + "prettier": "^3.3.3", + "tsup": "^8.3.5", + "typescript": "5.5.4", + "vite-tsconfig-paths": "^4.3.2", + "vitest": "^2.1.5", + "zod": "^3.23.8" + }, + "peerDependencies": { + "zod": "^3.0.0" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + }, + "publishConfig": { + "access": "public" + }, + "homepage": "https://github.com/patelvivekdev/voyage-ai-provider", + "repository": { + "type": "git", + "url": "git+https://github.com/patelvivekdev/voyage-ai-provider.git" + }, + "bugs": { + "url": "https://github.com/patelvivekdev/voyage-ai-provider/issues" + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..2173da6 --- /dev/null +++ b/readme.md @@ -0,0 +1,83 @@ +# AI SDK - Voyage AI Provider + +## Introduction + +The Voyage AI Provider is a provider for the AI SDK. It provides a simple interface to the Voyage AI API. + +## Installation + +```bash +npm install voyage-ai-provider + +# or + +yarn add voyage-ai-provider + +# or + +pnpm add voyage-ai-provider + +# or + +bun add voyage-ai-provider +``` + +## Configuration + +The Voyage AI Provider requires an API key to be configured. You can obtain an API key by signing up at [Voyage](https://voyageai.com). + +add the following to your `.env` file: + +```bash +VOYAGE_API_KEY=your-api-key +``` + +## Usage + +```typescript +import { voyage } from 'voyage-ai-provider'; +import { embedMany } from 'ai'; + +const embeddingModel = voyage.textEmbeddingModel('voyage-3-lite'); + +export const generateEmbeddings = async ( + value: string, +): Promise> => { + // Generate chunks from the input value + const chunks = value.split('\n'); + + // Optional: You can also split the input value by comma + // const chunks = value.split('.'); + + // Or you can use LLM to generate chunks(summarize) from the input value + + const { embeddings } = await embedMany({ + model: embeddingModel, + values: chunks, + }); + return embeddings.map((e, i) => ({ content: chunks[i], embedding: e })); +}; +``` + +### Add settings to the model + +The settings object should contain the settings you want to add to the model. You can find the available settings for the model in the Voyage API documentation: https://docs.voyageai.com/reference/embeddings-api + +```typescript +const voyage = createVoyage({ + apiKey: process.env.VOYAGE_API_KEY, +}); + +// Initialize the embedding model +const embeddingModel = voyage.textEmbeddingModel( + 'voyage-3-lite', + // adding settings + { + inputType: 'document', + }, +); +``` + +## Authors + +- [patelvivekdev](https://patelvivek.dev) diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..41f2a26 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,2 @@ +export { createVoyage, voyage } from './voyage-provider'; +export type { VoyageProvider, VoyageProviderSettings } from './voyage-provider'; diff --git a/src/voyage-embedding-model.test.ts b/src/voyage-embedding-model.test.ts new file mode 100644 index 0000000..b1cce88 --- /dev/null +++ b/src/voyage-embedding-model.test.ts @@ -0,0 +1,130 @@ +import { EmbeddingModelV1Embedding } from '@ai-sdk/provider'; +import { JsonTestServer } from '@ai-sdk/provider-utils/test'; +import { createVoyage } from './voyage-provider'; + +const dummyEmbeddings = [ + [0.1, 0.2, 0.3, 0.4, 0.5], + [0.6, 0.7, 0.8, 0.9, 1], +]; +const testValues = ['sunny day at the beach', 'rainy day in the city']; + +const provider = createVoyage({ + baseURL: 'https://api.voyage.ai/v1', + apiKey: 'test-api-key', +}); +const model = provider('voyage-3-lite'); + +describe('doEmbed', () => { + const server = new JsonTestServer('https://api.voyage.ai/v1/embeddings'); + + server.setupTestEnvironment(); + + function prepareJsonResponse({ + embeddings = dummyEmbeddings, + usage = { + prompt_tokens: 4, + total_tokens: 12, + }, + }: { + embeddings?: EmbeddingModelV1Embedding[]; + usage?: { prompt_tokens: number; total_tokens: number }; + } = {}) { + server.responseBodyJson = { + object: 'list', + data: embeddings.map((embedding, i) => ({ + object: 'embedding', + embedding, + index: i, + })), + model: 'voyage-3-lite', + normalized: true, + encoding_format: 'float', + usage, + }; + } + + it('should extract embedding', async () => { + prepareJsonResponse(); + + const { embeddings } = await model.doEmbed({ values: testValues }); + + expect(embeddings).toStrictEqual(dummyEmbeddings); + }); + + it('should expose the raw response headers', async () => { + prepareJsonResponse(); + + server.responseHeaders = { + 'test-header': 'test-value', + }; + + const { rawResponse } = await model.doEmbed({ values: testValues }); + + expect(rawResponse?.headers).toStrictEqual({ + 'content-length': '272', + // default headers: + 'content-type': 'application/json', + + // custom header + 'test-header': 'test-value', + }); + }); + + it('should pass the model and the values', async () => { + prepareJsonResponse(); + + await model.doEmbed({ values: testValues }); + + expect(await server.getRequestBodyJson()).toStrictEqual({ + input: testValues, + model: 'voyage-3-lite', + }); + }); + + it('should pass the settings ', async () => { + prepareJsonResponse(); + + const voyage = createVoyage({ + baseURL: 'https://api.voyage.ai/v1', + apiKey: 'test-api-key', + }); + + await voyage + .textEmbeddingModel('voyage-3-lite', { + inputType: 'document', + }) + .doEmbed({ + values: testValues, + }); + + expect(await server.getRequestBodyJson()).toStrictEqual({ + input: testValues, + model: 'voyage-3-lite', + input_type: 'document', + }); + }); + + it('should pass custom headers', async () => { + prepareJsonResponse(); + + const voyage = createVoyage({ + baseURL: 'https://api.voyage.ai/v1', + apiKey: 'test-api-key', + headers: { + 'Custom-Header': 'test-header', + }, + }); + + await voyage.textEmbeddingModel('voyage-3-lite').doEmbed({ + values: testValues, + }); + + const requestHeaders = await server.getRequestHeaders(); + + expect(requestHeaders).toStrictEqual({ + authorization: 'Bearer test-api-key', + 'content-type': 'application/json', + 'custom-header': 'test-header', + }); + }); +}); diff --git a/src/voyage-embedding-model.ts b/src/voyage-embedding-model.ts new file mode 100644 index 0000000..72a5c87 --- /dev/null +++ b/src/voyage-embedding-model.ts @@ -0,0 +1,102 @@ +import { + EmbeddingModelV1, + TooManyEmbeddingValuesForCallError, +} from '@ai-sdk/provider'; +import { + createJsonResponseHandler, + postJsonToApi, +} from '@ai-sdk/provider-utils'; +import { z } from 'zod'; + +import { + VoyageEmbeddingModelId, + VoyageEmbeddingSettings, +} from '@/voyage-embedding-settings'; +import { voyageFailedResponseHandler } from '@/voyage-error'; +import { encode } from 'punycode'; + +type VoyageEmbeddingConfig = { + baseURL: string; + fetch?: typeof fetch; + headers: () => Record; + provider: string; +}; + +export class VoyageEmbeddingModel implements EmbeddingModelV1 { + readonly specificationVersion = 'v1'; + readonly modelId: VoyageEmbeddingModelId; + + private readonly config: VoyageEmbeddingConfig; + private readonly settings: VoyageEmbeddingSettings; + + get provider(): string { + return this.config.provider; + } + + get maxEmbeddingsPerCall(): number { + return 128; + } + + get supportsParallelCalls(): boolean { + return false; + } + + constructor( + modelId: VoyageEmbeddingModelId, + settings: VoyageEmbeddingSettings, + config: VoyageEmbeddingConfig, + ) { + this.modelId = modelId; + this.settings = settings; + this.config = config; + } + + async doEmbed({ + abortSignal, + values, + }: Parameters['doEmbed']>[0]): Promise< + Awaited['doEmbed']>> + > { + if (values.length > this.maxEmbeddingsPerCall) { + throw new TooManyEmbeddingValuesForCallError({ + maxEmbeddingsPerCall: this.maxEmbeddingsPerCall, + modelId: this.modelId, + provider: this.provider, + values, + }); + } + + const { responseHeaders, value: response } = await postJsonToApi({ + abortSignal, + body: { + input: values, + model: this.modelId, + input_type: this.settings.inputType, + encoding_format: this.settings.encodingFormat, + truncation: this.settings.truncation, + }, + failedResponseHandler: voyageFailedResponseHandler, + fetch: this.config.fetch, + headers: this.config.headers(), + successfulResponseHandler: createJsonResponseHandler( + voyageTextEmbeddingResponseSchema, + ), + url: `${this.config.baseURL}/embeddings`, + }); + + return { + embeddings: response.data.map((item) => item.embedding), + usage: response.usage + ? { tokens: response.usage.total_tokens } + : undefined, + rawResponse: { headers: responseHeaders }, + }; + } +} + +// minimal version of the schema, focussed on what is needed for the implementation +// this approach limits breakages when the API changes and increases efficiency +const voyageTextEmbeddingResponseSchema = z.object({ + data: z.array(z.object({ embedding: z.array(z.number()) })), + usage: z.object({ total_tokens: z.number() }).nullish(), +}); diff --git a/src/voyage-embedding-settings.ts b/src/voyage-embedding-settings.ts new file mode 100644 index 0000000..624d6af --- /dev/null +++ b/src/voyage-embedding-settings.ts @@ -0,0 +1,38 @@ +export type VoyageEmbeddingModelId = + | 'voyage-3' + | 'voyage-3-lite' + | 'voyage-finance-2' + | 'voyage-multilingual-2' + | 'voyage-law-2' + | 'voyage-code-2' + + // Older models + | 'voyage-large-2-instruct' + | 'voyage-large-2' + | 'voyage-2' + | 'voyage-02' + | 'voyage-01' + | 'voyage-lite-01' + | (string & NonNullable); + +export interface VoyageEmbeddingSettings { + /** + * The input type for the embeddings. Defaults to "query". + * For query, the prompt is "Represent the query for retrieving supporting documents: ". + * For document, the prompt is "Represent the document for retrieval: ". + */ + + inputType?: 'query' | 'document'; + + /** + * Format in which the embeddings are encoded. We support two options: + * If not specified (defaults to null): the embeddings are represented as lists of floating-point numbers; + * base64: the embeddings are compressed to base64 encodings. + */ + encodingFormat?: 'base64'; + + /** + * Whether to truncate the input texts to fit within the context length. + */ + truncation?: boolean; +} diff --git a/src/voyage-error.ts b/src/voyage-error.ts new file mode 100644 index 0000000..b880cbb --- /dev/null +++ b/src/voyage-error.ts @@ -0,0 +1,18 @@ +import { createJsonErrorResponseHandler } from '@ai-sdk/provider-utils'; +import { z } from 'zod'; + +const voyageErrorDataSchema = z.object({ + error: z.object({ + code: z.string().nullable(), + message: z.string(), + param: z.any().nullable(), + type: z.string(), + }), +}); + +export type VoyageErrorData = z.infer; + +export const voyageFailedResponseHandler = createJsonErrorResponseHandler({ + errorSchema: voyageErrorDataSchema, + errorToMessage: (data) => data.error.message, +}); diff --git a/src/voyage-provider.ts b/src/voyage-provider.ts new file mode 100644 index 0000000..327d0bc --- /dev/null +++ b/src/voyage-provider.ts @@ -0,0 +1,128 @@ +import { + EmbeddingModelV1, + LanguageModelV1, + ProviderV1, +} from '@ai-sdk/provider'; +import { + FetchFunction, + loadApiKey, + withoutTrailingSlash, +} from '@ai-sdk/provider-utils'; +import { VoyageEmbeddingModel } from './voyage-embedding-model'; +import { + VoyageEmbeddingModelId, + VoyageEmbeddingSettings, +} from './voyage-embedding-settings'; + +export interface VoyageProvider extends ProviderV1 { + ( + modelId: VoyageEmbeddingModelId, + settings?: VoyageEmbeddingSettings, + ): EmbeddingModelV1; + + /** + @deprecated Use `textEmbeddingModel()` instead. + */ + embedding( + modelId: VoyageEmbeddingModelId, + settings?: VoyageEmbeddingSettings, + ): EmbeddingModelV1; + + /** + @deprecated Use `textEmbeddingModel()` instead. + */ + textEmbedding( + modelId: VoyageEmbeddingModelId, + settings?: VoyageEmbeddingSettings, + ): EmbeddingModelV1; + + textEmbeddingModel: ( + modelId: VoyageEmbeddingModelId, + settings?: VoyageEmbeddingSettings, + ) => EmbeddingModelV1; +} + +export interface VoyageProviderSettings { + /** + Use a different URL prefix for API calls, e.g. to use proxy servers. + The default prefix is `https://api.voyageai.com/v1`. + */ + baseURL?: string; + + /** + API key that is being send using the `Authorization` header. + It defaults to the `VOYAGE_API_KEY` environment variable. + */ + apiKey?: string; + + /** + Custom headers to include in the requests. + */ + headers?: Record; + + /** + Custom fetch implementation. You can use it as a middleware to intercept requests, + or to provide a custom fetch implementation for e.g. testing. + */ + fetch?: FetchFunction; +} + +/** + Create a Voyage AI provider instance. + */ +export function createVoyage( + options: VoyageProviderSettings = {}, +): VoyageProvider { + const baseURL = + withoutTrailingSlash(options.baseURL) ?? 'https://api.voyageai.com/v1'; + + const getHeaders = () => ({ + Authorization: `Bearer ${loadApiKey({ + apiKey: options.apiKey, + environmentVariableName: 'VOYAGE_API_KEY', + description: 'Voyage', + })}`, + ...options.headers, + }); + + const createEmbeddingModel = ( + modelId: VoyageEmbeddingModelId, + settings: VoyageEmbeddingSettings = {}, + ) => + new VoyageEmbeddingModel(modelId, settings, { + provider: 'voyage.embedding', + baseURL, + headers: getHeaders, + fetch: options.fetch, + }); + + const provider = function ( + modelId: VoyageEmbeddingModelId, + settings?: VoyageEmbeddingSettings, + ) { + if (new.target) { + throw new Error( + 'The Voyage model function cannot be called with the new keyword.', + ); + } + + return createEmbeddingModel(modelId, settings); + }; + + provider.embedding = createEmbeddingModel; + provider.textEmbedding = createEmbeddingModel; + provider.textEmbeddingModel = createEmbeddingModel; + + provider.chat = provider.languageModel = ( + modelId: string, + ): LanguageModelV1 => { + throw new Error('languageModel method is not implemented.'); + }; + + return provider as VoyageProvider; +} + +/** + Default Voyage provider instance. + */ +export const voyage = createVoyage(); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..afc64e8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "composite": false, + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "inlineSources": false, + "isolatedModules": true, + "moduleResolution": "node", + "noUnusedLocals": false, + "noUnusedParameters": false, + "preserveWatchOutput": true, + "skipLibCheck": true, + "strict": true, + "types": ["@types/node", "vitest/globals"], + "jsx": "react-jsx", + "lib": ["dom", "ES2021"], + "module": "ESNext", + "target": "ES2018", + "stripInternal": true, + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["."], + "exclude": ["dist", "build", "node_modules"] +} diff --git a/tsup.config.ts b/tsup.config.ts new file mode 100644 index 0000000..fa5392f --- /dev/null +++ b/tsup.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig([ + { + entry: ['src/index.ts'], + format: ['cjs', 'esm'], + dts: true, + sourcemap: true, + clean: true, + }, +]); diff --git a/vitest.edge.config.js b/vitest.edge.config.js new file mode 100644 index 0000000..9c43c68 --- /dev/null +++ b/vitest.edge.config.js @@ -0,0 +1,12 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [tsconfigPaths()], + test: { + environment: 'edge-runtime', + globals: true, + include: ['**/*.test.ts', '**/*.test.tsx'], + }, +}); diff --git a/vitest.node.config.js b/vitest.node.config.js new file mode 100644 index 0000000..585f6b1 --- /dev/null +++ b/vitest.node.config.js @@ -0,0 +1,12 @@ +import tsconfigPaths from 'vite-tsconfig-paths'; +import { defineConfig } from 'vitest/config'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [tsconfigPaths()], + test: { + environment: 'node', + globals: true, + include: ['**/*.test.ts', '**/*.test.tsx'], + }, +});