From 9d16407b0d7c0c8c9febcbe6b7a3d07bcec7a2c6 Mon Sep 17 00:00:00 2001 From: Matthieu M Date: Mon, 19 May 2025 09:24:17 +0200 Subject: [PATCH 1/6] pushing mcp2a --- example/README.md | 122 ++++ example/claudePrompt.txt | 25 + example/claude_config1.png | Bin 0 -> 121873 bytes example/claude_config2.png | Bin 0 -> 69412 bytes example/claude_desktop_config.json | 9 + example/server.py | 700 +++++++++++++++++++++ example/server_cal.py | 956 +++++++++++++++++++++++++++++ example/server_mcp.py | 337 ++++++++++ src/elkar/client/a2a_client.py | 6 +- 9 files changed, 2152 insertions(+), 3 deletions(-) create mode 100644 example/README.md create mode 100644 example/claudePrompt.txt create mode 100644 example/claude_config1.png create mode 100644 example/claude_config2.png create mode 100644 example/claude_desktop_config.json create mode 100644 example/server.py create mode 100644 example/server_cal.py create mode 100644 example/server_mcp.py diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..97f90e2 --- /dev/null +++ b/example/README.md @@ -0,0 +1,122 @@ +# Elkar A2A Google Services Integration + +This project integrates Google Calendar and Gmail services with Elkar A2A, providing powerful email and calendar management capabilities. + +## Prerequisites + +- Python 3.8 or higher +- Google Cloud Platform account +- Claude Desktop application + +## Google Services Setup + +### 1. Enable Google APIs + +1. Go to the [Google Cloud Console](https://console.cloud.google.com/) +2. Create a new project or select an existing one +3. Enable the following APIs: + - Gmail API + - Google Calendar API +4. Configure the OAuth consent screen: + - Set up the OAuth consent screen with necessary scopes + - Add test users if in testing mode + +### 2. Download Credentials + +1. In the Google Cloud Console, go to "APIs & Services" > "Credentials" +2. Click "Create Credentials" > "OAuth client ID" +3. Choose "Desktop application" as the application type +4. Download the credentials file and save it as `credentials.json` in the `example` directory + +### 3. First-time Authentication + +When you run the application for the first time: +- The system will automatically create `token.json` for Google Calendar +- The system will automatically create `gmail_token.json` for Gmail +- A browser window will open for OAuth authentication +- Grant the requested permissions + +## Claude Desktop Configuration + +### 1. Configure Claude Desktop + +1. Locate your Claude Desktop configuration file: + - Mac: `/Users/mm/Library/Application Support/Claude/claude_desktop_config.json` + - Windows: `%APPDATA%\Claude\claude_desktop_config.json` + - Linux: `~/.config/Claude/claude_desktop_config.json` + +2. Add the MCP server configuration to the file: +```json +{ + "mcpServers": { + "a2a_elkar": { + "command": "/opt/anaconda3/bin/python", + "args": ["/Users/mm/elkar-a2a/example/server_mcp.py"], + "env": { + "ANTHROPIC_API_KEY": "sk-xxx", + "OPENAI_API_KEY": "sk-yyy", + "AGENT_URLS": "http://localhost:5001,http://localhost:5002" + } + } + } +} +``` + +### 2. Add Metaprompt + +1. Open the Claude Desktop configuration file +2. Add the metaprompt from `claudePrompt.txt` to the appropriate section +3. Save the file + +#### Example Claude Desktop Configuration Screenshots + +![Claude Desktop Profile Settings](claude_config1.png) + +![Claude Desktop General Settings](claude_config2.png) + +## Running the Servers + +1. Start the Gmail server: +```bash +python example/server.py +``` + +2. Start the Calendar server: +```bash +python example/server_cal.py +``` + +The servers will be available at: +- Gmail Assistant: http://localhost:5001 +- Calendar Assistant: http://localhost:5002 + +## Troubleshooting + +### Authentication Issues + +If you encounter authentication issues: +1. Delete the existing token files (`token.json` and `gmail_token.json`) +2. Run the server with the `force_reauth` parameter +3. Complete the OAuth flow again + +### Claude Desktop Issues + +If Claude Desktop doesn't recognize the MCP servers: +1. Verify the configuration file path +2. Check the JSON syntax +3. Restart Claude Desktop +4. Ensure the servers are running before starting Claude Desktop + +## Security Notes + +- Keep your `credentials.json` file secure and never commit it to version control +- Regularly rotate your OAuth credentials +- Monitor the OAuth consent screen for any unauthorized access +- Use environment variables for sensitive configuration + +## Support + +For issues or questions: +1. Check the troubleshooting section +2. Review the Google Cloud Console logs +3. Check the server logs for detailed error messages diff --git a/example/claudePrompt.txt b/example/claudePrompt.txt new file mode 100644 index 0000000..77b9702 --- /dev/null +++ b/example/claudePrompt.txt @@ -0,0 +1,25 @@ +At your disposal you have A2A agents, which are specialized tools that provide extended functionalities. +You can invoke these agents to perform tasks you may not be able to do directly. +If you're unsure what capabilities are available, first call the discover_a2a_agents tool, which will list all available agents and their functions. +For example, if you are asked to send a calendar invite or an email invitation, follow this workflow: +Workflow: Sending an Invite (Calendar or Email) +Discover Capabilities: +Call discover_a2a_agents and look for agents with keywords like calendar, email, or invite. +Choose a Tool Based on Goal: +If the goal is to create a calendar event (e.g., Google Calendar) and invite specific participants by email, use the create_calendar_event tool. +If the goal is to just send an email invitation, use the appropriate send_email agent/tool. +Create Calendar Event with Invitees: +When using the calendar agent: +Set the title, start_time, end_time, and description. +Add attendees as a list of email addresses. +Optionally set a location or video_conference_link. +Send an Email Invitation Instead: +When using an email agent: +Set the recipient_email, subject, and body. +Optionally include event details in the email body. +Confirmation: +After calling the tool, check the response for confirmation or errors. +If needed, retry or adjust based on the feedback from the agent/tool. +Example Use Cases: +"Create a Google Calendar event titled Team Sync for tomorrow at 10 AM with alice@example.com and bob@example.com invited." +"Send an email to charlie@example.com with subject Meeting Invite and body Let's meet at 2 PM to discuss the roadmap." \ No newline at end of file diff --git a/example/claude_config1.png b/example/claude_config1.png new file mode 100644 index 0000000000000000000000000000000000000000..fd948da66e54cccbfaa5492017c2f3b7b817aea4 GIT binary patch literal 121873 zcmbTe2UJsA(?5(@P*G4+P!K^8k(MX|DqRr}ks?S5B{V}X(gGn6L<9>0(nUImg7n@J ziiiqGF98CC7FtL`4J9P`hg+Wa-bbFbzV)$I$l;t_X3xx?J@cD6yw=fDJARb=C=(OY zarOK6beWhAEHN?d&p&*KafjVA@goz{K2v*TWgT^8WquuZR~vgL2ouxQNUwMD8h71J ze-DVWiqX+gRu1Agc_(=7PLEJUJ{?*OeqEt(M{n);n}I7ApJ!;FIB-<+ zuAZ{qxURdbA#3?u!^GC|Q`&5RPuh>G3WNp8RVC%l6YMXitbZlt+l$wB=@vVFNVnLsRXO|uT zjY0H|A_BzKV*YNONI5F#OjY8GMkNeSK;+rkiN>zesN~9g7a*~{v)m``Shz>8pY}74 zqulzvJ698JrZ{|4C+cVi-z{5PTau*(8SFNI7^&N6YBGs1t`9Tqduh*ffN`~t@!@8C z7*dG}VPau?b1**l(wP5#dSEGS|KHd9^LHEG(Nk7eXMF2fyF(x@9(Jyt`SeeC#!zVc z$3~t;nh#~HU7Z1zPh71a03YY4yIq*%d}J7x&Ja&aejjHi7Y`X9`AdJakYQZ!-UeRc z|D%bgqx>Z!O&xw^S9b`%G~gQG+9idf{QUfK?oVuFbnmJD)t&K|{3Sb2&!;j#ptrX- z!21Tk)!i0&{r2tKz-tmf2?=pV3vmyai>IZJxQmCtpM(58&OL~SwY&XOPkUDv{@rmc ztz5l48bF4qWP=y zKOg?pQ4YAf^nbA8Pd@)~m%(U-qjJFi2uUQ|3uf-xQ6E%bCWO(Qa8UYI13~RBwZHo6fCD!Otzx4 zuH1LW)jnk9GnFem-PYZ6`haJqx!*Y)m(64>ZrCPvCD#ciXm8@F{Djh^8xrqI1cb%% z{kj~J0GV?3P%)}0ayp9Pd&4<^FbuY!2c%$Os36ZGF#TQ)Xv!o} zd=TCb#Kw(Pj3dHk^;-;{&yRfA81ET6*-c*;UyR!pP8V%7c7PH$@$fdd+JWAoyBn|{ z$RfYWN8Ve_zFETtp;JGAznWG$(EbxJ7VDS&Ujv-s)fCupkAp+j!RK2(h5b=&g|^Go zZ*dRtQ=dy$%)k}q$M=8uENf8Ev7OdQ9j|OMA0GSKX{BheoFyvoJ}fm6H!(P+0Ht?KzDJy^nvGq?o~*a?*G zT^tK**wqn{(-orw)J=rksKFxDR^2neheD8#=KD;BV-*p>&)1LVe$oFP>w4z6RQ9!$ zO3pR*Pog6x-)GIQ>CUZIUVF4mcC02Vh*bLpl^d4DopDmlmR^_v6KX~Ss6%fEK%McA zOYGeBJC}a6z+G9`V`3+=*Z|^@@%(GtN7{b4nq<$oGy}1+-{Z!dj|_}1%wB;&os8zJm z7%W&izQFTlR{Hb|lKI-%KoMDf&4?d+n{%iSzHt`u!4~15< z_H5L^@D0%ewBT<00Ab4Zu;DIF!m>&^ttX-iE^P%oqJ#>c<9D5elDI= zZ9JL>;clTBT9cCc9Zo--t2L&WnT&pQCtJ_y_EwYU>WV%BSmFy+$U7+CxmAlDi;6h2 z>>Z01aNV|Dr&J*AGafAU95cce-y!Sj-TnMb?<=-%n3>k~RCvo9#SpN!!3W3`dMJZ4 zj_*-bbFU|nVkl}nNb6~>Op@9xDBv%YjK5{1=Bg375=2vJtL$t~uDQO}O<)Rw9lDMp z*@>f@)_CT`^A!vVun035M_C)E=Gs&?T}66cUJklX*tCsTM@Fih2Of)@2s8ynqzb~sk0^^1npw+8PeMam~_Cf)O{4fO9pwo`g+HELiE`S>*5AmH-zs^oNF~K zm5z8j-jn-kLwZ27;FrY~AcW|R+3|0Da_13RRkX@4Ak4bZI^WI`5lo2i31eyVUQVhv zH`b>TP!JqN?GX=`9Bl@I=(v@!GhQ*SFjW$O;PLFw$E-dLz_>I<*xDnfW}Q7`m2VH- z02&%bSZr1F8|2r~W(!e#sE4RC8=wgTLi#l$Ig%Fv=R~A!Rr0=^Y<##Vm9bo&QLz>T zryorI+U6Z)dxG{Bs zX0HtpMdwGS7c$_;dz>oPETP3&@b20b$u}w$*~IEf7l@GyV86*+4F&jq0Pcdr)Qykn zwtBCZJ1L)8YV{tf{RU1*H+-qm-+CHo-gYF`6>nUZn`l^vdY=^K!a9I_+T3%ntuh#m z(3UNs8eGH_(vNy{ivw)^%Ot_+V>O`!a!Bhjg;gsU1*KM)OrGnPG^$fr4U&y4+CDh7 ziJ@Limv)><3Fc~w&3~Pz#kn&(c53t!&{pMVt>a$uS3wx3@eWKXySZ@(|i4 z%u!ndfK^#3U0pejXg#oHX1wZESUKLaQgM&Aytd6H0NVJ81huM zr&LqH=i>)}SjXy6;uyVVRQFJxJNKx6vWcmNgU$9>1zjv$rO3`;E2`D|I_eeJw5_Xc zpuAJ`tJW-1VwBIdhB(Y8*7%^9Y_g0 zUpVXPwV~@v8{qdKQ}aj1*m*0;ILe&FM`B6(KV#>^tBGEUbyZV*(>=Ee*gjMg0X~dz zyUqu}j8YKQ6)iyQdPNYPqkCtoNY z&A%fx3vux3vWWE#K9Cy1?clXqQB9q|b7E5{7$dp5S&?bi@d#|yO3F#kud*8RJ9jh z(nqdEBIn`;X=j(BU@#Droa~Aj;q!~j$7GRiwHfuk&usQ+G|00etD>niDEiQ9WmQet z_>TB<1uqk)p#Dav!#$w05hY))vfFA7!PR{UeqGsq&AuVcss582YbXhq*Bx|3>J5tS zTw2+@QXIgYm*JAbj@Yk^W2qeS2<3b0&^=ls?|sg<+IPzYX(61Rr7A&uGpp( zy{YduRKw-J6q`Xwih>YqA);gI)|Piir&qduAxQv@Y?z%XsZ`|h)E9!oJOwZDxQ=jD z_oiWUa8>Y(^StR@alLrjY-)4m6shFsz>lq}8ly>z!<-H#i{;kZto3wLbt3E@ z#q6@?I0xviBGAO^+>6$7`GO0LWS?gH^O{qW3y0dir@$zAe*QDubI__4)Ys<`HQ20T zmiPz-pWIf8Oc*ve|8@D#J{})Q`T? zq=ARlRDr|t7A>}1;^7&$85{XCHwJ7FceUzpx%tg%r0Lf7<;Qh(icYC0O{g)2Iz@C} z>ZwAfHg~@wo^Nhz!B}iL7}GajDg-#y;<)l&$IkMJyN(L>yodvvWG7&-<_2|eUQTgr zhGPmj5^}rxgxuVKjbWV&P)yN>X8BsuD1nB~$T!K#SMCtSaxx5yW#lS**aC{4N3g`1 zTxj(5!NFlwtL#7%aiCGEMc>zza!GM)7VAK#WBsfvZV(o8uyZ8T5tIK=7QKQV-3V1( z4-fUvn>)qT8Hxw91NZ_mV<$=;G0V%Oub||dQgH;{n`R4#(6G`S;-1c0M}SVOR^i5 zh_;e_+^DDH#N0!@X}P#+c`zVOWRg1Dvk9B)p_2NCbo)xlRb;r}Hi>TW$iS}4dsjcq0f=pL^`I@R|3C;Qv*E&I`tFWY$5M)%?sR`DHlDi>DQ_|>)JqF7#Kacms1 z{L4x40`t1qGrsidU&s~l0{YE*6b)%@4YTttv@fUwXdCWx%oRLJD|O}`ksfu?pl_NS zG(|}kIByIWMv9|?ElfqRB_4&|xn-4YDBAPLqtSydx$i}>@>QGQE&WxXQ~gFw<@TWC z)yFb+LE%LX_Er|Ikvw3do^*Z6PWT3{Gt??g#U)S6NUUy1jua*F=3-jcfH_%7i zfb~U_g!Ks$E5>xeL1i+;mcuvfpy;^5To5oV12(L#W%-IIWqKAnd@4o3PkXcG%mq#4 zlBAmvd)=KTtVV%BFuSpxx|ev|SN)E<&aW7f>`T{BWzoB!xJ~~r&c#4jp$qtNy$THv zj0rS3q6i57QNL+f?vtW?U!aU5L7-DEFoPt8F&>dJ6cU>+9yIn-)Y@!g|)>v$dC zzONMru{n|Ip5B(n6uB^yl^E;{3Vavt+3Z@$mnov*=)OGej}CDKzMVy1_A9I;qT3?y z;Zfe%Z`5owVh_sAPO@B^HopVcnB{Alj9tAU3I;zh4K)NO{X9*(p@EB^bY<25DJ3H3 z8o*e^QN?oeI=WnpNsB1Ac3xbi}~6a-=dLtUHL zs&S^VUYBz>nkO5tW^CRz`nF{H{794ZR=~@__hr7@I@&jiyf7~G6)BgM@6Pj46|l@{ z;?LM5=i`^f0k7@F@M-%G80} zS#Ty;xoq3g4dqoINmGc=R4@vro=5tVM>;X)HG`J0?M{sccv7$F`9nB(U0p26Qeks- zd)1taMhV&AOI4)J7Sykq^U!R+(2*a};q{#n_<*rZH|ne(ZE34*gB{Cg@tj`W_{Y^o z_#bV(-~3-&>>q9EOVp7L4sOHIQay#VbBQ;)8q4V$z&-oGk zsM6AMJr~>kLsihvP8ywYoZ`l9M}F`L^7IVU{;U_@)O2@ue=^&VJZ$+%`Qd64i?FfO z;lupTn3#Y2rSoVX9$ESby6!n{`ryGENr%4sA1dtt6!Ngz%rB~{^e5*Snm@~fo^k71 zF0sV=J3Tj@HmtJ{*7yY5P3p*A36|iU3h_50=km-DqRe; zC-Eot@57g}dM@;-SGZy?1TFq#-h)pJ-dTyCuRyI%*(j`%)tcv}8J%26bYTPjL3QCh>r#ihLB zBW%@1B8^Tfu~rNJwX=}uAfEX3ck%;$4qyHuptq8jq||uxJe106m*a}U@+xUpjZE@lz^9`&yMbCd*gA|CrfY(P@U0vdf7tgtYjNrw5ffBKu z3jf-N!zC)uHPEHi2I#462#Np*fM6z)EC)e1I$T$6kR#|G_5tGFf$i#D%5{n6|%1qk` zqRYHu#9Q7ir@^kNX&MV(#E4OQXSgEK?ws4Ua8zn4u%2G0+av7~)sZZ7f3|}c7ZnP8 zXPd$U#QNO;63biB=)Q1X*(YjRS|e>4OuvghP3FMlKpn$3HP@bd zi&#G6@~F@_u}Cjix0%&Y9jrvo>yhJp{}8^46<4XYf4-1Tdum}_J(m+!;^LT6pZf~} z4t)ZGL)2R47raKcBE3BwuabdNMW1>m(xsx1f;BrkYq4g=vt~X*BSq;y+FV)V>|TPm zGVukdg~?VEU1KFOcBb>hc}>C6bBmn-tiFE0A3JDQ)9eERHH3N@eS5!dj!m$8Bx zaV@ci*t0K_S~z}J)c0>AuFmXegmqOR=jKCQ8k z8S&Zf+IScQS4C$Q_Us*?cO!szvQfF9kQ&_XP;EAOx_qBS^J?m&Q zVh^yd`?X2MCr4Y00Q=sWSPXP9Jt)ht5H-5((OG|Kec|AA(skQv3Q3;&OT&etZ%Av$ zQ5JGfO;1NhL=#8JCu++!`q5W@)~xfGo`>6E1_1TE7i+!dFKfgD&>9|!v8A0CE}lD= zu=0(b;5(w)?=Kzmn zb|J6N`UtJ>N|i}pl4dL%CT-aP`u`yp7jxuqrHxU~C8GVJDPC}bs)YQ`(r6H)~ z-nIbQYAsZ|ucEwSSbuGColCy+kZ%qVJi6hzH9|>!2^m^B1GvEc`!S;=c5t#M(EH2S zDqz^ctqtEG>-Ef=jCM%H$X z&+LbJ>KLW95=)(Ht>}1^C` zI^@yMpLwM0mVMXeW-1uhi3-zouw^bhJB@&ty1P^ zx7_M0&9gc3esjB&l26?SyelCoa=At#k9OQM0sF74w1qw@ruN@H<=pHL=Tn@o*CB&l zObu2I<8Cefab8$iTLDZS%*^#%>;k*Hdo&XrZRs6nujd&F442uPIho{1=NVOt#Kd~l zfmp!)sIq~(x#L;op`5@Y&7d+Ld%IBs&V`?{iR#{6X*8=+;8_t(Y%xqqoA@<r9LNl$OI z$8leDQfvLfYuRZeuHHZtLKl({F6f)3v(*}AgcdSk$mP;2>j?Q)GY(9NJ}aoiCm}RG zmlt+(%rky^`rd^?WU}lig5D^z?R1wFe_l*@@Z*{8kdo~;&YsC$7b;xQ2ApC>izQB< zH`Z9$rMZ_9X1}+|Mk~ZLvRoJkBop#a*%{4#)Xck1T}_zi2C4n-m{?s(L-8>dbkn{q zE?bmKb3`2*hi#eVI;%O-S9pUuhXaUmRF3>|us=|nv|gsC9rZ|{^WADEPfoP@X>f*9 z=7X`@!-mR>|~Au@6M=L())5?4VdFBzS8$!P>2jwa_nC*p|)Zl60FB z$da~TT3m$-w&|^W@O?tF*(O0;w9h<2fHPjFLJ15`L2mXquAu9a3I&$8e-2V;jKd7z z&1)&06C6ycqpM3h4bBY+p*D(>yop2Zjj5__&uA39^V#ST)T&sjENMF;)LMXu*j(ni zSweWzMw^tOB6jAvk}a&BW9WoR#nz?XkO!S9qqsUaDQ0c$SaRB=ePy8pd#Yv%0BVjz z!HHR?97eKl^v*o!{S+CUEa^J-*tP_vv$6tRxW{eYvVGj%Jq@}E8hmF!!&gbHU)sY* zJ#iIZ^0jqkdYbEJutxk(AjI?nB{zJy#-fE|cjZZP?W7jTEr|X!E^cBT(ce+aDjD7^ z4jl9+$jDWxF3`c-xZ?Hol2JDHJWgVuValgUtjruSVElS9<@*q%P@cRnWAm}Sy**l3 zufFS?t@#ni+IrsYm0|p;(T9c9^8KAx>Wa=i;R~1q6(?9xmuM`ht#1=bN2X6oOBBLZ zXJ`OWB_gQJItO}M+D^xOxZF`ME7B*|AV1PgCIcWKpeIYz6;$-8&J(@1d8JsxF%=z!Z;xN&|k@L2v2ru zI0iN2(=)%*pe^NDDmi4r`a*xSbiT>!(N34u8S$*K`VN`x#X1itA2Q#hHy4$Xb!Jdf zO5f=dWP5KYiWgy=&@zi+z%&%3sM@qT8l)~D+>YYFLZcDC`%Tbt3s>9fBZa3yuN0!dtm&ULe_pk0FG)!F@ zej#(|@uA;sh!RuawZLZ#SoXgu`zz>pEqvm>6%*L%!a0ZF-{KGkInRzBI2-T-|GSB0 z5EJFv3Q`rN(rJJy+Zn_``AU84qx#?FR<3 zv+3_Sp_1&<`|r==_w5-lz7}aXjJzi=(Z{+Mm-d}GeER;r6MK@w|1OQW#o4$r3Xm)- zXt$otx###^Cj79e@0um<{R=huO8DQ@dUrwhb@mYhAwT zoZt^UIM<*t9;DpArxv8`k3jDwK1L`k#!wz+zVlx~_IkeSfu;Ws)i@$@;_AES2lBc5 z_YD1+0+l!fmVD0aE#M+%-y`AzOC01mo$J3x0Zf51z=q45UNk|by{J-RfE`EAK8yeP z<7LfWjOue^%pKh|upH0SzQ^f*;{KC;fOO62Oy$wNn6-dpitifOif7cP?Lqr%u@fhD zJ5T+O&Q(g!Xtgr5qiI`v^*$orAiu=1y`*z{uSM4Y7_u-6IM{{Q>*)*5E;czphUT2W zJ=~sjmnrcpD%NZ5@ABEB`@RDlApUpXn2`4r6#9~21%wCaj?_Va$A?n#WdU|$N!v&l&y*PLg zQ(^09t_9X~xEuDxvBZu&1*>ku%Tg=x#MV|rON&^L#|#lD(m1%Ig-=TI+W=>qE2?ly zJ5_1mPm{jpPZfZAKlP;D@gFMS+XR6?1^clIo7B!vKIp)m|6+s4mGR>W>d^h#+;d8A zE1Zqn!g+5;r9|FQ12`(uTnL0cN^Vo%N-b7PN8_$|N$^FO(o1xB_`@1l%gChGy zDDz2PpSXO*$t)S!6{l(5Wbb0x?CHm~2NqG5q{v}ujzUoHW5WA~v1MWQ+ku_e@Wbn0 zvZVgGdvv%CO57;*JUzz7fGfgk#dUpEZOop$(n!=u4jzzM+37%5#hm}uNA&suDP2B& zg*41>K{^R9?2BW>xeTNjLJc|YIo2N+^g#CCvOZI^kDFYXLT$GVpq#W4&~ac%N%cp{ zE<1fa%FKK?2B>2)CQ3~4ZvTM)S*!ULDNZjswf|o`C)8O8lp44=RMF8C z&XN8V2OS3qP7S>_OjJE1_;p_vTe)Fc1=7&z>ziMB4|!pUdT@45@qa7yLe(<{9r1m` z*k>=kupaOnev|F~+E56LTnYQ~=-%D5Lr3~THr`NIdWtOfCzm~^@b{&~U(i?imyU@D zTHNSi4Dj}A?rHOT8A^BVAijl^qiwUlzVxvw;>~e$Ij5dgpHc0cQ0_;SH;yImeR^BTS4gJyTjg+UMHU(4K9A%F^Zj zmm)lCDBu`ONfswp@~1m6`?4dSADvYa0tdI;XN)iME+f8c`0c#||F%tmH5cC{CgN$F zc|7@H=AxM&zGZxzoc|`O9_)OWUnfq9|HQkUojD)|C<-M1m#M#8_l$bnE`XKe%ayc^ zhFrfxo|F1x;W3B%R02PKyi5!*eONy)2dz ze+Bbf%eVPL)&Di=pC`fzy+9GOyLj}AM?Q52H2%G!zZAqu^u)WfW+4xSz)tMP_8!H8 zx#ikD;Jc(GF&17)BN=bvGzShhqdrg}Hs4nq?RVQcM9ip2$9HW|n zR`9U8^e=h+wNrGSE0t&+v8UtT@(V8JkAGVD0Gj^FO1tbm1~Vxhb{?(K=LCW;(Rnsv zkQ-x^X0jnfxgOz<>o@$a(fcVU-HDMv!%;}}@l9;9j|=K)sRjEx2T0p!-j(k>uHAuL zQC0{4rTTw*A!#Mdjp|}ZEo7@rNp25rdiv+~ah{R-<2*ed8R;39H4HsvvoOhbLHK(9 zz|Yc7_hj$h&jA+$}vf36GomyI3qehS;@8i^L{<-P@v&Q$&z<2TNs8TKD zN0Eqcxdo4W47Px_zhXQ(ZetD1?0P?y%PlofC8p;xr;)22vEEq!POLr-f~}*}%6$)m z>)O~=BW{s)SgHDPdyfCjRLM5IA8)IiZWI1(H8(Y`;*rAklJ!QJ-j8sX+kTrlD;*fu z7IT_=WRhRc>y>X)5v|jm3Q=vq+HZtH!@?9(c)PxY^Mb7e9nFVpzX^^F+|RyL8dJPM z|B*!+LnUIMZTUB=XW8W?2juIwKJkor)OuEwy^D&vR5{|8`77zV8G<%13tJAhg+$1c zuZqiH{>y~_X|xU>iMjS#rC)7QfA%U;N5eBK6FMArYs$0IzWL1=m!WiJ7WBpVFB=7z zT3c;i%ek;yPFO@7hW@jOz19BnaSuHC=~5$0i-~L96#=zmfg836L6k2+ltMrYl?9}t zHlX;N9LzIm0?U z_SE~k%q_!aT)w(hx;JE&*<{yFY(c?KKN%9%{hW6qcgO=8s)U~97Nocn6~&8kRW?-4}qdG8%{cUmeS>d-pAt+Z1f zC&L7+UUT0fVn~8QH*4TyyzW063syU9vgKiOIVJuSXtmiYZB>Qq=b3nO5eBTJaZeuh zdme?M4oX!@B>^nRr7$T7st%gRSQ#LrW*14M53)=rdtXs;GB33WJI3q#VQjXrsA**z zU*P8UDS#Htz-Azo2#m9L6`LFn3|--z*mbx%iTAb(y`~)hWQ$ac`#rp42sp6hz=Nt60}&~knM*Sr$wHN*h|lJhLuK}*d8XC6P~ynB z;h91PIt#luo9s-gfg4uB$&0rUk|30C7zLf9r#3%ao;$c$p?|~-il2<)^Y4qaqJLFJ z7t{tTLU{MsxlDmp3>VxPl)jMc1Hpm&{Y`p%aTwS7P?5yN#YL-SY3JeT3PLf3nC{QW zj=2kvoPxX#8%S+Jud5dGYOBLIb9%p}_U=TtL7 zcu%BTzA-FX=a~G~FsfRN~G&i?q%}rOP!evz%aM?FvB|7_&vUh?@_eq$( z@XNBjHf*^b{|U{~R>G91K=cy4Ble_o{NjgO%e6gXtHbUmS65D}l6)T;{%E>j$9M;k96Nl(*S!X&&+aC6coJzz_Cf4zpL~@M%0! z+vm3y@SvVDd%fG6X^mF#!($$usFmqx?RXsLx`L?n^wq+*9m%pSTeGR6&;S$~3|DsP zKXPO5Tzib*J5S!S+v$g3~dNRllySz2(q-O#ful{-GL<6*s9s zepA0@^+*7Jd9f=UC1y^mtxb~Vghi>1a@Y;z*@wA2$KYP4Qi(kIU?*7=en{$q3h8TcrbF$Z2Q zPO!-gz2JdQNphpDbZq%YI=`;p{sga^*yq$v6W#`Ns+WA3bMy?`s^xOBaw!>pc$V zH5oX6$?hh|-eP8yDnv1GVt!%amV)c@aL;&o@|^K1k1Fe51Euxs)+p%KBd8L+q)`tb zV_#_`xJ_1cewhiwo18dhhOI${MVN>ni)3@YV zir29L=I2fd4GY0ZsCZLg@?Tnlv`x!vcDrCybFcYp9U`aX|Z#C8yJXC<-3zSm}V;a|Kri11eXCatIo z@-;1tG&W?$5c~7vhC;ZZ+jQtIWoq-;F}?V!MeMjGnIUH`P(i z6gl2o2Sfy)zJZ=~9Vt^!*_mZ6`9Z905#%zz%1A(K59DKYa(3>&U}Bas7=Ses*G)kypu79xZ1OtR_1=iCzF%Mh*4w)TL4ZE4cT_h(JEVAG| z4iZXE&dr)9*HJc(c#RLes!o>mZvCnf>WE@J{uZU=;BcxK-BnM=8nyiV=_w$DcKk6) zqin=!D<*PMm|IRV;&S7(7bktX`zv>}>gHC`_XA~wtS&NufOXWN8ZV4;$2qFn7P_9q zYL?k;dcL@{P;8;2mpGScJ*t)cL}t`obkZa&eoRT8h+N zVCWpcaiAc2sC2faZ2rbw+?Y>O|4MVbd0Sc_HOu-Xc?I)aWo1&Mux;Wc?`5?MZm-%X ztDUy3N!8fk1+N(JfXB#nwKvIY;-*#izSpmOavDPlmZkokx6S_0u%WnORL0UZ+ts?p8s&S$BV)zt$o0qywMU+=_mK^uPl3N(F#$S@j1iQO`?Tb zPw|L*wZGP@tRs5ZeuPPQEySOUM-IzxcgT2DdCvE=c-^m~5Y?6V%54lMr{(=4+Xt0I_|FA>$0s!@r7@=&i0xG_GoNs|?LoZ) zkG{#BtqhORt*+iqt$5Ax=Zn5 zd((miujRP}kc9JaJkj*mrMMZh$hK&9*){%ia+^%&C=J%>fx^&gLQa!b4CTARLU?bH zo>m{6e!Pn!^!cZ@B3-1go>t+6sT)l;8FTOTTAAELL)4#2e)eKwKj&U0w=_OgV*P_{ ztCJd0<8d#y@LZWQM%AET1q`eRW0-F*HHI@QuD}d*w+h{~x-5hab=@}|t|WeP9L0gz z&+sR{j3Drecj;`ho=>x%9rBHGx(Q<=Nji@q6Uywmkv2K5_S9H~vq`NS!KSTATOKs#gE!k!Y`>u$uwFks$$Dv+LnP@xx)jr}fu&zg zmhn1)Pb;y&jzZ0EQYua2JJMgXZ?BCRs%cU=9(^Bg+%I^;BN1O_-(LcGJbQ59Kd5K z0n|2_%$>W|m6#6ig6aj1mf;G`@A6yE#X!i1G%|WMtCm(}-`J6AT)8~FC_SglFnf%! zS(Vn?)q~h9^ElDAT0LtX>DoWTsJc+5G#MHcf<%w=NX6`SWyASII-1lFY=fcUOypu+ox)5i} zb;e-tLmziEbq-&&RY1W>r{36a+&4{6E*Wr>-6@O4bt$uZCWk=zzPZ4O7rD$lwXPN< zdu;QTRuo6dyw3?98Ic3kcvjNXF0L)YclcrMHo$bH3vbms2j0qEk-$eiA~!;mUu)Xiu%h|c#n)GtIjN4=M0 z$cX2ZR1aAD*W#EUyTaOwL4OZuuX#1Wa3VM~?q~YTV^E=LM4b72`1OvcuQ}?MWHITl_|KP4spj>o zi(Nr&M2cbop~wk7LvTFCvmz%Q}R9@W=FSAqCrj(N%Mic~p8tC+eo z#)aUL-E?cqK#j3l*FKgtuXyV4^x>E1{nB#LRYIxW$=c8qO92Lcl^jrA6ndh$=>pu- zOW^x=h0XV3M&o98oo25HrCj@HbW@SH`hP-y$WKScqaVS-}d=DDsNE`$sKF5&3vS|r;J*guKymhLw( z7=(O*s(%b7d3u7)dF5>syM~@{N;`Z zq@&0S8s+{*r?CTCK!=56MhulSTU zUhpcL5!nyUeZSz#%AsgP;lAl|nE{aQXaY1DDRVJa%E?}$`P@q*9}_TC3$_Ibh>P8kq{L$|cZ{=K!Zv3jELA|7mcP@AChK>^o;>Arq<8S)ro?!rseP)5vU*494_FqMX z9OqeRH(MP&4#pGH({P7l#4Loak}q6*oZ2w3=4treOah-eUGCI<7Z|)`6O(JFxnm9s z{Z!hfxm#}sTdUWuITgSN+MZ$#BYU1xJ#`HRGwPXLhpTQ+j!8nAZTbH4!yjuk05Z$r zCFKKJA6oQ|s;y%nWdjM;*i{u^YI)SxPS&sJ%-g`LKQ}rKIK?E7^5uWD;0>hrp7iAW zfw(3InL8qJm-O0p!>;t5`8&QyF(e!Gf@S8>CX$|uz*2?N=t7TEf5%VGZR<=vUEL_b za`jU~+#@8{O33zr=S}APP_XWzciIqj^#N;J`F@wCkTCbY$d|1tGQOlED7lJiFwIe* zGhA-v1p1_X<^_V95&mo$g+BBXS@OL6hfJ4@*CK!JnwkzPd4zUIt2@_3#P@=MF-Y3@ zsq(Pu?YnCGhOepyynxiC=oy>UF8ulyS}xG{mAGKv=u&7!K9`U{l(4OQ#OHJXYmo2l zbB&cD0R9tKlhcrjr!=%d3Gz_a{XCF+j0pVhKDhSg2KDMlt1pB~b`C;F3AHKi6$}^8CjF5$uDIPeq zAP;sy;e5T1JbeV*rER{>8LYL(yk;Jf z3@$(ua=oq%QDFomP7*~b@X8*#TA$IVy5c-sHZ+=8;4vFcOo2{poCk1YQ#upyVP8vM zD^|F6#+A!7#%r#4Re@7v)YRrcMU+;Me7*~Xh|%keO{@}SFQ{9vLNCu^T<4xSuV zwE8AYBnv`8q)~dE3Q67jT`h}SUPn`&K*yCcrEGUE*0d=G#RDa~Q0e7&OG7fSK7#rD2SR-0`+z^6&oz&$1HV|((K0lggJZ#0$!>j|QuZn$Kxv+1Fx$2>@zmyI z+0XNHmA0wy>OpMyz>w`JGb3JN6*aXW<;L0%Exc3ccTAzOq(?1H@pQ1QCeR_qlN7Mipu$Q8|u&Z%H)0x@@HpZ zULTn&TOxE9{Fp@Y>yz5!GvY&sFSDLG4!tl5!1c9RcrUyq3o_nnl)+`(HZO)Z4dE)g zlcERH*eH*BJJB))dAt+JRtW#RsocPD$&oMXV!*-vv`O1r&O;#(2r~T0*9~aoT76+ESpLz?J^J?oU4+6Z+?loZO@2U z1I+*y@ve{6iYTcsSm+<6{7h5^@|(H=CKvz0RFrlOI6Z$6^?akryoJ|Xuo+k*&N+Kf zTEHI?l>9Qr`@*Bh9nqfGbJ|4{=usIeqc*sdIxuv``K2K!*{^u10*)QJ$1N>q2$6xC8bv6-SS__UL0>#{~MhfXB~XitjvD9%j^T*)%x|W zVjHKu5m>S^!=9z|l!NvAgvz{%K7kx(6Np9?375%%TzgIfV>+`lF_pEU7-{`}? zrhk>Jl)b<38KPz|Cu;Ba4z6?D=ggtmiybYiye6Ep-4+&kZioRhi=Sfn*>RsV^|Bsi zhA_}2|04(P);sr=#Pyv2lTL?tB?O{wnvp?alt)HIMhY`P%i52DNg9hB(4!3grzvIl zak?u_iZZYh{o~S}5gk=uM4~S2I{*z+$~6$rQVE@jDi!#Vyy@CHRN^RdH|K9`NO&LR zWQR=GZau6?g;Ol!H3vhH)YdDLrWqyfAC;K`jgBrZbbd9e2u42Oybm~0kSv}MlF!J! zB_4E&F{6+PA5#D~KRHM7ZsXd{SnQZJci(5c$023X0Pte?nsmc2A4a(yBK`suCz#1p z7lB0rMFAY>@!F>)3dm6F2Wc`2tk(L5E3dVl$(F0l-VMBW8BbE+9EAa;ih7th-eGr`Ab33L@YLp)H`pDRe?XEog3^UaAybQ^q zIsN0wKe-ztVzE(Ce&ov{TycOgyHRKq!{hpEy!_?HRvh4jJ_~rhWr*Q>AMO(i zWEAiY&B`AajiW1dkhxFSeYpJ`Cep{T=*IbPl62d4%cW-`Z=}r*FTB=k~CF-R7>$1(M*;AjrrR? zU&_c^Nx!`RFS7p$xCS0L)y0PVAg|n<(-fj!YNMIuG27wZ_ef3fEU^F1UdU{k1M~HB zfrjNGkHy#DUl+iYb}B?O(8!CkDK}~y5+b?(wcY-jiZRRK`*Yjq4>vhTX+eiHHi)A* zM%o}I{(HSx5DVW`IH6zu`K{ZxL06FLAxR2;RU?e|7$6;sAm0|kXZQK6_P9z_-cndXzdN#&T#{z3P&qO;tOjHbai#_x-InQ z3I$N{sklsbY&@wxQIw?VYZ8(QHMXnIHuGH?`A zVhvtv%WwO1f~;*Ea&vOEr_dG4(MwRF@gK=lWsxGXW- zfaQuW8tE%h1hZR9Uq)WO*X+b(A zr9q_|q`TqJf`D`gC=Jrx-Q7q?cXxNbi#wi2eeV1Be)wP4`9Pe#_u8xGoMVhR7rT7a zP?~gO$`qM6;Q%=?O$2vG^fiD8dQjTY#BJV-Wr5&d|UA>$6?Ou9? zGhOwOcI8?!!g6a=TQ{D0&2!q9y{*`{ax48B#2`(?){?Gf4{AJL+MIoR_$>t!)7zEa z7)k^zy2$Zj{q73O+i;iDah03P*>>HFBl`FC*Owg$eY_eVu&?ts#!$k{^}U>x?aW^zZ1}#N(e<#M&&tPbfLY7%%=%(3s~C^N!i zVzXZ92~EqCoQAwK-S5`Eb0U79r3IVNVd8k!<(pX;YMo^LJl(c6yD0as7ng4ug$h!#thtSvK9Sm+UKFblw97{IiDZd8=tzP~Lh1cF;@=?=34J zIZL-{?WN1z5?nwAh_~oO=!)8o5nir9e;L5^nr-Jml%wR0$SH)xyCt*!J;_siw93|4 zP1=6*rg^IJkd$NBq&lLW+o^h!p`_b0hUJ)JGAF#?tq6Vo-jORHR4*60zwR8&uJlHC zN9L-irqAn7Xsy}LD8(Z4oPEcl6CA?grTZtDhld(vNX@2So^$6NM;d1XD0Gk`g}|A5 zg)b7w)jf?(&&xYIn-b@ulw_+iUN((aDbKrgV+%}sSa>)dYCm0EsT2nSprVQZG&beZ zUyEwF)%gJiVX6#(c48paL^0lZ!%(SE8{}y1uzv%YiCAXSZevxQR~Br;Jj zfRKJZy6!va;fdt8h95lA;`-R(IAZ^x+@C(;PG!P^^SA$%7zxM4aKdBnAIGa^A)BiF z?uT`#7^WEMoi*_ALdgh?`i{i8j8WKEO`TWMHN8lyTI6(UcF^y3H7$uUKa1RVLgaEZ z0GX}2^-i9!-keh@b5=5lQR8>&Ao1~{vz<0Ar`zGN26}|$g{zZE{&Kg0WT7IxUfl9t zj_Iv|NNkvKDDOIi$VwUAIyv<_9JLJt?>vt zNY58pz$&~ue@FlI`k)&J^%mF(3j61qPvYcQxW=|hka>?1$5J!PXCVG|C2^p7+F_mv ze2`Ufg~^b@1bnJoM(!Ih4H7!FThpLfp#f zc!R=z#(g8?bazc4maJ@2(6FQ?e*ih#R#8V5{>Kcy4Wnm z*k|VYt1f9>UmRQg_%XVXQ@q;cRX$_kq~W$m+k41Sd0jS{qT3yr6zV1vhULVe+l5iN zUBB8f=AsdyA5XaRx(Ss&``pRtYEC}7gqq#qP z?L=V&^7Z#9Fic0tk_dLYC1IY^3b{+%Gb0Bl*wQKxrPf7xUH@y@t807fW#Ac8WdUA8Rb`AP}SzotXr*ViHHG-suah7EMavg&>RlQoZ-43$DWpj0k=C@_s zbzZkJWq+NhF>h%q**6?+DhRduGf27VfoH{<>L>J9b*`>cMUnD)=~4u`(^bc2HX5Rq zcN5ES%$rXax@t{yvr8_UI{c-c;T>Z+J84Y)dDi{@b*T=hl(1c0FNI~D!8t(pU3`z} zcqTl*?xgCa;h>;?j%%~+ME%VLo|)iIftJAW%ol{^slyxc?aA_}B#x|d$w?Ng5u*Oj z_A_zh)V3|TA4gxh3JZI49er11-})}FycoQ-a&WMaz@v(k!F;A%`*y2sV8r7TtT|zuw&20-JGZ7-BnT<6=vwc@ zq;hq~ZPR2_;K=H>egh{7|3mlLLe)AQ+pQw8gft-AscR|> zjnSMef6R*a{~X2pru2EFZcovDf-V@yb!RMY0D^rtcP!ElT~9Ws-0eW$3NOUp9Dcv|I`rwK)Wfes$Z z&os+BMMXuiE@zODZ>%}{pV8k9aLv{DAAx1TCJa=m%T=81Tp-b{AB}27hQv0=hLf6! zW+?1o+Y-FzQAMkX{zkE&TesQMrEaToHGao6{O05phj^)^av||nF(q~J(UIMO>spe} zStkWEv)sj|*OU2m0ZygA!XkjEXs)6O^^qOZRCzH0mtxnpUdkV@=~MZxad@XV#W^crSZtSLE9#8H5}zK zlcduxojLC?SyD_xUg!vSt4~*p`$>fisZ$m!)m_;GUNBSY0~0^e=;fHMo?b_T$AjfA zYP!)*t3g}nz3HUOWu?hr?v*F(e3Y#PomOD^q@Ww+j2v|@b&v&xfWV+ljcO=Mj=FMt zBxVZ!rk8b=Du%^0@12V%&{W(tR}H)1>olu*P))gwoMe_2T_^@z#nc+IP#}yp2h3w~V(}6xw5>BoXOt+)}#aAE=gaReFzsV2k zY18Zn%v|f_*8FrP!U2yc1Cfsx#4_SAX!8QqRFu;!)JJK7F@kPqA-wVDu&n%j!eFGb`UjVG#4-~_BsoXPOnyw-Wq@}TYM^mOMXJS9Fo`mF1u?UBoVD>ewz zG}u8Y8mp`Q_DZGnTgPU}gK7I&A4>hdC3IB8D8anS*;aIgMn9-d$!1VFs`}~riKSTYDiD#CkQ6n32fKs6fe{|& z_PDxoR;skpjlfk0z91E*l=dIKU*W%4Q_N`8`_r^drOMVS4P~z^9?Il~2{ z|Farp=K#aMg!WH0dB?_2U2`6+q7`CM@B+Ljov(%mVNpr{fKJIBsU}Qq%nw}rj+mZa zy3Ay}3tS9wHvFs0o@%-kOq|xb?6z}Q(4iX30?gOL0lww1m_gn4>FgjFAL?muZm_|N z!vOIo4W}(5!X-c1U*pBMjaIOGy0rAZaQD&yrjKHQLc9(jWz-%UpzU&Mos=9P#ploA z8D{jj%b^c<;@CGD_ef>`V?1r;8=htwfnpPvuI0Pu4ghOgpq01*55w`;xS;__5u$I| zJ>G|=bfuDG-WeHB7!#^vX5$gDJ8bi9I#QC;Qp=(`JM))MS%1#(kUglA_N;4S{nLul zZoNhJ13G+9>rWPlAYW8d0bwhvze#O+r|O-nl?TRf>dBxyn4-k|4N?a~Lrhbj+`b7T zrV9Ajp}{e*u|s{L{Bz2YXSmxY0}cD@kuuXFd6DVX#d!N=W&i*jc#Q$8u#ffHtG~&i zJoHK^Ec}VbA4!iOYa?#Ue5OX|_J<{0^xv3d1lpWx@*5NKKl0)-+})9ww4l{tYJ9=i z{O{!ED}7`Ur&wzB_Q^l|^Bxo?Ivm4Ip0;Mo&zTJWnc|5YFi<_ORN|&S|6_J0Un_Kl zSDCi@zg(;g1OBUA`TX&J?)TH;@$iFZfXe^Bu7y?qKU%E`t=?MW8~8783|2?(6vjWM z9D+6j*=?rw^MAe=q}x6PtP9pV6)lWE4bJ~O0ux;m&EL-v74IKk%Lwy3s>9!_|7R={ zeEapg`@(Y+8=*<+|2t0LF&QAq?SS#OYySKd`cr-POAiA$*B#tnCD?*8SWfuGh;VnLtvp97L7*Cl2-NlZ*nvwfh{lV+@8P zIhi&)Q%XS`8yU*hng($HICGu?ra+WgS-a;;=m7v*I(O^yL#+F8)V@vfFaIOC@r+95 zo5&o2&3Dc9ncC_*C-7kKEJ}B8?tyN8Z56iZx0j%FmRaTb(>rAI+-N20V9bpuR4R@` zs0EK3lH$B~L;eTedC(=!Xz)GJ_xi!bX?T=6gA+X!2ao7fJVVI1Ta6Q2d^j#NJyieL^7{-g=+nWvBpS4rTog&0h>1>)I~&*&7Hj9Y;kN8D=Bd)F{9FbBhLrMsgv`Ya4Dj@HQ% zHlML=)*Lp2s9=hw@N%oTy#9HXuAN<3PxP_mvz-7ObsPeY5@b`If68FGcVB~eTq9Go z8;~=fYzI?8@}e})O@ZPMP;2B5{7wT90WBO$pj?|gO}uGF{w1vmwXPe`xv0-5_uFDs z`8N7&Pu5&*jGCjAv0HKJ5Tl$=k?QI1`mVwCbaYs)Kk&evjKUppmH($Tr%fwE#<>Ty z&3>CT)eBwOB|3DRO(}ojXKM8M+;7q(zEUAz(O_(PalWA!jbqq1#UACj*(#cFJgkdo z700UXeizJj0Cx^DG6WnEaTr^s(RZUXTth!1U>jaE^Iw{@E~qFLG*oM-DJw@mUYngY zJtTUF&%j4Npti~z2>BZEb$y|1$Y~{4src!Jjo?3#vKcN=%tfHcMb;M%+brj-s2(gt zJM+pH9N)H{#O?p!*sBARtx&4MIKNsrIwh^k{BX5<_U1%keY(0l)@D!!=$=3Cb9iod zgzAd}LD2b}XRz+tcXV6EfB~z4gxigx+u4fr7QL^fI64V#S~d7qd#}viJl@>9ygtRG z>$}z8rJ2jgsAF?gtg5b7sj8N(LKQljgc6$&f_wPvS#t7+&o-|`?Zv?H8}>K-yF4o| zE*;>jyLS#ao$Y3wnC#7*cYA0KomOKxH7*~_7bxTtGTQGzJ%3yE8G}ku=E8Y)wA7!6 zmtM7G%-4^*%0A*NlYZy8^{Pz`f(v0g6gl5Ne?o)#ywLaju<6a@?OOR!b{@|ceQOV3 z&29lH8*&PE)6}~qj?x!LH_ANcX9Ct8p=3Tl6Ee|JdAQoUx@$Y&zlQs0GP%`%uxPpa z1I!D+&CaVDXzxr_9xO*qFMA?az5dFm8&L6ZKhL<_tdB0>$%{9GX`(t_4Iuw1hC#_| z9|?zXSgCE6wPRrPn+Sd8Y+{g&0xEL$;+uxUD8la0hDB7qCr$xR1|+{!r{lg!mwMhA z_7u#Js#Gt*Qn04n?E>sw13o_3h?cKa5Ut%LPHOgbCq; zo%!$Wzs)Aq4>EZw)G|*dQhRbge)DYdHQ9YM;-5Ya9MH*Y+9;<{wDvi^ell-Qv4oms zrgtq(j!SO`bhyip4aP$z!k%f7enMsPZkN;1Y4xM%45NtBX%DJ$v1ohX3JMo3^?WVH zt#Sf*aI$b<7J^Yq$WXd8Y`5)d83Sur1CuauoI0;a5TR4nx4w8<|GcXydC1WgnMg3t zW7&Mg!km}p6MQ_kMN#VY`f)Bf>bYSU)bg((Cl{}_cH>~696T>)@*iBht|xK}C*ZWN z=4O1G_V{1`c01LjU3v*{PiEa*ooyVY#$tW3FQ z?H}JYMl)w+tXf;1CvT3TO*r#s^`oZ7+nLUWH2Z+0z~{GB+g9s?t*MY7ejh$Y%9yR> zI2PGW3zzFwhC;t~|L=zj6%M)oL;MAnY}sZPym~~XFC$X@h2#7JOz;8$Rl}pgy}srJ z?48Gx$V5B~vNg>w?009F9XGeAN2;>@C&8WKkvwWIk)B8=uO6(zk9a_x6Hv?Zp3eQ4 z_)4!~Hjd^;XfMy{d@ONKBYJDRxZnEz4m>8*z&DZbw7|{oe)<)*RY|PqGfU3i_vl{fBB!t!6Y*Dn%Vt}y{avc50v76j7*(;1h zpz6C}NNLzT_>iAI4&*43o*MsoWB@da*%T?otO;$$ww;oKOJ6qoYM!CuRf=0$C#QM6 z(Ls({Eky2Msm~Ccs4(ls@WlIISxlT6k(%qMLv(AcYX9QFA z%ORBcv;D=ds%{#tHy(yYXR|uG2@r}2+RT-6r%EC8&M@Y1{2>M%2hDL>P(p05LRRg0 z@?uLApYf>KXL$7VE&zZA+RHaTEKSw)xJuj2aB501OiRchd-dTg&CF|GeI(WhRJ4nz zJjD9C!FyiuttYyc&uX=cPO12Pq++3VS9`)q$QufO(Z&zwOQW+lR(8#J3FM+^-Qu*k z;w*?!ZTv#kD>N2C_qSP%6h#hJI(1hJR`a+SjgABlXOt@I9M4nJo_aY>optv0(mzvJ zTDWvtVrwpREcW)Mt|^bA)zD=lLpH{W^NfUWHI>i???7x5uv4_Y9QE7uMnx;DyXB?^ z;4-~ZV529)VM>egghlEk^7Q7M~F0!>$-E2YzEztQP4#yDvid3Q92&0q5(A4e|n(s=EewffQJI&0$> z&5w_G(m%|ov0HNO{rExjvJv~2eTmqkojj4GiPGr)4ldEFS2J`r{WGL6y{SnVItCb#h$IV`Os!5E0yHLyL$xAnLA?-FTQcA>d+63tL z$FuGZ_`FS*f;A=}eH&x19^nO#{^do^Y=IK)a6V?vW5IYDCC0A&2e)CfZbU|cG}#ib zjx}kNOG5m_ntN{WqT6>ru4YHqCApc-I!dBZijuo!DKeWz=UrU$+dfZQ*2-k=$KCLL z!mRPJ-|Z>qL6L{~!TZh+$BpPjNA+qkqk(%Dpm-OqD+(t9R*!j5yK_q*x(MWSJ^yi< zXtEc)oFFw=EY5sE{BWfsQ^NjOl3DNQD68M?F)e;)A?^smXI77-Q!#{|#{EGZ1JQ2t zB~jdiUZ!~<-GfR~MHl(ux;Oe|h=0|^`O$i@?)t%PPVx#SNi%zqKJx-0O9LLAQc=pA ztaqRY<>19k*K?)%8HB2iHUjY#4Jq$ zhawab>ZmM=?Jw9Mu;t@gpP^Wa?UPBVhM$?a=>CeBS|!a(spsBeqXHkCs5jihdANml zFEQuPGSX)o1w`I|pF>??yW(eKtm&=Wz7AJZ58T5q;i{3*i=zwCJcTr~*spw&ews_P zFg9$+L=p7J4W;9|aAgsNuBTtcx28J9U|B#a+#_#7M2PL)uwOL`aTE7lvPZoHf`Xrr?4x`FFWkbX+2j?N&MRH zhuQVo3NzFa=^K%NcOawn8&Pbn#&e&#raZOkr>21&YkPw&EYc>6EuScMYcC^PWLRd` zry6vI8Oh|c)}k48tvS+FmVnz7?)%7WNWp5`h1Jy2XCHB(S!QiHKO6 zOj3a&xR4Qo{Z^sA&aU^m>&FToYrWm17n<)=ls9OUQU+Xm#QZ;GgP(N=8NVjO`tE_k zVl)hmn3_WZ7urfyruYIQ-o#F}jiYaxn(K;z&Pj1QZ`vsqim;|*js$jqsT$&|WXC7; z)a#NomPLF;;-G$n9%q+1NA;1G<#F_Jo9APMEBx;kvfXp*x~!KK+mmXulilOz#8=q( zRB}|t(GG}#! zrBV-H8b`8|n@$WLoTsa8`QTZ?s~golcI=>`XLoRFByLixuozg1-UK<2Z=;O4_qLjm zy#z{vRW0CPKH6h#)twMEB~wyvUbK6Z}wCf8-&(yiJJG+LM^=5S~X0!6F-NN;5!_u=T zaDMwR-@xmQD_w)}JWO<3nK>ot5kx)@91qbdE^nY{qZ^)1mkmKyMlh_vgT1i6zCc7* zhsfeI65A<5=`IL*lfOH=oD5p4+v$6B z_~Z4VEd4Zr76CU+Y&y1!52x{CbgCu8zS(fC!hM`HE7~|*Pv#B(#|f{3{t$0=XcBj~ z*~KH>_tm<5`5BWGyNQ&x`qSRTHYbJkND zW9?Z&OvSEj1#GUXkfEON`LQ0{zDLN7S#nezScq=5mWyfLCwWW=uZUOiYrdfzT+G96 zOJ-1DWw@^ra5c__`dk@A*`WG7VQI8D-EnF2&#s`ebC((a@ zduf7hT=3DW{TdAOjgm3LO!F;~#A^nNIWLXgwHF9F&|-b&d&2w}>It+MjjD%*5lxea z5I8JORp;dL5@EX;9qrKY6Tn7H87rYsgC6gSJz0UCfkaS6qA!`YuzRq-g3F5_>sN_k zy9ws?`C4eVGuHOi9=V;Eci#PG_UnyPw&x`ng`YqCQTGH41fuATDet4fwL=%+(sD(V zWx>Wz6R(lrW2r+t#sYAxd$rMP>$i=N1cHi*G4l@$=(v|uJhkS%v4_8975k}PGJnSK ze_p}lFl)O$)#sH>sxbwpc8>9I03Ly_Av+gRD%OUFE%89}3FtR@sSXJ@81 zUEK!ObGcEVVqIHJ4l8?ETk9hcbN;v;B3bF-!U(WBDUW znZp)XZ4CMBR;qlp`m=_2{;IAzZBmT8lpA8rxhl2KYU?-kCU}#$+5;@NtVdyU8vOBn z{C_NdXB=v4XiN+2=UF#t3q+ZJ&LP$Pov`LK=h}LO?z2u+$m>LJNa$uC+FpUtL0_B` zxwp5jfI+EAPlf21C@XSu?6klz)#eVS^~YA~x$)7$>&b&ddQW^uJm#J! zBtx%c&s-8X&8CEZF&SDy#A2Z0Yj)Oy!+r_jB2KvI1D^c0S#O+#W{3N7=rW}MR zf=E#7!<@ZD!HHnt_-R9>kt!Fqyf4wYhC=kWEFZHs6MHFXRc7>SkhIRI=P17T;Y8O) z=GZCo(Dvy_>oYoyx~<2&r9q`9)89j+Mo~1g19bakIw=@9IXB0kXeQ*2u%EFfx7ua>DH%#8=WEv zHmvG_dAb8_y)os4>^?bGLDutS`(edpFb96t7=)kq>PmW;x5g%d6dc^Ng_PjZU;eUF z!_h-GO%`i9%%i>L{qlewMVUq%iX0^qj_->V<}L#toK!IN%)G-Ae{l@w{qDNiAtzrJ zyZJVCB-;t@f~#q8yyj*ty=Aw`ZNu@=x=(VrvC57h?o>2dpiY!^3b2cLD1)3=uiQl;3?4? z&HjuNz38DjhI~}5#Ut|`YY^y||Gc{Hwmf#If%r;P>xRETn>pC~G-88k)0eKF-=qHW z&LE_PQ-j*@Sbe^$w#L#k@muiw^L?Gs?*d_$C~f%P@9`XeVE#ZREf zf4^||$9lL7Si-5j@Gje);oD`i4Nyh1_fRdW@kMr zYX6M&BL_QNXKAnxti!kXqIwcKgt|tu^Zham#P?C$u?gcn3{VrmjPd35!M_h5!gmk) z!^=?W|C#3Tm{86S{qUJ`rHf=q+h4Oi`t4T#{nghC`om85^=md7q))$g!cQGS3hR;h z>l zKYx1u&x6xM?O~wm$xAxw>; z6x$CIDJfAfFvxpjSR*2+fzTZk=2z7!Gf_&lnCC|WDkbZ!wE>;wnOdj&qv(UMpJ`Qu z&1UOpZ?DU#QnK}2ejDg{uLdRp{uojru}p>XJaW-kwpa<+>Q|s8@o>YrDk^$CU!Gd) z^@n^yZ|fSQ@P9w{z41-T>v{L;bJr-TH#H6e?!kw)4PmVaDW;S5&X|j?6!+%0uj%3_ z-0Ga~A3otR@eRP^RGuhv6j3gRv|cIlt2ab&uiTGcxri&D4U^9=aqdxeeDZ7m@(B{7 z(2{z8$NJ&3I^XQW({ws@epsY)iFAIxdcR1WTg_Q%XNr9}#vIb+PhWM?4-74uBc(gpZ%Z9T1YZUk<^?DfFNr^eT$9y83w))^YNRRk>>WJm4hLNWxH{aVor~kmhn=8$l}_QciKtQQDj|?A&gg zdZGm{xQ6d=xJv)|{BRZ4llQqB4-Ti^XD*H6H0k@u=O3u#^02lXT>qGjC~W!amGEff z4D*rM^7(fBl~&7sS7$nItNNRW2O%Uij6k+(b+q;Z2NVdEh-dsUaou4&RbiRx@BJfk zXfavHKQG?Zcqzu*sT=rmvAO_Nq6M1lI*jM4irhJL-Jn=asz!an9SqJ08Jy~{wKi!H}1L(LqAp%MGfl~=I|23 z!H%12*!Oe0ZXID1;&E~=w=U-6)%w9#2b+AWEK4WZib*v!$98zdxgw`|-qX>{n_pA5-*KAS-o@2RTm`O;!=ic@gD z1Rk8dR5W>J-?+4Q!t?i+a#c3Z9m~~JJlOnMwq|Va%0jgT;)*T68e*Xc`(MYDn}o)5 z*JHUkxa%Oft=KTZ#?US?C{AA*k|c0D=#yqL zm^+EhsC#h7y{FA-!6QS^DFea*I4BN)quxsnTO*j5DX4yK{j$|T9~s5vt_akIbd3mwtk5c_3TRxgG!R_Kxiyx*qAY!Nld>%*A#d$i3&w1Dk zXUq?lh=yw(?>&GIX&547c;6vYX)*udG5(o<01>M&&-Iax;XrcbQfK&Os1zo>x`gXR zVZKu0LNhXNK zUwdkYE&@iEI>U?%Eag|z0yvPSD4mWk#Ly_k-V3tsS(Ji_Ar^!`o(klCeQJ9UiSVbT z!}9a}MShsw2w1^mHIBvO!_}v(2XFkbCF^eq*}Hn$;UqIdNQJD5dDW*J!kF}t&Z`up zGv!OO@ZV{fH#U8wLgTsE){WyRjXQieKSVc^1nL!@;N(@?Z8O^T$Fa^jtCySAo^K%s zV|0d#x?XF;ul7GAI6PXveRPxOo*N;sE4s=d;C&RvVv0LNdBxwco8b2NGe+mz4kEXk z%-cdvy6wq&YJ#J7#-5?W{?X5Rk}+K+hC?tqP3PZ*tf2>gHeT^`_Z?<(mC768Y*Dso zid#6h4wvHeBbJSq$WW353=WN@lATU>IP@tL@>HMlV#+Tzdt%Y(`e9r!gpWE{%xO7q zSJ_&L*DB;`RMYLLcE)i=J+df_r530gEJ~G#`FJ6&^Mpodo-(f^<;62@?)M*cV zD;?;uGJm-orINvh{TAYKoy-1^kVHrLHWzvR1pq4>t7!ddF6u|ZXdJB!XOq5_6$=nk z-R!RtBlcIn56YDN7R~b=GX!MjjE&RH0^84}1_evmeF5`#EdXS~FkEJ1(Q_-^)W}Er z5mfRFGm2|Jz>!nS4=^{7hk=G&SB^|QV3DwkCj^Q4QYKG$S7goao< z4!hHK!45}S^{nE#vrk5{e%I(M+fMUX(A39BeG;DLJ>5^b z`QTLLiBeOt`YgqBpb1fq@vP+^ZbF6@a8N`dyo#Etz1%aGDri#YNmTPK0mal_rq1cY z@3J|+fJWwrYbIeHHAtMUHho-ufryLaVZZi$9vONKTldU2bR*P$;)qPE$?F5-NLSYb z*Beozcll4JtKE)$zZfFT(f{OCc`%S^e(b=aRsHU;n55+}^Qxx!8rD%8tX@%mgfV75VJZ2<(Rmn)gN2IXe1*E z*#W6P05Gp#rXUV;A4r^8;~&}CZVX#Ka-fyFClN{W^`*hs*tid7x58_nR_zfu%^3K$ z9HF4j(jM?+9?ru!ix9=3K;Rk}Y>z#Gdm0aWV(J^N5)~@SP%p;STce0x#+HZN0aJbZ| z1I^$RFYRTvKq(_o3rHP|9QkO~dI>iBo!DUo^X&E8K9nV>-dqjWIRr$fHQJqYa3<1= zQ|c*!TrT&U62xF$^MyFM_8z5T=BZ-=(Fn%W6!C;UUpo!gOavi`_zaRFOvF=QWM8vl ze%1s>jJ(J{apez~0D>Cpgy)I%X}6o_uCrCg)El;OqP67J)tHL89Q+%YB>li6wUmbq z7KC$nZaa16gcfEUC=3|DZ0#+6vKgho4>(O8^Kl074x6Lq7Jl~6%2jXWNm8C@b$8tU z0yjETLDM85krD`GI&(-)!K%P=U{I^F>Ga|!O@E3tV#(}PQH9G-NfJV`3i1(0KWvw^M63;b z%S^Y}b7_3^@a%rPk$LU8H|kDEI}-m4H0H79fDT)eo!X_<_64(bf`DQ^#oT(Ve393fsS1fqB`>?1JuD!Ts9SM{I9d};ydk!ClO=kaNq=Z_s=g7+mlJ<@zz*( zn(eA{tXDfhIuJdMEh^g5r_~`-GfB1G zZsacHbig8a_vMST&zh2L-(e+2Vbe8Xj+#4Ubz++cytsU!41E6VcRuos+o9m6rvz55 zmCC}Hdlu4-r9nlYGA*;?1TmiyFWkJ>DSl19s%h{e0~BTAG{xopNBtVqKGX5jPPVTC zdK#@_QpKbCyx5c-4`g;RG1Z9M&KVu1dxwUh6f=?yhn^N`H46`!+{>H*G-F0266e$a%8_%y&L#QsFx{1_&DB?!VjC3 zP)9t%pMqw4W_!+?LAyI-AyFpj14Ls+$MoRb@A7I@!$wYeZQ`jyLWFyr#M@W;ot5iS7v&7n{?iM2ZblSa221hK<7A|H#K12!~&}lO`lW|52bsF`0 zABe*_GA>gKv3=St#Tpu$pmd0-A z%IuAiTZ4-aIaKk!#6t>Xnx1Nj^ z{?g>6%%Tv(G=VWvadCO@R7QjGbDllnJ2N4V=H9LK1B}2jWi;!9EjiUgG zCNElnmon&3d%S&F3s0Gw!G3mgcCFBul`miY3~u+OR}3i^7XKHQ7Y|15fVjiFNv<#v zhTn)*Ag{7eryU+Ev_Tsob$F;kde@vHVFj5W#c`HKC8zS*!hCb&Sp1b+1I&k+EhXz5 z();!NIRc!DYp|6MwBSy?R!O;*^mtK@3kas>rl$%8oE(pTj0hiHs$t`Jv_HWjfNv@I zBDmVO8#CoS>vpSFaOH3_g8qh!ACd=)h--p{hr=*O8P0^7jlya^@=kBFt4@AK*sZ<8 z>i2lqldr^g%L5x1ZhpQ{_>h%E1kGxY#IQzpFWN8-`4zLVyuxmTtrS4i%z*K@5yLH(Kk?U2Qgq3j|fD8K#0PG#GWXq$!L7CpvZoV+ichUm{pQ> zPcC>zPrZPEuxFdo#tMS2tb6VFQFAP9yIN0Z?52uOwmnGA86q>eiTIq5gnRT5o91(V z-A3x-dZHnb*w=cdJ+rOxO=G9i^Y~v88)7I_-vjMKJs=CyFss^%?p~c|OI~}h(kvZZ zcUU^xi#kK?d~|OTYbgMa$2p_KbaaM>Tx;^UV^Zqko~No@su*XJ&lXGw{&jw%n^?23 zWL)-lZMR(I{_N9E`zj+%-t)KI#x(wTtU-6j=_-ijnDCFx35_NR=)0h}DFoqjQ)w8y z#>xqS1RlxnMgSKWcQ*!P6ugc)<&AusPMFu>iwTQO8=GDkAK4vaUizYa9~)lK4r={n z{uCcTN(>QH@t(d?GT^V3{a?A(q<*-86 zo=^>`HEpgvKH27Md_#~()Um9W8?pi6lFx+UOp$h&zzXByH_B^li48U!{Nh;DGLYWu zw;lcy;N`{b@zU_k`iBJAraCPZ|M_{P?;~+==Pdk>9Fim_#59Aaq<0P!=98W&6)iPF zv*SPL=crB{fjy^IpzB`lZd6DV#Gz4tVYN?x{|Saw|W=s*`FT`9B;ulP#Alp?JdIo_|0(RvV6XrHfsg#V)GO_r)dJO6!wuO zYaG2_nhw6+WJ|4#$4Ywk$3Ds*$M?+}QNn<}wOU4`_uv4c`H_*wF794x^{&RBafGY)NI4 z1rHA0?*o8jtApib5P*h>W6>H&p8yFYa(b;!`R`A6hps_QgpFtANGd^2Dpq6<19t9j zO!c=P>foTKqb}>ZEO>uKzkV8?kcPLUC@uFU(govtf!XH5gi`eK{+~PcKf2HJKq%-d zk{^Ea+_OL4k{7DnY&z^qRmkI?P0t?)Bq^r{(3ZjfFYuZN1lea~!oMM3#2)${_x|_G z04F1Tq1~21#_q;ZjMY9Z^Vn7rFQvK=1hg)qVF$u-evGA-P-@1{#Lo>XI)@@D}|q zP2$Rio{P2E9HFg*Y)8k+g_-!uT z{feRoJ>2U>=%6JWgZMjX@Si!w=MMdZ5{sAt>o-#5zo4~80Xd4`ZFNCQZ`p6}``=%Q z8K7PP%$h;-cS7T*sjemJPEw*J8e{r@wuK+>|EDc{$TbBi|5sD^pn5#8|KD$du?Gd+ zw(t+#`sXX5M1pO>A1!jbkoKpT^tasuup?_!C8rOb_5Dt#{Cr=~G5(5P6sBbW?*FO3 ze*XDBnhY}6zY}O7d=3<#U-medwy=MhMn4l7&+g~(cMtsw=>UeZ=>?#5<{hlYnE&;K z65ZRa|DSFkbD#dv^Pd~_+s|Hap^h35($SmE{&kYv`(u|R2ZY4G^c6XZ6g>0`cb3?` z$JPJdqheq(av}bA2LJY2#Q5kAPoZlLmb*nW-%!L20p}Zt(JG7D`}3CLV9vdN-Qa$@ zxakXD34_WaV=d4a;hW zlRqYIU7a6JyV}w2=J@_jLjSjk!~yVvukZJ3ztr1O9Itf>K9&t5m!gr&mXE08b*9f% zzLo|hqm57E?pO=8sx6w;P07S%mmlrd|JqY~70{$5?&X?|q~JVB%YwpNCP%-Xc;;rW z4rQpB$=wn}`Li-JNxkA&5+=&gTtET~A)?cDeRd zI6ZM#{$_BUhRMz4Cr^lh@!oZ+yfODb%^;B^9$an^2iUn*iS;TSWi}vMIw*cT z1C7t4)Qw04d~PU&9$|-R3BY#yEsHPr^SXwELIBX&RBzJ)7}xq|b$_@~FYVU*hyx?? z{%EZ})iAuRv1=c|$p6{e?tzMUb9Gr(tmZSHC(F&dYn|)^U;CJ%?DqJFj_%D&tT+Vs z2NA(JjqsG1)ViHr6y$u9a&nwLNX)&QxZ(B)@vUR)O62*^s^c@bTZF+H{U7zJN#5AU zO_TyktER6ij6(XUT=u(Az=U+?j2FjEGU@d^!=_iYa6LJ0D|PO|c%IT=eV%Fzc&MEz zM^I{A%J6%l#}(t8ex^cw(mAq3lNbE^r4CdyCgI08%tj^TgBCNht^~xu(#X*_Qq)B+ zx&E>s_g>F|O7p^Oo~L*=Y(745i?{nbwcGgdc1cQv3)9op_7ut`a0J7Pq3rg5^#z{; zDN66Ysj|aH*sTjFX;p$juRiQ}uZ8hSM0>N$^uz6vS4wLD&fB{46HcWvlgiAK;dQ3L z%D1o}wX56&6hcy7YkKktogyrfR;4q#bW`FhFtB$JIBwXqrWzR5Btc@MVd77i)Mcu* zkRLh-;H0JqP&c0kt!j7w-VOsas4PasYCiQ@GKSTMJyZ}>kdUpZXbHd(=!@f}2b>GS zArIH~LxIbh-Y|+{NQ(Yv7zC^;0;4eUPpKwf85yrmiumhU!l+Gf2gI;g*$IvLPdF+)BnC17!lJnFWb`gwBd`)eWB`;*KC6 zTGk|Q)2S7`p4g17JsR|XQ!ISUuq)tTuqWEDqM_r?HkxWp8Qe8g!A5-t{a&{sph&;lRF; z53WAgfh=8Pobg4);f`CYgAl)H3G(&d@ z64KoQ(wzg+NFy_#D@8Gj>?Kh>C z#F7Uf&@5H9rBLrnI8k9k`^7i91~(w~0m!&oiYbAUm*R|5J+oHY8qe#M`}M)hY_+rJ zWL5UrWgQ^~h6s2{u=R*a@PG;x>U&9Zvw+M_cS%P=TBq{wVU}ciw(cBkFSt{-8-#ag_rSUnZ%M zZlw3){zN#}+Hg(*cvniOPYI}FcqFN4+0%rTld4@&pyK!(eP}#qAME1JWJZrDFTcq>76?{(y z-h0ma;dxv(i{xW&ecdMKS=GMZ1?*;>-NoO%>VA+E{J8VFRE1*EXXn{syJt{*!r+@{kj=4Sk<0#;Ym= zgR4!b0i(m(^ON4a3L#VtaiX`84loHzI74aT2D?*x?ESccEQ5>zQc<4Z&a|$Qp{MNwrfQwlv9lQ6sgl6&K z9>BMf&pNt!w>w!}pwqye|2dXE)%qa)ApjypQi%4}ntgTJDxD3uAiRg7c=aAIx3|O4 z2MsA_3KY8(J`O4=pLE816Y?;@cAjdzPnTA@R%vlA;6*JkQoo?fUKZEEAw)}ic2qOa zN=ffC8XKz_o5g0u8=lE@f*Fej(_i0y-5j~`!CsA&0jD+RqNf+59Lg}I5J7qa8TM5` z&LIR*gy2H(4vD!oyZ+%*=XReXX501C{o*y55P6^uIoz{D9|8JISpOdxa={7ef5 z+>b!#?ZXde!D#*crI7|e?ftfX+x;~F+pFoFZ2X!^J0fsLer*`O@<%av-HwND3CTRF z2DR^*@a}n?@iC7`UH3v4r2M@04)_@pw$1}30{6g2`k`c$BeozI{v`x?uJfVvGm#+B z8sQq_Rq&y_Y_V(u^weA0cF;pn#4H&CP;BkqLO`9ot3A8`kz;2O<|!&}_r+y&XDP3+ z_0rhsR}z1P%~G`t;OJuf?1$I!RiPtbn>c~Bm)z#<`w?x|7U}!xJvqNmE|HNQf-Lu2 zUsIGrdIRE_OQxukMz#4MJ;}hQ7Z(tqt?#hK(vypO1esU*GlPOJOa|6^6!I-9KpQ9b zV{tEN(ixKTO0PG8AwlgX@poauXoe_#K_YZk?#q5A_nO6Vb!Py+(qx z5Yf8;tn@l!D{?Ub(i#De(?+jz8E)ct5hBjdd?!_p$hFL%hmc+;yVcwR4`mwAK;DDT zzC|TasQ5}AS7V~|($Q_X*Qq=|g8VG^j)iSsoy9;qMK2bRH6{T1k!0kf3gOAs{jtFH zOR4`<5QM21ud(#I>;B-{7Fj+gD=-uH)nFJe!J-zx5W+&5duSv-0>Voe=ImtUO9Ldb zmX=enj?nJ=@St9<``ULpv&}rkxqR-|^BEq4ql&8mK!YT!r2POV@%?s79mMPEq}ue? z2Ur*BfO&sRN0^}KsS%lxjW}}V(_^OhVR~$H?dCMsOWqZMA@bf<(z$>MVAnonuDWs2+B~Sl#kM;-MA-`QPVm5tJo9NCEyU(MS#qA-uci>JHq_^iAbdHv&)h% zfjk91fp2@$2(LN;rMJ=fn`{p~#QElMTpxIQ1?{G2DT{Vtl_os&r2cv=x<`oi(N8se z^u95=z|A#4Jq)xuwc3pWalyzTO%+y+DXvfD>9pbAP`gDlR2%Yl#nA;@IwPMgeW3wx zP$eHje((Bjr9ySbW9ZW zTv|_XYWwB4lu-lU9Z>Ij)EST=_5SJ8-8SdrZ?A2qrDo3)qCa9|Qar1|7<*zCGOvc}c${q#IuPWh))gH1m!3cL@koU0fWA8@gB)+m5 zu2#vYI-nDVH6KENUDt(sP=sKBJfPi@3OhFi=w0#18-o7h^}c7UjtF!3B#8!^0Zd05 z@^;K(kalAw^-GQv+x~qLA>cX6cHie}Sl0yD6nx1Itqp=zyS4g|Va`JagFTnZ_hd>t z`7pja?ibG6hMT0LRCXh_1TcK*Z0`0;U6~bIz{#88%HqV7;nADcP{w!hV<{CA&$bAU za7j3550-4Xfrk2|NIK)0{@D1W*`gEe3gBum;DsaTbBj?kddHr?m5P@0)SP|Q#V4>YR?t}11vi%xHf?)6yMhODy%n(@%g+3@isg?ZHS#5ay&%N{W4c&P=kC_2 zQt%c4jISkb%GDFxzZTH2YIpW)sA* zkN3O6^TnYxwA^y}6UEC)?K-wyQU0aK0-L5U=-jj-8>8yc9l&k*?#}Bfl9?dt!W<3` z#J(r&vEQim8bd0$ZnvT3*l;`+XdY0K!at-Jxco%0W!3M5yQrR-N~H$gi4&-d^ZKX% z_T(7=tea+b6vI5vg^y}^GgCCO=#)Cu1}PDM?P2Ow-%HH`D!eQSIRUTBO8ML9D-8Ch z(n<^*4?IIBhyVL3CFHW*E}c<(KdYm z+6RsJ0>Z?N^efLzPnl?aZ%T{US z%91>Yhhule(yj{r;E)JjAGCU_Rpk{vS*|{~ld!$hDhP(h98(0!MeX@+^pC%ihDa`^ zaBso;t$sJg-rn70y-f~?3G-CLJF9=QU2TTELG7P5*VSKn^teR4?EFH?$wnx?`f`5+ z9TJ5UL=o&Iv1r&00wb0pV+|-~xy8?pxVhUWJBPmVj*?BKT{jqD$!U-ra*VF;pziis zx#*PI0L~G56~TiFQMX&~<5SlP83-YkFF-t9mqgC(fhb1JZ7$60T_e_X8K+e#=HYPw z$7|gsv@Coo&!-ObS_c`iq7Qz zswk{kOl~4U9y%tgEJE<2DdpeIDXDacphr3Q4iw5Ns{n6n5>y*eQrK+}fC8`6RRZmj zEpY)#Thyv=027!6U|M58mguLEqE|>_GiwELNZJA~-QOjj2v~KCVsHxdek}300uR1+ zqN7TD`JUr@QE>LBq@CMKNCk#FS`7a0AqgD{tV-zuOi!RsP7u)DDeH8`W17x41M$U? z{7-2(h$zg-rB3!E_Did&trJpmR^8wd{{5Jen5$)h4+vfwne4c9_yKQ4%Ht8Xc7RR5 znrrku@nAwEi}1*>pP>tM`556jUIv< zgOla}(!$X4eeB@G)rFq~L-PVF2_f*$;6?ANO;`XHEIf`$9(8yBJyOFPEF&fx1R{9G zCq0@_RBdzRc=ncJW#j0e^~~b#z}DR}`grBmPt$Dn$5e?ZE*Lu@r0+3?BAEHgMA@;8 zQ5B8S$uc-S4+3C@80$N>O8ZEKSo#8JlVX+_p|Klhd?;!?^zA#ne&pLLc^gb z279|w+I4i(IjwUS12iEHSiYu#Dqmu*jeEXmFTPyeC2Z$(a?8-V5rNDh>Q^(@ngwEz z0&ZuXCI_Qe?b@)BSLtqh(-r*C_?D)sa!Ggth6wbo%ZY?;8Xkt|euipC^iIq}NWFvI z*zUMmucqfy(T&=tyt672HA!2uyY>!(Lf|@dwS>?mpFQ7H^xR|)?bd}S1}%1Z3fN_g zKi$t$6*4)BL#AQhCMdq^=G#j)6u@cNlkJ6rR zbcn+lEY>NE+Mh&aTMQ)s0)rn;W0^khEd-hYe>6Ga(-6SnrhayOLssnbhO-V0pYDUPbggWLbw1mX!Tnt1u=>ELuIQj8n`?4*nqjr8pZ2>z}l0)F$nwm0q* z!Z2gfUZ}8X(h@{@)lbV->s3(48im!eIY=r@aCXK7FxgLz+Iil;1ef(hKO0R69k6k> zq?}jE6e`Zq>kHg8<1vHWq6>pZz3yZ_A#*pgZ@wMQdA#5He9!5D5uNhk!k>VP38w3N zMEB_ZD;x}sux?j!o}t?S4@-*ZK6QfD`BF&CdzyM&O%{){KUgHQT$F z55go81x4Z>|FXQfIyPkPRsnzC97#|h-U0FHyll^W7$De+V6$r62?~!(@1eDiq&aH< zlCU*p27S}#k9(n4uGd4B@|Jttj>MfU=dEA96XrN@ZVV@Vav~9GH3aG=YmqOdZ@q@Y z)km_LhJpT{`oa)?Cm$+hUz6(RzP(eM7S@Ua=B*#*A!GklNLAPRs9jsK4w5RbiD@yo zSFCif3%Oy0#(zXZW75vt$Ql+0j~#B)yb(ejz(@~;`uOBu58So+iML~FYni+cM zxkgY$1+C@J%si|UKoNeAtVBr+kBf(XesJ(muR-L5%0ON`SY(8>MfZbhh7Yn(|i0G46;_=ZlH?awtrf$6aDFDD&7kq4ssKD%AlNNMyS35 z3GxEr@p6a}3fr&v6%(x1V5|4+22=(B8dm}ZIJ)I@ZTK(lq@}HJ6WkdeIxL!Nl^-AF zEf;?z5Ru0=*a4*#6M;HRiF#DIgDe!lf$mlIcCaUUO~lIv&mf^DIV!EL*MZ&ql?|Ql{NdfvY)d6wgw`bfc}z zVq4GJBe?O~&y=KOKNn^oCkxcC9Xg*EYZp|@I}-Tcze0XjN-@=$0UjAY@5XZ8^dcVH z=8Ph*y+Myh#I>(i6Z_1fC}^pGvsaN!(29=n-4p&L$$tLNo7ks(k2nS}j!I6N9XA14 zZ6x%0w@%P@6NTfA_u?M=`au(D*@s67?00R_w&nYMD_*f+DZZZ|Bv>?NZ^$8|6rAq? zc3e9$Vipy5uvH%yv?|4m2fqsQLf!Ic{YC7~)!!?6woWZ%8bj2&HRKJp)pofQTBcpe zUIbDy(vc{NW z=1gu|762<7<3y{*3%1>ZyzfMCq<|eI%nO{*fV z@X9)-39E6MEHMjGbq>271^C89eya)L+$OZnCRGHH?m$0ut2q!xcNhoP29ifxjss44 z--7;qo43?iL1@$X%aXn^zPsEF8T64o6b`d~u}5+QbO<<+KHCN227{kO#dt5qfWEKb zfJ9`8iiXebMcwVp#VWzcrXYp@dtqjt(qu%;&Ep=t!Hr+2fsn5{rxqnXXjQ5Zz6N>L zuY3-*2TkJJ#Qy`^({A95-RH(gSxcL8uzM^XYDirA1t1|{;Lt{r>XS$CS|l&7kObLl zf_E^DHR(>AyyXn4!8w8t-IH^z1Wd?Dj7#}eL=jP3G%vkoRPc2cO-ZTkp$|^vB)9#U zF@t)!+xD%#(5;F;UhDEaXcU10Ecw0kNDk&n^mwmL2l5lOh*2ActK(~3%lpK* zoJwdmRBzAI^xwYds0Xf43Xoju1>d7cvqUbiKc>uzdZ2qplVyg2z|yOYzMHK*{bs{X zW1jXK-i@o0Oaq-3NShQK!H2Rq)7_ayvXkVdzO&G~yvNStdo0605oO)84R}ZC{4C`vkeQ0K z1$PT=OM5kN)G$iMrt-u(X(Z#JHMYwe=Y)@+5TSzCS9M%P{!@Xg#jZfR`3jyFok7!3 z?h-O(0V)OFqp^Ec9(7jcIZy3>3WD5ygnDliQY<3LSE~gGIJiwfCZ|2il**tB?OO7D z?~XGn#+8b(gOV|^eNjiQ1|Jeul9)K9sfN%fhwoznm2QopHdI3;xU%olHu&o4@P=!4?iR z$UJVd=m-%D8nktl9Kgd;nm$HJG&8E`;uH8~&?_n08pjqbq!bJ5$+S_JpAHF={H0s9ODC!JsFIlC*bwHP*qR~b2M&d=#Q%xsW9^bRzFHs8G zketDuTp1?gF@IRNy*Anitv4LWE(AJh@2ecHLVwegFX%@KnDbnZ&! zSZ?IAlCW8!<%Mo$Hx=C^O-)#*J3KD{uZ-3;>c@FN^gE4i$LD=UC@}>oWs%l8uwIIu zDqS{rvwcS-p57+gDtpThmV{mZ&X%130e2b zi)I0F*LQ6c1t3?Xr z64T5ORg)J2`u38^A^k@R1+kRU;e{pNBP$U2 zXYP9y+>(9s>5Zra|dn$AK7!? z^nbS(_Iv^3g@<>+Ve5lrrv&FKGRvY*b}J8ZDlHP6S}E#=sdn0KyF8E_6(n!NADdcj z{Es-me~f_;BMJPkbt-X5XDBKXoJA9Cu1rYU+x{j;KG`KsBL8{z#aD|RR3%*6Ym&;K zv&-bs8nr&v(ZN~x|dw5jXL?%RBw{}y)NGWgvpx}OMSCs92d0aF#v#>m3LPg^~6SvNcW1x!6B zBt&z=;YI)97yJ)$0Dx+S-0QSF{sYJTi$w&k$9}QCU&Fgh#rcmf^S@BN1O|fy8h3!= z(-y!h;J@-Z;5J0reit^w{IdVQpCIhO2Ao6O6N<6YBY}(nP%2aKLCGi zCF+kAEfGoL9%YdEQ235j23Pc={~X>(r#JWd!`_wE?~{V>CtvOid4Kj>;jHx~nSb3& zTQwX0Cja9j8>;_ijLP$mDpCgV7L7NS(dq547%^LkJ}~?3>)wMh@th_CcI`wibrDVC z0tM1IktU}H^Q1IllNC1s&lM6UoqP1!2NPLykRz-0CCab9*IIs1I5rMmoZhp{Yq>KU zO_X6%XW83{A)G%P)nr#^A>Q?_T2|1-B4gL!a02oP4zHUeA=)E?4WsSWO=Fjl?W*ow zt1c@-8tE5Jj^=KQx6)Za;mQH{GWz5S-HJYizGH>JUW4X-rV|I_Zd_cbB>sDX{8g9< zO*wg=?Q&NAHDlOV7$cd&+#~}@v61Azg*^kOZUJyZ2B5L*ERbd{e=Po}1ZX!oC+;#h z)e1+y0d;sV>`+W~IrlC8x9)J_6d^$VM~Cyc+j9d+%)7F_Qp6Uuw)&E=s|hofH-FzU zQaJyb=CkM@_rNt7JV2dKkar*GJ+lL=61Vn;3U?NZDtdT1!Oe>cWE3-D$ii$~WU*;@ zF1jj*RG!GM z^|@pUs7k@o=V8lrZDW_|fYsA4^3G1p&!;^4t>DM=UGAvH10%bW4M*+}al4Gns4g<5 z*=qAdnTVvc_6lLwgY%E^^Z(hELpai&mj)XzKxz1Y6cZB3!~&k@>9-9YWgdO#i~On=3tf;LqN zS4F}fGsP4PqT)`>6n>pi@(%^N{D^0LpP`Ug<{U+1)RMwu_F_q3XQCjMm|6K(=`7yH zNdkL-H~H_vKkf|jOq@NS^1MV`aL74coi~E1T&a7*jRaEhk3KWeGSK4{mYJn+-SPe-{Pce4OC> zwzKyuZmzdA*ztIck;mN4w(k>nB-;Ry$XJrP^)&f-N5D7jXWs^rSj~FB*aqW?F>6(u z#vc54?(^G;|n03D4bjPqk*7Z9}gfD(Cg$ z?-ey@+Qi;mFapiH?J9s|oj?D3%a>6m{1^oY#@BskK`azD{ z^wnasnUj-`a_Vgbj=Q+;YQTxx6?OrWJNOh%EwilrMzf3%)Da-Q!KRD(#7p z6g;XFyhWUNr_DnN>@D*?d52(%fW>@Oy>&gH_T|%R@QJgmPS3=fPHxq6D@VS$L;*EC zhQ1{J-qiGQ#iVDSt=f>9XlCOYZO}5bNaNQ~58xk$pOogoed4T)aHh$-h1^^`ugUaH z##{L0(6#{aRNgl^bAHx=bXaZ+G^g4*Yh}s1*pl8oaD59SI7^@KtXzc4ygmCt!U(v5 zIm%U7f|Z{M`*c>i7rUFJ@R`&%iLWLn>AWUNmzCU|vTRS<(1_+rAGAN$t+C64ctpdR zAcR{ah-fj7=0ugH3EuTlfrEo94+10TB4lR5yQkTl&u)qS3b7RnK3-34XO_VuJH2%K zxrDWlckBiS{B*XouRv4UFuPl7+u{YiQA}kh_rUtmV6p=$S%@%Izsk!JagpuN)qGu` zF6H0%dGAx|AYgBjzNzxm7TnUX-nJt2RyV1<^l(pf-UkJ^*48Y|yX|^gw45uB^5W~Y z|M2C9H(d6Qn~ZoyiM#0dM)R1x=a%$Pmam|hYXor`G!5KR!<|tfu&S2&)f}pR(P6=t zUT^-oaS2y(q?fwoiF~XC?g0qN7`orO50+y0&~*bg+@I! zp}}VVA$Py4>&=1y!JAh2v%?uS$Bwnn+hnaqkBYWmn$5jmr?zkvxg>cbgC)XoxY)2f z84)^6AB>j_nF$rqA`~|t)uXLB#WGgCax6H_acX`@&iAfsVT}?E8-4HzNsJ*elOhiR zKb*ESn?Pvq6z%jvJaho(!1E=(WDw>D{s9xJCP&k_tBAz@p;<(MW|h=PW%ZZMcY{PE z-FHt#I>SEn6Ja1GU5$pB!CU)UdZ%zRrzO-vS!m=1S*>qO|iLP5qyShiZpa5>mo_Cbz>qxO!gD=eney?G_(R z`+zDiKgM^v2UcWOa9-h(o`8@^c5k`pr2XM>sTQ zozwXVlyF4$DNW-sVFtSeAA&r`<&WE$6m3vo(Ri-MKK~<$c|v=OQ7sn62r~65(VXHw zF(wc+gY|ZWtkckG4`Mu#XuBoUZhxFR0_&=jQa)>J0r{~%q-P6iaquXY`WU+X(1|~5 zX~ZNRiznT4o#{Pb;5V4gnwQ5lf9u{HZKL5pj865iQ>--~3!glvbIi_PHojza>Fdq> zXOa&DMg3XaATB4p9No?!A#M*b&5#=`Dbs_C$B~#Y>NY=e9-r!~$31LE2O>RJPsn2Mh{MZilQkOYIKlocWq zcjKl#yw=MVB!*6%N5*7{+Jwl;3WAj+WQEn7y!GU;81{v+d$mJU_MD@)1d|~51^B4L zq%=Jeg!{x zNjidJ82oefDX?W)L+$;fulc6}@7LSjaIMFJHM@2Ic?cKmBa8nhBiaz5>S7p1t017o zViR!&TC!o{D|N=36iweMdYP&7NuNwmKKYvzNfCzlbFd zejOOH6Yv#1qco9PHWaAfu{IbLhj-RMR}Y(=&V&XFjH$cd=u#4rxE;)~kBTPoYx*us z)oAp1DJEnnTuATuV@A}0o(Z@O=Hzh4M*ylR> zW#DMU1H6_bvW-*se$WeUuzN zt>7&m^wD*R-QKEXC-6z9y+KI-S5&B`uI|xtW5_}s^|t8n>wbfO)qJr5fbo;u%db=z zKq0+O=m({iVm;2Xf7)t!2*dzCNu|YJfL6W`X*NT`7$IhGJnx2!V{ z7Vu9Z=_bJzH&Z7he{Gr{$GJOGuo?x=?(AY%Vn`?Hq{L{*pa{4S2x9)p9`ZqCnC02c+=*Gjy(xqbPf7Ix8`mrFb6 z!VB_tfDpiRt=@7P$_Nj8wz%+PzH@${; z?=}NB-;T;!T-I(I0&R9{a+K6cF04Op5RCCm&Evac`m53ndx$}ER~Pv(Z}sUPnZ*hY z_KGBGzsZS0I}I9UjU5q8_OuO6R_R442X6EWaJxjjRIarzr`eZ{-gyF)YA6i4& zu66FQQWrp2aKpw#Pw-4+|AVDms_>hv*g#g9YSUEIVdSr-;9DPXn2Uw~q%Aeb;idbe zvx(B#xzuG!Lw)STBN~6Wc2}3%$FuG?$tjDpGpj~FViJQ5vHTwryzb`@qtcExfuB@8 zj(Jmr4g%}`gx`y4JXmPCq|^J zyk65v^m-KD=K-?IDi26Fa$lZy7 z`a`54%IRXamamq}enbG1^F$ zKLIy9CGw4<+n;a8sCq{Hd2zbxa~E*w>&<5#T55Rz5#j%l0k&cU_=-}v_x+0f!+!TO zz=$Enk>4j$3d8#1Yat{ePFUVxv~F(sBYZG$G7pd76zkFTQTY81^oXa=e}__l*KT=S zLi+B0G8kWdnffhFM*j1D7cV}YglvWqtS| z{c^p^=&Vm|UiRa2cS*=9p>Z%DUG+@Yh6?xf7ke2Iuk{YB2jyTx|4M{F2$hW706|N( zOEUZ=iB4?XBc#NmYPThuGS9r^^gtFYME$60=dow#7Pl0*xbCUHch(ur8r4ZF1ef`2bS7{M!{cX(i>^NTTm~fRJty5Mh(m4p3j!DT%6ee~=l zU4EIqo!t*V_^Ld(w!xoJFz7?xqoA2ibWu0pzAoVndMbzpfYZfN2=DdrZ-P!Cs2Vpi z`w!{|_xrLk4$OAAETv^6B_ud1Riq>z4tU_(yo{;r>Au&a@$l!N)I*7aZj6T?EAJJU z7^N*{T!>&Dy`ZdVsp-i`S-$dhwb&W~(l!yIP;b=5YLJ$&W7FBVBA+woJN$A*erwh1 zLNoL060Uc?tL_%F_p+9Jo5{l#1-ixamOYUm<-qX8q2@T(FRA1%B?Lp)%ehg6Q+21f zOfJ9AxLr((m>|9`1EB1m)k}jtQ!R#99X`{=)f94^Pm`g3C;h91H|W{^17%^?J6oi4 zy%HBy54v1TLNRLebIL6WQF5v;Ft3HzHYUz@UU!L^XH+pL(kjaiaq;bCk-Qh`bRCmN z?t5qF8M}1n$2kvF-tl`J7daioTcYrT(?#Y;_2L}+v(e6JP(2F-O1MDa_6YkfuZmVr zT}>p6KAa?RZsSWcXRe z{hG^VVOj5PF6IT9V-gS$RLPw!^$fs2@~Ow+dON>!B_yREI3-hHIN&-$aC#v;R_D@f zsp>nCeggh0brIXZzsTrKs0SIB_GcZ<$#&NxyJjUxmPa}HM|fG3m)gMyM+`KK&NmXW zl{<&FIWf+Y0p$?27dy*bNT`o@7>n)ijO$Z_p=BOQm2IxXyNT&9>~AXa@)~iK%+zk7S4(WO5}lkp=D3^{2fa9Q$-_qJ>7N-{ z&+HWTn<$G}^GlD7yD7cMZ#=@)SL0V13!SzoY)=LWeeCd(@t&@?gxnUYk|b0ZE=CLx zZzo6fj=KumI-TsuF)C8TbJ@;`E>7rdryCw+NL)y`&leLMG!I%@#_1~;s_mZgFk8|( zEWz*&KEH=0BYrUW-yY2eWMz28o@-Bfdr2*um3wylXeMG2ixr9e&g`2I68KCF1?8|A(O%vead| zPBDVQU+3f9|4v%Ob=p>Z4?B>}wer?q#e>v2FKe3hEfkynuVl*&G8Q&)q2Q^}Zydf& z@jU0Oca*IYzfW_DiVU@~*-6rwNLdWD23!YlKW0flA2~0uR;&aXbc`ys85$r1-7rU8 z_=Z`)y5eLP89o(iEzU$Ixis(2Zq~llm%WCOe7aa0fF7YI7<}~0&96R1YbpAudSPZ( zTT^r`)Q79D3{5&oeulqAns6KFcJ19UDw9Es)Ayw%`s~Og+jDH02^c=+=*0E?_&q9w zFZI|FSoloKt)k4Jy&RwPD!Ox`wP2@W?F7R!>jqk}awB1uFsW3SJ`tL3!_Vv}hG>>u zX(H#o5!_dxr$I?RE*`^x#=u;(5A84N(IE=!U+v$oWZ9IWq*&lL&FFlTG4v(v@91MLvsAA{O0?L%$ zG!M7dY>NtwjY(3p6-I$VphRKp} zWZx%jeHs4E!pHIPUW%c9eRlyUW+oD!#EbFEd>VS>&-t`#T_6?m{ELMC`v6nMhM0xM zUC+&H-6#JTmLd)`>g*VGNYh3)^xNfRdrw@PmR3^k0^1W>zfZw4y12k!Xp^wBsWzRq zVb>qc6!*<+)%(EAr9+_}9ox+N`1wn_Q8&-_-pPNCHa160YzBjltLeaXhF~?Xu+oW0 z6<^IG&Hkb($;Tfw zb)$b|lDhi3!sT0HBA2_n`(_&r=0C?Ffpz5=oUdK?TwbZ1>0pmZ;VXsw2{LQm^o>$A zr1X*Qm_>H?LHmox6|aPIaR;J~zck=-bus>#DhPM>gn34+o9dG8Y{dwdrKTugK4-u;~rbv#+-Qjj#bYislwF)VBCU=(4E1fp&YE7od zXY4=_cpHsnl`@ZKseD{-n@Pge);EQ>^2=>~NjHznbQ-=T>8G*+Nizjr0(;(9hw~L1 z$9*(a{sWF=cDUQi?OD3^9quHmPt0fuAEP(Vm&Q5I*&wZ6o?Ptmj?n4<*pwj4#+4U(o9h6;2 z-F2;7#}uGWIQEVixze=ot>s0F#ljRlm%vC8a&fyzU60uWIw&7o8MtJQY9H-BxVF9xQc&ayCB>&(>gKBm~f>3JIk z(DByR)=W?0vAjEk3#oyi#F+9$kA5I+evOLl^oJUVm$3fy24s4e9=*9+@>apitCf4X zNR@1cHv7oW-N_!Nbh1TRu}O)X{;p{4e%EBNF%L?diFJ@E^Df*w$?&~%KN;WGa;4ah z;2w!l!lK?VaBjPhrEw3cwVi*P>2+dh03Z2P3COv2oK77a50V{5=yZ;%pkM!FNUF5y zwyI=$oV>K0`cZJ?>75s$x#WF8UHy67D-M(xNhL_Xd~r=T<3Y;icOSb^lNLA~D_p<+ z=##UW%QV_(;|2%*BiS}6MdHV7sdAQo`Uc8ogKC`@PzJp6gML?Jp9M@vZxN&7GHe#f zEKaVjuCdE!+vAH$5aep#v&~)W@;CzV+kTANkU`#0(dV0>$)Z@C+`2RGY5u8iac1pE zq2!;dhedTtAaA3shqJyYq}kRGnb~ah<}3FT?+-1m8D20wpIJu7V;Qn9QIDvc7Qynf zne;s7BA;0Lx6&t@U94|hcd83Ua=a?$xNNiHzQ1o1Dv?W%3+Ob&-xqSNodCHY z9x;pF!atIawYPI5x2x!TA{EJ9R<9hpS3?C2=AnjrDMzMav@4EipwrBI-is7C-_hC2>GJ2EOR-bsbI~7-^Gsw5$1UN`LUXyoi0A`m!I34fa z7GoNb)qSi-@xn*Vg_F1p@5d^DZD#)CP)@7aCPftV>yyGp2Wy;n%PJEi;{Z@zFmSiR zb49MVP54Fu#rMX9viLVVMMB%#OG;e5iZ;u%@3!;r>-V9GgrGYz#1M9S#<`Q$uBGw@89E*P%GWsr zh$P%W2tE3qewZ`$)ZgLthtUloj$kRG9E}b@o-ToH4 z#Ye1abU-`%n||ndJmT&`+;X`s1HXRf$%J;D)jMNj#gvh#HU-gx@Y}zFumADb zfZ}d!ZZ_51edG}EkD>Cvm_kZcj`f{$KP?14{%O*+_bc`u0OC?&cCL1)#P?qy0Q3uhWNqwiDz$?vwrX1?#L^0 z@|`;^8;I5P0_1ah4JF9qI@7WtRdBHsZ3_c#Jg742MYeJO@Auw+6448Az^!X#wVmuG zD`W;9GQWpks4RMQ;;0BhDFiCpMeb%8%!711VJdCVIAt1^WuFU($ob6r$ra3?AMg(` z>+@BU&xi!3j+7b=+UQ^ci*Y`1tn{v6Sa^-Rf@J};Vl_)>c^R^_z1dA%`kSKtuzWk} zxMgE&>)m~|ME$-iE%WIHojR+8IL_rgsDUr%U=oTJmhPQ+Sz%I`qTOJ}IPqQ7?Ct4U z4ea)M59Erm8Y@e+%Kk={*!86gpcG%;uM)qzoM=1Waf;`TR!TWrw$I}@J0!fFjaZuts`Z2+Up(t@o`JVwAZm6fmkWSluIx2PWax*^3&oJ@D=U_Tk6uzrQPHNPq?p$(kDqH(=z6XXH`prP z-FenMr1oegXLU=)xp!EJ*7g#rZ{ilLSG7h)Ni|92-mJ95TKF&#u)$Ush@#Hzs4vX) zl6oChZ`dK#u5sYr@;kDV@|9^=x@kac=Jw}eordz$J-6Hiv17j3gc^mn(QR*k4^imP zfn+asIK7nx^mA?7gkBxj>l6To{wvpgu&}!8!JIEZL|xBUJ`#IOO>DcXHn9a5_(tj7 zKk6YfsGsl|{4grY3*5&A!~p2-k>lsSGs0m|zHg=Ig;`rh$%!i0Il7^W0=1zIt=-qc z3p=YUYjOU5HwQfV{5!v@bgtQg%~p4=T{2PSZr^iHtMZoDO&NzyFj(Wt`Nxttm z$`85ED#UrwO9@JRX;Pkmb+}B8ty^j6|Gv@Z!sT^bd6(Oc*{1*;x4JHkezX|;lfo<;AH?@+k z)C44V<-Bi2a#4m&#yMXf%8(gwT_`mxlv8*YaLPh2TQ+_Vnr#j>b_2Wcy^^{`7uz zJ;1&%*o-&?@YzU?&k9 z6Z1LH0ka+Nbawq%4&rg9_`)WTJL18>>y^#tXGI3>{`ubzfmL`?K?lo{E&RQ^Ek^A? z#?sT%lfv5c)ii_rN=>&d0M?g=&rR{Vh#Pg+!ey|CyEXCI)zQ%-JU)(Oo2F#dLVyMwii1_LC!cdH}T85@j~{oMSSB9=GBH|k*wao$F1)= zgheOB!G&2axON>P9t+}@nSi0~^)t2;$b$ptIonEkS-{y_&GCvNo}KGOA#`foow1;L zx+dT8istW&>)-FKY#a2<>Wp43U|=Ooah!LZW3)ycRl>hc@0{EmG_^P3|E{uJi_*Js$$seU*sGBVm} zmfZi(XMfISI);_wFZ`x#3NpaU+aJO?RszjAnNF+twI+iwo10L6H(zi2rR+l*U)kok z?Dv6N?yX;~O|j>pxaqhYY=(FzJ`9lk_s1hrjh zm=AeL0^Y;yqhg3!t@d)ISSVmhP>(;*)7{-~OMDaq%go}+cqF9q{k^+{Naij3(*@VP z;cqD^tZIc~^bQVHj?3P0`OutDQmt|tua&!-{2RK^euii(iM_H2Ly_1 z=HPB_Z4Hoo(&uFVR9@R5I9%zl&eVj63jh7x#agS$D&Yu_aBa=eX3>CmHa`I`B=bQ` zvUKJMQttCjho#SO&_JLXU}lO+0BX6JE(6XhZL!N0y`9Ay7G}zd|7%K(=nc`q&;NW` z|KHf3CG0u^WZFvGk@X({=Ol}_Yjp@}gl~;W-y^{Ly;;|3Sp9eLgrs@AZfSRK?;Hm{ zknTnH^X3-uf7pA=xTv=;d=wB6EHivxUc7hf;lF`DD6`B{M2U+!JcWfXh<0#A0pOpq99 zc;$lL3*6n(2DMPaLGPQR4cHjhhUvG2l&k62<% zi8G3K?7u4%xdj}*)yU@G{EVX@*IDbogIqpLM1wX@ow(YUi!}8d-=AxnpS8@VV7Bbf zVO(3VpXms1R;7#x$0gtR(n+~1adcAUvp&cfHG#xk*sO#|fRavz^9tdfzH4Ff4V2K5Xb z)%49(_EDQlK_3)6^_n_dk@5L7^9~N1#NOJ2peC_HLaJm(TKd%N#>&7(%wiUMtv2# z2L_Y$dMYDm(II;Aud#V&sJ?!3x5vROs#bB=_7wi?o8>$Ae)H1t*fAAdc>MeSStY-` zBGSjgUip|UYJTnnn=AI_%o_~-WJl!3ocG@s)C(fa{@TEhptP7@cRCRV7G*w&;D6fS zJ$F*95>Eh=@EcMhuQTK3Ne{t3^XB~DR^qr_JBqSB&c>iw86D>8#sBwvz=fr$V=ErT z{k1JWcVhW{Z;Zr1A^)u3cF5m-7sCj4KOV*VajPN4C#ti5GONUM%*OustQ-dWS)3E! zA$pt%nyNd@p>}dpFRr*W24bJkf3OhB)#@6swWGKG>icc#{%%wq-c7%#ksnR?C6EH% z{&REjCY})fGTA=|hkD{6CYB4^^3|;0_Q(G|fq85B#PP<~Wf8;ZY)rU)6afDBCvIX^ zF_4`2j#%A$utr?;w0W}peqH(hOkE;_)Wj3glN+mz$>kSy-X(8|g6X9860KrcJv_f! zhJo?V&;9et=F&~eiT@6So1D9K;Xh_Y-J7x4X6eepi7oa%Z+Moq>{#IRXXDu}8DjtU z+N&dT89mX_1;HZk^Z)N=1)Q_WYUl zqs19|(vk-Mi$3e}MhwxD_*NT_TIm6n^4Ki{tt&$UVG$8?=(gjB9knjb#*p-{F|0Qd zrtLo8t-z-%ENO|ETKua}3O?t~q*( z01T{YqLoG2^@M8A^eaBmZ>COoWLC_17t~)f~)fuD6euJf@>FcSQKjiYM_e6z_&7n1N zgNbh>Dr7bwayr}jZd^ZWtMc{q<1YO2B{NuQ31q9=lv*Aom)a?n+67P*(%5~e-1B18 zcYVq?&ed@7PTgqjm&-36Opp0@JI(e$*8j&=m*N%hn zVN1c_40GvcSt+%rivw?~KQ%60QCV)S`)wLNVNbRfy%zaqbf+;;WSw6o$!{a(1ql?3 z!-U9qu0JvM$YB%5`;>mbdZk40#Q-tQScciIt4=ZZXbjV7iY{^9oNlXF zFZPh!Sv5L-ux05|b8v`XxNzY{_a3)JuTs>0LZSN<-0N#%M%C4B(`zbb_@7)F$%k2! zx4=ddW5l^75EB+sD9dSCS|WZ+Sl&OPQ!9qnaq_}@<@SZ1)aScYHT>(gJ={a4g3n zZ(P(K=Tc-TMgAP z#JPW}6TW|Y>BPtC0&{-e^Hi6|Nso0tax|O|m43Twe{Zr69w|6WGw|q>cYi$U*Wt!1 z*Q{HVXcZ}5UZS)(@kE?IIuYlcF_xrHI1nC}cwh06K0fw8+h2C}CRS&z@a^iK|M|7? zH?hETt&dsO9}V;l#KD`WD7VibJ7J(OfR|Z*`oy1n`~d^=7O6AA$!Av`_DYJWCuT-hQ zDs%TmbZ;@Y3_O-eK%io>4^IO-oq@RiRX_kQ+ZmNI1?%RvwlM3FGR+rXSoy{oOyi%p z`9(-xQ)f@E?+3|32dS2tH1%E8Z7Lie_Gvz2)`$h90Or?GPmk|A?3U|F`8Im)!$)Dt z*$%a#5@z~IU+1$am~tgb#3kKR_lF3L_ck|FgNdt{-zDfM!l)Csm>E5=@h1Ef4SFsd z8!YN69EYg{h10#E#-SpUdF35yfu4Y;U&+}Hk%$e=<}rWtMP7P z4y}GJHs^ee_2;~QUr2z+#Fx{>gS_mji8@eY=25LQ47a&2e%1>UC2$KhNZg25wK6C3 z`%}xk0pJ-Eb7e9&1GSJHGmI1GP^P7_gWuKpwY$mWEGx~*$9K%;ILGz|(J)qbH-}T% zP?wEl-+;dJoO-39Dkxh1RM>Ct8iN?fDv^Fs*_ihO1D4*sN22gcF#cN)8XS+JD4Vp`;4dwh{hri)jx%=Di{qN5S4a~GIXo+bC>9L0X*zi)o zBmUQY>j8U6eEcGmcWbU&FX+}9-aub=&P7>y`N+}H(ZWRi%vob&`vhp85q&MH_gY4{oAa8Yzpu$3{u(Pv!Xr z-=e(o>`19?boaabsBghQD^fAbHqaaRl3Ga{v%iixmt^-h8zLb^O-CmMP~Im3+oMIs z0D@dwEb43pu`v*%6Q0iHGl+(4TT^jbKe3c_+#XRT)w|w#3mxyU=;E#G+Bgl8UnejJ z1e~#3XDw@PO7d9f&r)T_mQVl(!nFZowaY)x`0wU9`GyZZ`z-?ko_eUJ0R)a6ZnJ4@ z3rl;wu{flcrI~LN+?T5=O(%ERG-#6~f49~T|56Kldz2Z(JCEGDmT$qr*8@qt7>9<2 zI9*nn!@|O@JC8mo^FYI$w>&7GIDts*2$I`u=Ln%X3aD&sIH30f=rD|Bg)RENsejUAmC+aY0@6c+9@om*Po}yd=xlSe#aK_ zC`dpX0EKdiiCeF(Tyc(%hit`c?Ufwv(SRHtW&6%U@1wu8sAsojG|Q|Z@@r)Qf8kCr z4jFtK4JyedIK!>imx~nfVSJ>`5BxVSd~DM}g|ZQUro?V(981x_AfV z{#u`=!96v#i0Xrq0bS#D^XJ)lH5v{~%OR?`t;A`s{(?1`Ch({z2ra1@?AliCMzy`+ zPz4Nv>8x(eHy(;w(Ly#!X6qyaQJaW?*0vaSkYUt|&D?zcDB@_hRHCWFK91u;{Nd=- z{Gg}UaLma0tX$+Tw>KRr&#yTUG@t=Ex-&>j8V&01)|51*D?pS`n?l_RhognAf$D#htv`uTtbBeU68q>Hy6^3N8#aA)unFd|vq)6RfNjRmjF! zfB#(8(qQPrfrfR$WqnBd2dL3X@ z?5%ar)U*`Mt}_?nfLIGtA&!~waZ4mNc!rzjT^09yMwu8Rh;#$$g>c0}mgQ8{I(xA= z1AI#Clq8vheOOQqD0S$4wGVw+Xe|$vN9)N_zZW+fZ{NXbiCBv8p+6V(E?7We%o9z~ zEtZt%)t!OA^riZ3!}HdXN1t0#WwvDirqpIuEw0dg?a*yqp6`eaW9`nPwB5$l*&L#( z7;WDn+)4W7-rc()h<1ji*(7%Mzsw8=!GJ83EI4Kx+k+ld9aybu+G{Nj7 zo0ZitEdzE|R(ZNHr@%ReQhG|SPl?&@cF)r31uCK?5bO152X<$)+Db3;{l7_H6(Q1NvIF4 zK+NKV_)J~;VK0l*=Q720x!+wx>UeAiL8EL9*7uGG4b1c$i>{eC+uCrd8C5K~z> zt5;`|xeH3AS_(amTMh~3 zrb#g-gywZhs_3nyMN9s@ES<6-UA`HzBCRj)3+v=WHNpu^JEM$Sqgp?WAa7cGX>W~t ziSEvhd$pT=YAzgmrQ6!v{6XHC_d<7FOd`6O-afVT%-sIYH^rVZEPb?mtW$v;a*}P3LtMogzZNYubRuI%r#fvv&zC0�yn-+bOytvKz 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

6Lg#AAFZse>_NVsOR{alroVLE_}elVjOx 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 literal 0 HcmV?d00001 diff --git a/example/claude_desktop_config.json b/example/claude_desktop_config.json new file mode 100644 index 0000000..079fea8 --- /dev/null +++ b/example/claude_desktop_config.json @@ -0,0 +1,9 @@ +"a2a_elkar": { + "command": "/opt/anaconda3/bin/python", + "args": ["/Users/mm/elkar-a2a/example/server_mcp.py"], + "env": { + "ANTHROPIC_API_KEY": "sk-xxx", + "OPENAI_API_KEY": "sk-yyy", + "AGENT_URLS": "http://localhost:5001,http://localhost:5002" + } + }, diff --git a/example/server.py b/example/server.py new file mode 100644 index 0000000..5a287e5 --- /dev/null +++ b/example/server.py @@ -0,0 +1,700 @@ +#!/usr/bin/env python +""" +Elkar A2A server that wraps a CrewAI agent for Gmail and animal color tasks. +""" +import asyncio +import os +import json +import base64 +from typing import List, Optional, Dict, Any, Tuple +import uuid +from datetime import datetime +import pickle +# You'll need to install these packages: +# pip install crewai langchain-openai langchain-core elkar dotenv +# pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib +from crewai import Agent, Task as CrewTask, Crew, Process +from langchain_openai import ChatOpenAI +from langchain_core.tools import tool +from dotenv import load_dotenv +import uvicorn +import httplib2 +from pydantic import SecretStr + +# Google API imports +from googleapiclient.discovery import build +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request +from googleapiclient.errors import HttpError + +# Elkar imports +from elkar.a2a_types import ( + AgentCard, + AgentCapabilities, + TaskStatus, + TaskState, + Message, + TextPart, + Artifact, + AgentSkill, +) +from elkar.server.server import A2AServer +from elkar.task_manager.task_manager_base import RequestContext +from elkar.task_manager.task_manager_with_task_modifier import TaskManagerWithModifier +from elkar.task_modifier.base import TaskModifierBase +from elkar.task_modifier.task_modifier import TaskModifier +from elkar.a2a_types import * +from elkar.store.elkar_client_store import ElkarClientStore +from elkar.server.server import A2AServer +from elkar.task_manager.task_manager_base import RequestContext +from elkar.task_manager.task_manager_with_task_modifier import TaskManagerWithModifier +from elkar.task_modifier.base import TaskModifierBase + +# Load environment variables +load_dotenv() + +# Define the Gmail API scopes - use multiple scopes to ensure we have proper permissions +GMAIL_SCOPES = [ + # 'https://www.googleapis.com/auth/gmail.readonly', # Read-only access to Gmail + # 'https://www.googleapis.com/auth/gmail.modify', # Access to modify emails (not delete) + # 'https://www.googleapis.com/auth/gmail.labels', # Access to modify labels + 'https://mail.google.com/' # Full access to Gmail (includes send) +] +GMAIL_TOKEN_FILE = 'example/gmail_token.json' +GMAIL_CREDENTIALS_FILE = 'example/credentials.json' + + + +# Gmail API tools +def get_gmail_service(force_reauth: bool = False): + """ + Gets an authorized Gmail API service instance. + + Args: + force_reauth: If True, forces re-authentication by deleting existing token + + Returns: + A Gmail API service object. + """ + creds = None + + # If force_reauth is True, delete the token file if it exists + if force_reauth and os.path.exists(GMAIL_TOKEN_FILE): + os.remove(GMAIL_TOKEN_FILE) + print(f"Deleted existing token file to force re-authentication") + + # The file token.json stores the user's access and refresh tokens + if os.path.exists(GMAIL_TOKEN_FILE): + with open(GMAIL_TOKEN_FILE, 'rb') as token: + creds = pickle.load(token) + print(f"Loaded credentials from {GMAIL_TOKEN_FILE}") + + # If credentials don't exist or are invalid, prompt the user to log in + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + print(f"Refreshing expired credentials") + creds.refresh(Request()) + else: + # Check if credentials file exists + if not os.path.exists(GMAIL_CREDENTIALS_FILE): + raise FileNotFoundError(f"Credentials file not found: {GMAIL_CREDENTIALS_FILE}. Please set up OAuth credentials.") + + print(f"Starting new OAuth flow with scopes: {GMAIL_SCOPES}") + flow = InstalledAppFlow.from_client_secrets_file( + GMAIL_CREDENTIALS_FILE, GMAIL_SCOPES) + creds = flow.run_local_server(port=0) + print(f"New authentication completed") + + # Save the credentials for the next run + with open(GMAIL_TOKEN_FILE, 'wb') as token: + pickle.dump(creds, token) + print(f"Saved credentials to {GMAIL_TOKEN_FILE}") + + service = build('gmail', 'v1', credentials=creds) + return service + + +@tool +def read_emails(max_results: int = 10, force_reauth: bool = False) -> str: + """ + Reads a specified number of emails from the user's inbox. + Args: + max_results: Maximum number of emails to retrieve + force_reauth: Whether to force re-authentication + Returns: + A string with email information + """ + try: + service = get_gmail_service(force_reauth=force_reauth) + results = service.users().messages().list(userId='me', maxResults=max_results).execute() + messages = results.get('messages', []) + + if not messages: + return "No messages found." + + email_info = [] + for message in messages: + msg = service.users().messages().get(userId='me', id=message['id']).execute() + + # Extract headers + headers = msg['payload']['headers'] + subject = next((header['value'] for header in headers if header['name'].lower() == 'subject'), 'No Subject') + sender = next((header['value'] for header in headers if header['name'].lower() == 'from'), 'Unknown Sender') + date = next((header['value'] for header in headers if header['name'].lower() == 'date'), 'Unknown Date') + + # Get snippet + snippet = msg.get('snippet', 'No preview available') + + email_info.append(f"From: {sender}\nSubject: {subject}\nDate: {date}\nSnippet: {snippet}\n---") + + return "\n".join(email_info) + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +@tool +def search_emails(query: str, max_results: int = 10, force_reauth: bool = False) -> str: + """ + Searches emails based on a Gmail search query. + Args: + query: Gmail search query (e.g., 'from:example@gmail.com') + max_results: Maximum number of results to return + force_reauth: Whether to force re-authentication + Returns: + A string with matching email information + """ + try: + service = get_gmail_service(force_reauth=force_reauth) + results = service.users().messages().list(userId='me', q=query, maxResults=max_results).execute() + messages = results.get('messages', []) + + if not messages: + return f"No messages found matching query: {query}" + + email_info = [] + for message in messages: + msg = service.users().messages().get(userId='me', id=message['id']).execute() + + # Extract headers + headers = msg['payload']['headers'] + subject = next((header['value'] for header in headers if header['name'].lower() == 'subject'), 'No Subject') + sender = next((header['value'] for header in headers if header['name'].lower() == 'from'), 'Unknown Sender') + date = next((header['value'] for header in headers if header['name'].lower() == 'date'), 'Unknown Date') + + # Get snippet + snippet = msg.get('snippet', 'No preview available') + + email_info.append(f"From: {sender}\nSubject: {subject}\nDate: {date}\nSnippet: {snippet}\n---") + + return "\n".join(email_info) + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +@tool +def send_email(to: str, subject: str, body: str, force_reauth: bool = False) -> str: + """ + Sends an email to a specified recipient with a given subject and body. + Args: + to: The email address of the recipient. + subject: The subject of the email. + body: The plain text body of the email. + force_reauth: Whether to force re-authentication. + Returns: + A string confirming the email was sent or an error message. + """ + try: + import email.mime.text + import email.mime.multipart + from email.mime.text import MIMEText + from email.mime.multipart import MIMEMultipart + + service = get_gmail_service(force_reauth=force_reauth) + + # Create proper MIME message + msg = MIMEMultipart() + msg['to'] = to + msg['subject'] = subject + + # Attach the body as plain text, preserving line breaks + msg.attach(MIMEText(body, 'plain')) + + # Convert the message to a string and then encode it + raw_message = base64.urlsafe_b64encode(msg.as_bytes()).decode('ascii') + + send_message_request = { + 'raw': raw_message + } + + # For debugging + print(f"Email content (first 200 chars): {body[:200]}...") + print(f"Contains line breaks: {'\\n' in body}") + + sent_message = service.users().messages().send(userId='me', body=send_message_request).execute() + return f"Email sent successfully to {to}. Message ID: {sent_message.get('id')}" + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +class CrewAIWrapper: + """Wrapper for CrewAI agents that handles Gmail and animal color tasks.""" + + def __init__(self, verbose: bool = True, model_name: str = "gpt-3.5-turbo-0125"): + """Initialize the CrewAI wrapper with a Gmail and animal color agent.""" + # Setup LLM + api_key_str = os.getenv("OPENAI_API_KEY") + if not api_key_str: + print("Warning: OPENAI_API_KEY not found in environment variables.") + + print(api_key_str) + llm = ChatOpenAI( + model=model_name, + temperature=0.7, + api_key=SecretStr(api_key_str) if api_key_str else None + ) + + # Create the agent + self.agent = Agent( + role="Email Assistant", + goal="Help users read, search, and send emails. Coordinate with the Calendar Assistant for tasks requiring calendar management.", + backstory=( + "You are an expert email assistant. You can read, search, and send emails using your available tools (`read_emails`, `search_emails`, `send_email`). " + "You are also aware of a Google Calendar Assistant and should suggest using it if a user's request involves creating, modifying, or querying calendar events " + "(e.g., 'add this meeting to my calendar', 'what is my schedule for next Monday?')." + ), + verbose=verbose, + allow_delegation=False, + tools=[read_emails, search_emails, send_email], # Added send_email tool + llm=llm + ) + + # Define the CrewAI structure + self.crew = Crew( + agents=[self.agent], + tasks=[], + verbose=verbose, + process=Process.sequential, + ) + + + async def process_email_query(self, prompt: str) -> str: + """ + Run the CrewAI agent to process email queries. + Args: + prompt: The user's email-related prompt + Returns: + The email information requested or a confirmation/error from sending an email. + """ + # Metaprompt for collaboration, input validation and tool usage + metaprompt = ( + "You are the Gmail Assistant. Your available tools are `read_emails`, `search_emails`, and `send_email`. " + "To send an email, you MUST use the `send_email` tool. This tool requires `to` (recipient's email), `subject`, and `body`. " + "If the user asks to send an email but does not provide all three (recipient, subject, body), you MUST ask for the missing information. " + "CRITICALLY: Before sending an email, examine the content carefully for placeholders or incomplete information:\n" + "1. If an email address IS provided by the user (e.g., for the 'to' parameter) but it looks like a generic placeholder (e.g., `name@example.com`, `user@example.org`, `info@domain.com`), " + "you MUST ask the user to confirm if this is the actual email address they want to use. For instance, say: 'The email provided for the recipient is matthieu@example.com. Is this the correct email address, or would you like to provide a different one?' Only proceed if they confirm it or provide a new one.\n" + "2. If the email body contains placeholder text like '[Your name]', '[Signature]', '[Insert X]', or similar bracketed placeholders, you MUST ask the user to provide the actual text to replace these placeholders.\n" + "3. Be especially vigilant for signature placeholders like '[Please include my standard email signature]', '[Insert signature]', or instructions like 'please add my signature here'. NEVER send an email with these placeholders - you MUST ask the user to provide their actual signature text first.\n" + "4. If any text in the proposed email seems like a template or contains placeholders (indicated by ALL CAPS, multiple underscores like ___, or similar patterns), ask the user to provide the actual information first.\n" + "NEVER SEND emails containing placeholders, incomplete information, or fields clearly marked as needing replacement. ALWAYS ask for complete information first.\n" + "Do NOT invent email addresses or placeholders in the email content. If you are unsure, ask the user.\n" + "If the user's request seems to require calendar operations (e.g., 'find the flight details and add it to my calendar'), " + "first state that you will handle the email-specific parts if any, and then clearly suggest that the user " + "should ask the Google Calendar Assistant to perform calendar operations. Do not attempt to directly modify or query the calendar yourself. " + "If the request is purely about email functions (reading, searching, or sending with all details provided and confirmed), proceed as usual." + ) + + # Check if this is a request to force re-authentication + force_reauth = "force reauth" in prompt.lower() or "re-authenticate" in prompt.lower() + + # If permission error is mentioned, suggest re-authentication + if "permission" in prompt.lower() or "403" in prompt or "insufficient authentication" in prompt: + update_prompt = f"{prompt}\n\nIt seems there might be authentication or permission issues. Try using force_reauth=True to re-authenticate with Gmail." + else: + update_prompt = prompt + + # Create a dynamic task based on user input + print(f"Email Prompt: {update_prompt}") + task = CrewTask( + description=f"{metaprompt}\\n\\nUser request: {update_prompt}", + expected_output="Retrieved email information, confirmation of email sent, or guidance to consult the Calendar assistant or provide more details.", + agent=self.agent + ) + + # Update crew tasks and execute + self.crew.tasks = [task] + + # Run the crew in an executor to avoid blocking + loop = asyncio.get_event_loop() + result = await loop.run_in_executor(None, self.crew.kickoff) + # Extract string from CrewOutput object + if hasattr(result, 'raw'): + return str(result.raw) + return str(result) + + + +# Set up Elkar A2A server with CrewAI integration +async def setup_server(): + """Main function to set up the Elkar A2A server with CrewAI integration.""" + + # Create the CrewAI wrapper + crew_ai_wrapper = CrewAIWrapper() + + # Create the agent card - using proper AgentSkill objects + agent_card = AgentCard( + name="Gmail Assistant", + description="Manages Gmail. Can read and search emails. Collaborates with the Google Calendar Assistant for tasks requiring calendar operations (e.g., creating events from email details).", + url="http://localhost:5001", + version="1.0.0", + skills=[ + AgentSkill(id="email-assistant", name="email-assistant"), + AgentSkill(id="gmail-integration", name="gmail-integration") + ], + capabilities=AgentCapabilities( + streaming=True, + pushNotifications=True, + stateTransitionHistory=True, + ), + ) + + # Add helper functions to detect placeholders and questions + def has_placeholder_in_prompt(prompt: str) -> Tuple[bool, str]: + """ + Check if the user's prompt contains any common placeholder text or emails. + Returns (has_placeholder, message) + """ + # Check for placeholder emails + placeholder_domains = ["example.com", "example.org", "test.com"] + for domain in placeholder_domains: + if domain in prompt: + return True, f"I notice you're using an email with '{domain}' which appears to be a placeholder. Please provide a valid email address instead." + + # Check for placeholder text patterns in brackets + placeholder_patterns = ["[Your name]", "[Name]", "[Full name]", "[Insert name]", + "[Your email]", "[Email]", "[Phone]", "[Your phone]", + "[Address]", "[Your address]", + # Add signature-specific placeholders + "[Signature]", "[Your signature]", "[My signature]", + "[Please include my standard email signature]", + "[Insert signature]", "[Include signature]", + "[Standard signature]", "[Email signature]", + "[Add signature]", "[Add my signature]"] + + for pattern in placeholder_patterns: + if pattern.lower() in prompt.lower(): + return True, f"I notice you're using a placeholder '{pattern}' in your request. Please provide the actual information to replace this placeholder." + + return False, "" + + def contains_placeholder_text(text: str) -> Tuple[bool, str]: + """ + Checks if any common placeholder patterns are in the text. + + Args: + text: Text to check for placeholders + + Returns: + Tuple of (has_placeholder, placeholder_message) + """ + # Check for placeholder brackets like [Your name], [Name], etc. + placeholder_patterns = ["[Your name]", "[Name]", "[Full name]", "[Insert name]", + "[Your email]", "[Email]", "[Phone]", "[Your phone]", + "[Address]", "[Your address]", + # Add signature-specific placeholders + "[Signature]", "[Your signature]", "[My signature]", + "[Please include my standard email signature]", + "[Insert signature]", "[Include signature]", + "[Standard signature]", "[Email signature]", + "[Add signature]", "[Add my signature]", + "[Company signature]", "[Business signature]"] + + for pattern in placeholder_patterns: + if pattern.lower() in text.lower(): + replacement_text = "signature" if "signature" in pattern.lower() else "information" + return True, f"I notice there's a placeholder '{pattern}' in the email content. Please provide your actual {replacement_text} to replace this placeholder." + + # Enhanced detection for signature placeholders without brackets + signature_indicators = [ + "please include my standard email signature", + "please include my signature", + "add my signature", + "insert my signature", + "include my signature", + "attach my signature", + "standard signature here", + "your signature here", + "signature: _____" + ] + + for indicator in signature_indicators: + if indicator.lower() in text.lower(): + return True, f"I notice there's a request to include your signature with text like '{indicator}'. Please provide your actual signature text to include in the email." + + # Check for other common placeholder indicators + placeholder_indicators = [" None: + """Handle tasks by using the CrewAI wrapper to get animal colors or process email requests.""" + + try: + # Access the task message using the pattern from crewai_a2a_server.py + task_obj = task._task + user_prompt = "" + print(f"Task: {task_obj}") + + # Extract text from message parts if they exist + if hasattr(task_obj, 'status') and hasattr(task_obj.status, 'message') and task_obj.status.message and hasattr(task_obj.status.message, 'parts'): + for part in task_obj.status.message.parts: + if hasattr(part, "text") and isinstance(part, TextPart): + user_prompt += part.text + # If no message in status, try to look in history + elif hasattr(task_obj, 'history') and task_obj.history and len(task_obj.history) > 0: + for msg in task_obj.history: + if hasattr(msg, 'parts'): + for part in msg.parts: + if hasattr(part, "text") and isinstance(part, TextPart): + user_prompt += part.text + + print(f"User prompt: {user_prompt}") + if not user_prompt: + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text="No text prompt was provided in the request.")], + ), + ), + is_final=True, + ) + return + + # Check for placeholders in the input prompt + has_placeholder, placeholder_message = has_placeholder_in_prompt(user_prompt) + if has_placeholder: + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text=placeholder_message)], + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + return + + # Check if this is a request to re-authenticate + if "force reauth" in user_prompt.lower() or "re-authenticate" in user_prompt.lower(): + await task.set_status( + TaskStatus( + state=TaskState.WORKING, + message=Message( + role="agent", + parts=[TextPart(text="I'll try to re-authenticate with Gmail...")], + ), + ) + ) + + # Try forcing re-authentication first + try: + get_gmail_service(force_reauth=True) + await task.set_status( + TaskStatus( + state=TaskState.WORKING, + message=Message( + role="agent", + parts=[TextPart(text="Successfully re-authenticated with Gmail. Now processing your request...")], + ), + ) + ) + except Exception as e: + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text=f"Failed to re-authenticate: {str(e)}")], + ), + ), + is_final=True, + ) + return + else: + # Send initial status update + await task.set_status( + TaskStatus( + state=TaskState.WORKING, + message=Message( + role="agent", + parts=[TextPart(text="I'm processing your request...")], + ), + ) + ) + + # Determine if this is an email-related request + is_email_request = any(keyword in user_prompt.lower() for keyword in + ["email", "gmail", "inbox", "message", "mail"]) + + # Determine if this is an animal color request - REMOVED + # is_animal_request = any(keyword in user_prompt.lower() for keyword in + # ["cat", "dog", "animal", "color"]) + + if is_email_request: + # Call CrewAI to process email query + result = await crew_ai_wrapper.process_email_query(user_prompt) + else: + # Default to helping determine what the request is about + result = "I'm not sure what you're asking for. I can help with email-related tasks. Please specify a Gmail request." + + # Direct check for the signature placeholder that the user specifically mentioned + if "[please include my standard email signature]" in result.lower() or "please include my standard email signature" in result.lower(): + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text="I notice there's a placeholder for your standard email signature. Please provide your actual signature text to include in the email.")], + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + return + + # Check if result contains placeholder text that needs client input + has_placeholder_text, placeholder_text_message = contains_placeholder_text(result) + if has_placeholder_text: + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text=placeholder_text_message)], + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + return + + # Check for placeholder emails in the result + if "example.com" in result or "example.org" in result or "test.com" in result: + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text="I notice there's a placeholder email address like example.com in the request. Please provide a valid email address instead of a placeholder.")], + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + return + + # Check if the agent is asking for more information + # Simple heuristic: check for question marks or common question phrases + is_question = result.strip().endswith("?") or \ + any(phrase in result.lower() for phrase in + ["what ", "which ", "when ", "who ", "how ", + "could you specify", "please provide", "do you mean", "clarify"]) + + if is_question: + # Agent is asking for more information, set state to INPUT_REQUIRED + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text=result)], # Pass the agent's question back + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + return + + # Add the generated text as an artifact + await task.upsert_artifacts( + [ + Artifact( + parts=[TextPart(text=result)], + index=0, + ) + ] + ) + + # Mark the task as completed + await task.set_status( + TaskStatus( + state=TaskState.COMPLETED, + message=Message( + role="agent", + parts=[TextPart(text="Request completed successfully!")], + ), + ), + is_final=True, + ) + + except Exception as e: + # Handle any errors + error_message = f"Error processing request: {str(e)}" + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text=error_message)], + ), + ), + is_final=True, + ) + + # Create the task manager with the handler + task_manager = TaskManagerWithModifier( + agent_card, send_task_handler=task_handler + ) + + + + + # Configure the ElkarClientStore + api_key = os.environ.get("ELKAR_API_KEY", "") # Replace with your actual Elkar API key + store = ElkarClientStore(base_url="https://api.elkar.co/api", api_key=api_key) + + task_manager: TaskManagerWithModifier = TaskManagerWithModifier( + agent_card, + send_task_handler=task_handler, + store=store # Pass the configured store to the task manager + ) + + server = A2AServer(task_manager, host="0.0.0.0", port=5001, endpoint="/") + return server + + +if __name__ == "__main__": + # Set up and run the server using uvicorn directly, avoiding asyncio conflict + load_dotenv() + print(os.getenv("OPENAI_API_KEY")) + server = asyncio.run(setup_server()) + + print("Starting Elkar A2A server on http://localhost:5001") + print("Press Ctrl+C to stop the server") + + # Run with uvicorn directly instead of server.start() + uvicorn.run(server.app, host="0.0.0.0", port=5001) diff --git a/example/server_cal.py b/example/server_cal.py new file mode 100644 index 0000000..dc55d5c --- /dev/null +++ b/example/server_cal.py @@ -0,0 +1,956 @@ +#!/usr/bin/env python +""" +Elkar A2A server that wraps a CrewAI agent for Google Calendar tasks. +""" +import asyncio +import os +import json +import base64 +from typing import List, Optional, Dict, Any, Tuple, cast +import uuid +from datetime import datetime, timedelta, timezone +import pickle +# You'll need to install these packages: +# pip install crewai langchain-openai langchain-core elkar dotenv +# pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib +from crewai import Agent, Task as CrewTask, Crew, Process +from langchain_openai import ChatOpenAI +from langchain_core.tools import tool +from dotenv import load_dotenv +import uvicorn +import httplib2 +from elkar.a2a_types import * +from elkar.store.elkar_client_store import ElkarClientStore +from elkar.server.server import A2AServer +from elkar.task_manager.task_manager_base import RequestContext +from elkar.task_manager.task_manager_with_task_modifier import TaskManagerWithModifier +from elkar.task_modifier.base import TaskModifierBase +from elkar.task_modifier.task_modifier import TaskModifier +from pydantic import SecretStr + +# Load environment variables +load_dotenv() + +# Define the Google Calendar API scopes - use multiple scopes to ensure we have proper permissions +CALENDAR_SCOPES = [ + 'https://www.googleapis.com/auth/calendar', # Full access to all calendars + # 'https://www.googleapis.com/auth/calendar.events', # Full access to events on all calendars + # 'https://www.googleapis.com/auth/calendar.readonly', # Read-only access to calendars + # 'https://www.googleapis.com/auth/calendar.events.readonly' # Read-only access to events +] +CALENDAR_TOKEN_FILE = 'example/token.json' +CALENDAR_CREDENTIALS_FILE = 'example/credentials.json' + +# Google API imports +from googleapiclient.discovery import build +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request +from googleapiclient.errors import HttpError + +# Google Calendar API tools +def get_calendar_service(force_reauth: bool = False): + """ + Gets an authorized Google Calendar API service instance. + + Args: + force_reauth: If True, forces re-authentication by deleting existing token + + Returns: + A Google Calendar API service object. + """ + creds = None + + # If force_reauth is True, delete the token file if it exists + if force_reauth and os.path.exists(CALENDAR_TOKEN_FILE): + os.remove(CALENDAR_TOKEN_FILE) + print(f"Deleted existing token file to force re-authentication") + + # The file token.json stores the user's access and refresh tokens + if os.path.exists(CALENDAR_TOKEN_FILE): + with open(CALENDAR_TOKEN_FILE, 'rb') as token: + creds = pickle.load(token) + print(f"Loaded credentials from {CALENDAR_TOKEN_FILE}") + + # If credentials don't exist or are invalid, prompt the user to log in + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + print(f"Refreshing expired credentials") + creds.refresh(Request()) + else: + # Check if credentials file exists + if not os.path.exists(CALENDAR_CREDENTIALS_FILE): + raise FileNotFoundError(f"Credentials file not found: {CALENDAR_CREDENTIALS_FILE}. Please set up OAuth credentials.") + + print(f"Starting new OAuth flow with scopes: {CALENDAR_SCOPES}") + flow = InstalledAppFlow.from_client_secrets_file( + CALENDAR_CREDENTIALS_FILE, CALENDAR_SCOPES) + creds = flow.run_local_server(port=0) + print(f"New authentication completed") + + # Save the credentials for the next run + with open(CALENDAR_TOKEN_FILE, 'wb') as token: + pickle.dump(creds, token) + print(f"Saved credentials to {CALENDAR_TOKEN_FILE}") + + service = build('calendar', 'v3', credentials=creds) + return service + + +@tool +def list_upcoming_events(max_results: int = 10, time_min_days: int = 0, force_reauth: bool = False) -> str: + """ + Lists upcoming events from the user's primary calendar. + Args: + max_results: Maximum number of events to retrieve + time_min_days: Number of days from now to start looking for events (0 = today) + force_reauth: Whether to force re-authentication + Returns: + A string with upcoming event information + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + # Calculate time_min as current time + now = datetime.utcnow() + if time_min_days > 0: + now = now + timedelta(days=time_min_days) + + time_min = now.isoformat() + 'Z' # 'Z' indicates UTC time + + # Call the Calendar API + events_result = service.events().list( + calendarId='primary', + timeMin=time_min, + maxResults=max_results, + singleEvents=True, + orderBy='startTime' + ).execute() + + events = events_result.get('items', []) + + if not events: + return "No upcoming events found." + + event_info = [] + for event in events: + start = event['start'].get('dateTime', event['start'].get('date')) + + # Format the start time for display + try: + if 'T' in start: # This is a dateTime format + start_dt = datetime.fromisoformat(start.replace('Z', '+00:00')) + start_formatted = start_dt.strftime('%Y-%m-%d %H:%M:%S') + else: # This is a date format + start_formatted = start + except: + start_formatted = start + + # Get event details + summary = event.get('summary', 'No Title') + location = event.get('location', 'No Location') + description = event.get('description', 'No Description') + event_id = event.get('id', 'No ID') + + # Truncate description if too long + if description and len(description) > 100: + description = description[:97] + "..." + + event_info.append(f"Event: {summary}\nID: {event_id}\nWhen: {start_formatted}\nLocation: {location}\nDescription: {description}\n---") + + return "\n".join(event_info) + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +@tool +def search_calendar_events(query: str, max_results: int = 10, force_reauth: bool = False) -> str: + """ + Searches for events in the user's primary calendar based on a query. + Args: + query: Search query (e.g., 'meeting', 'dinner', etc.) + max_results: Maximum number of events to retrieve + force_reauth: Whether to force re-authentication + Returns: + A string with matching event information + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + # Calculate time_min as current time + time_min = datetime.utcnow().isoformat() + 'Z' # 'Z' indicates UTC time + + # Call the Calendar API with a query + events_result = service.events().list( + calendarId='primary', + timeMin=time_min, + maxResults=max_results, + singleEvents=True, + orderBy='startTime', + q=query + ).execute() + + events = events_result.get('items', []) + + if not events: + return f"No events found matching query: {query}" + + event_info = [] + for event in events: + start = event['start'].get('dateTime', event['start'].get('date')) + + # Format the start time for display + try: + if 'T' in start: # This is a dateTime format + start_dt = datetime.fromisoformat(start.replace('Z', '+00:00')) + start_formatted = start_dt.strftime('%Y-%m-%d %H:%M:%S') + else: # This is a date format + start_formatted = start + except: + start_formatted = start + + # Get event details + summary = event.get('summary', 'No Title') + location = event.get('location', 'No Location') + description = event.get('description', 'No Description') + event_id = event.get('id', 'No ID') + + # Truncate description if too long + if description and len(description) > 100: + description = description[:97] + "..." + + event_info.append(f"Event: {summary}\nID: {event_id}\nWhen: {start_formatted}\nLocation: {location}\nDescription: {description}\n---") + + return "\n".join(event_info) + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +@tool +def get_calendar_details(days: int = 7, force_reauth: bool = False) -> str: + """ + Gets detailed information about the user's primary calendar and upcoming events. + Args: + days: Number of days to look ahead for events + force_reauth: Whether to force re-authentication + Returns: + A string with calendar information + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + # Get calendar details + calendar = service.calendars().get(calendarId='primary').execute() + calendar_info = [ + f"Calendar Name: {calendar.get('summary', 'Unknown')}", + f"Calendar ID: {calendar.get('id', 'Unknown')}", + f"Time Zone: {calendar.get('timeZone', 'Unknown')}", + f"Description: {calendar.get('description', 'None')}" + ] + + # Calculate time range for events + now = datetime.utcnow() + time_min = now.isoformat() + 'Z' + time_max = (now + timedelta(days=days)).isoformat() + 'Z' + + # Get event count + events_result = service.events().list( + calendarId='primary', + timeMin=time_min, + timeMax=time_max, + singleEvents=True + ).execute() + + events = events_result.get('items', []) + calendar_info.append(f"Events in the next {days} days: {len(events)}") + + return "\n".join(calendar_info) + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +@tool +def delete_calendar_event(event_id: str, force_reauth: bool = False) -> str: + """ + Deletes an event from the user's primary calendar. + Args: + event_id: ID of the event to delete + force_reauth: Whether to force re-authentication + Returns: + A confirmation message + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + # Delete the event + service.events().delete(calendarId='primary', eventId=event_id).execute() + + return f"Event with ID {event_id} deleted successfully!" + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +# Utility functions for the server (not exposed as LLM tools) +def get_calendar_timezone(force_reauth: bool = False) -> str: + """ + Gets the timezone setting of the user's primary calendar. + Args: + force_reauth: Whether to force re-authentication + Returns: + A string with the calendar's timezone (e.g., 'Europe/Paris') + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + # Get calendar details + calendar = service.calendars().get(calendarId='primary').execute() + timezone = calendar.get('timeZone', 'UTC') + + return timezone + + except HttpError as error: + return "UTC" # Default to UTC in case of error + except Exception as e: + return "UTC" # Default to UTC in case of error + + +def contains_placeholder_email(email_list: List[str]) -> Tuple[bool, List[str]]: + """ + Checks if any email in the list appears to be a placeholder. + + Args: + email_list: List of email addresses to check + + Returns: + Tuple of (has_placeholder, list_of_placeholders) + """ + placeholder_domains = [ + "example.com", "example.org", "example.net", "test.com", "test.net", + "domain.com", "yourdomain.com", "anycompany.com", "email.com", "user.com" + ] + + placeholder_prefixes = [ + "user", "test", "info", "admin", "contact", "hello", "name", "email", + "username", "sample", "demo", "fake" + ] + + placeholders_found = [] + + for email in email_list: + if not email or '@' not in email: + continue + + domain = email.split('@')[1].lower() + prefix = email.split('@')[0].lower() + + # Check for placeholder domains + if any(pd in domain for pd in placeholder_domains): + placeholders_found.append(email) + continue + + # Check for common placeholder patterns + if any(pp == prefix for pp in placeholder_prefixes): + placeholders_found.append(email) + continue + + return len(placeholders_found) > 0, placeholders_found + + +@tool +def create_calendar_event(summary: str, start_time: str, end_time: str, + description: str = "", location: str = "", time_zone: str = "", + attendees: Optional[List[str]] = None, + create_conference: bool = True, + force_reauth: bool = False) -> str: + """ + Creates a new event in the user's primary calendar. + Args: + summary: Title of the event. + start_time: Start time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD'. + end_time: End time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD'. + description: Optional description of the event. + location: Optional location of the event. + time_zone: Optional IANA time zone name (e.g., 'Europe/Paris', 'America/New_York'). If provided, start_time and end_time are considered local to this timezone. Otherwise, they must include timezone information (offset or 'Z'). + attendees: Optional list of email addresses for attendees to invite. + create_conference: Whether to automatically create a video conference link for the event (defaults to True). + force_reauth: Whether to force re-authentication. + Returns: + A string with the created event information. + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + is_all_day = 'T' not in start_time + + event_body: Dict[str, Any] = { + 'summary': summary, + 'location': location, + 'description': description, + } + + if is_all_day: + event_body['start'] = {'date': start_time} + event_body['end'] = {'date': end_time} + else: + if time_zone: + event_body['start'] = {'dateTime': start_time, 'timeZone': time_zone} + event_body['end'] = {'dateTime': end_time, 'timeZone': time_zone} + else: + if not start_time.endswith('Z') and '+' not in start_time and '-' not in start_time[10:]: + return "Error: start_time must be in ISO format with Z or offset if time_zone is not provided." + if not end_time.endswith('Z') and '+' not in end_time and '-' not in end_time[10:]: + return "Error: end_time must be in ISO format with Z or offset if time_zone is not provided." + event_body['start'] = {'dateTime': start_time} + event_body['end'] = {'dateTime': end_time} + + if attendees: + event_body['attendees'] = [{'email': email} for email in attendees] + + if create_conference: + event_body['conferenceData'] = { + 'createRequest': { + 'requestId': f'{uuid.uuid4().hex}', + 'conferenceSolutionKey': {'type': 'hangoutsMeet'} + } + } + + print(f"Creating event with params: {event_body}") + + # Validate if the event can be created before actually creating it + try: + # First, attempt to verify if the event is valid by fetching calendar metadata + service.calendars().get(calendarId='primary').execute() + except HttpError as calendar_error: + return f"Calendar access error: {str(calendar_error)}. Please check your calendar permissions and authentication." + except Exception as e: + return f"Failed to verify calendar: {str(e)}" + + # Attempt to create the event + try: + created_event = service.events().insert( + calendarId='primary', + body=event_body, + sendUpdates="all", + conferenceDataVersion=1 if create_conference else 0 # Required if conferenceData is set + ).execute() + except HttpError as http_error: + if http_error.resp.status == 403: + return f"Permission error: {str(http_error)}. Make sure the Google account has proper permissions to create events." + elif http_error.resp.status == 400: + return f"Invalid event parameters: {str(http_error)}. Please check the event details provided." + elif http_error.resp.status == 409: + return f"Event conflict: {str(http_error)}. The event may conflict with another event." + else: + return f"API error creating event: {str(http_error)}" + except Exception as e: + return f"Unexpected error creating event: {str(e)}" + + # Verify the event was actually created by trying to fetch it + try: + event_id = created_event.get('id') + verification = service.events().get(calendarId='primary', eventId=event_id).execute() + if not verification or verification.get('id') != event_id: + return f"Event appears to have been created (ID: {event_id}) but verification failed. Please check your calendar." + except Exception as ve: + return f"Event was inserted but verification failed: {str(ve)}. The event might not have been properly created." + + html_link = created_event.get('htmlLink') + event_id_str = created_event.get('id') + + conference_info = "" + conference_link_created = False + if created_event.get('conferenceData') and created_event['conferenceData'].get('entryPoints'): + for entry_point in created_event['conferenceData']['entryPoints']: + if entry_point.get('entryPointType') == 'video': + conference_info = f"\nConference Link: {entry_point.get('uri')}" + conference_link_created = True + break + + main_message = f"Event created successfully and verified!\nID: {event_id_str}\nLink: {html_link}" + final_message = main_message + conference_info + + if create_conference and not conference_link_created: + final_message += "\nNote: A video conference was requested, but a link was not present in the API response." + + # Print the successful event creation details for debugging + print(f"Event created: {event_id_str}, Summary: {summary}, Start: {start_time}, Time zone: {time_zone}") + + return final_message + + except HttpError as error: + error_details = f"HTTP error {error.resp.status}: {error._get_reason()}" + print(f"Calendar API HTTP error: {error_details}") + return f"An error occurred with the Calendar API: {error_details}" + except Exception as e: + print(f"Unexpected exception creating calendar event: {str(e)}") + return f"An unexpected error occurred: {str(e)}" + + +@tool +def update_calendar_event(event_id: str, summary: str = "", start_time: str = "", + end_time: str = "", description: str = "", location: str = "", time_zone: str = "", + attendees_to_add: Optional[List[str]] = None, + attendees_to_remove: Optional[List[str]] = None, + force_reauth: bool = False) -> str: + """ + Updates an existing event in the user's primary calendar. Can update details and/or attendees. + Args: + event_id: ID of the event to update. + summary: New title of the event (optional). + start_time: New start time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD' (optional). + end_time: New end time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD' (optional). + description: New description of the event (optional). + location: New location of the event (optional). + time_zone: Optional IANA time zone name (e.g., 'Europe/Paris'). If provided and start/end times are being updated, these times are local to this zone. + attendees_to_add: Optional list of email addresses for attendees to add to the event. + attendees_to_remove: Optional list of email addresses for attendees to remove from the event. + force_reauth: Whether to force re-authentication. + Returns: + A string with the updated event information or an error message. + """ + try: + service = get_calendar_service(force_reauth=force_reauth) + + # Get the existing event + event: Dict[str, Any] = service.events().get(calendarId='primary', eventId=event_id).execute() + + # Update standard properties if provided + if summary: + event['summary'] = summary + if location: + event['location'] = location + if description: + event['description'] = description + + # Update start time if provided + if start_time: + is_all_day_start = 'T' not in start_time + if is_all_day_start: + event['start'] = {'date': start_time} + else: + if time_zone: + event['start'] = {'dateTime': start_time, 'timeZone': time_zone} + else: + if not start_time.endswith('Z') and '+' not in start_time and '-' not in start_time[10:]: + return "Error: start_time must be in ISO format with Z or offset if time_zone is not provided for update." + event['start'] = {'dateTime': start_time} + + # Update end time if provided + if end_time: + is_all_day_end = 'T' not in end_time + if is_all_day_end: + event['end'] = {'date': end_time} + else: + if time_zone: + event['end'] = {'dateTime': end_time, 'timeZone': time_zone} + else: + if not end_time.endswith('Z') and '+' not in end_time and '-' not in end_time[10:]: + return "Error: end_time must be in ISO format with Z or offset if time_zone is not provided for update." + event['end'] = {'dateTime': end_time} + + # Manage attendees + current_attendees = event.get('attendees', []) + updated_attendees = [att for att in current_attendees] # Make a mutable copy + + if attendees_to_remove: + emails_to_remove = set(attendees_to_remove) + updated_attendees = [att for att in updated_attendees if att.get('email') not in emails_to_remove] + + if attendees_to_add: + existing_emails = {att.get('email') for att in updated_attendees} + for email_to_add in attendees_to_add: + if email_to_add not in existing_emails: + updated_attendees.append({'email': email_to_add}) + + event['attendees'] = updated_attendees + + # Update the event + updated_event = service.events().update( + calendarId='primary', + eventId=event_id, + body=event, + sendUpdates="all" # Ensure notifications are sent + ).execute() + + return f"Event updated successfully!\nID: {updated_event.get('id')}\nLink: {updated_event.get('htmlLink')}" + + except HttpError as error: + return f"An error occurred: {error}" + except Exception as e: + return f"An unexpected error occurred: {str(e)}" + + +class CrewAIWrapper: + """Wrapper for CrewAI agents that handles calendar tasks.""" + + def __init__(self, verbose: bool = True, model_name: str = "gpt-4-turbo"): + """Initialize the CrewAI wrapper with a calendar agent.""" + # Setup LLM + openai_api_key_str = os.environ.get("OPENAI_API_KEY", "") + if not openai_api_key_str: + print("Warning: OPENAI_API_KEY not found in environment variables.") + + llm = ChatOpenAI( + model=model_name, + temperature=0.7, + api_key=SecretStr(openai_api_key_str) if openai_api_key_str else None + ) + + # Create an agent with calendar tools + self.agent = Agent( + role="Calendar Assistant", + goal="Help users access, create, update, and manage their Google Calendar. Coordinate with the Gmail Assistant for tasks requiring email access.", + backstory="You are an expert calendar assistant. You can manage schedules, appointments, and events. You are also aware of a Gmail Assistant and can suggest using it if a user's request involves reading emails (e.g., to find event details sent via email) or sending email confirmations.", + verbose=verbose, + allow_delegation=False, + tools=[ + list_upcoming_events, + search_calendar_events, + get_calendar_details, + create_calendar_event, + update_calendar_event, + delete_calendar_event + ], + llm=llm + ) + + # Define the CrewAI structure + self.crew = Crew( + agents=[self.agent], + tasks=[], + verbose=verbose, + process=Process.sequential, + ) + + async def process_calendar_query(self, prompt: str) -> str: + """ + Run the CrewAI agent to process calendar queries. + + Args: + prompt: The user's calendar-related prompt + + Returns: + The calendar information requested + """ + current_utc_date = datetime.now(timezone.utc).strftime('%Y-%m-%d') + # Metaprompt for collaboration, date awareness, input validation, preventing hallucinations, and conference creation + metaprompt = ( + f"You are the Google Calendar Assistant. Today's UTC date is {current_utc_date}. " + "Your goal is to fulfill the user's request using your tools. You have a tool `create_calendar_event`, which automatically adds a video link (like Google Meet) if `create_conference` is true (default). " + "NEVER invent information, especially personal details like emails. Only use what's given or derived from tool output. " + "Before using any tool, ensure all critical info is present. If anything essential is missing, ASK the user for it—do NOT proceed without it. " + "To create events: you MUST have a summary, start time, and end time. " + "- Attendee emails:\n" + " - If a person is mentioned (e.g., 'Meet with Olga'), assume they should be invited as an attendee.\n" + " - Try to find their email using prior event history or from related emails.\n" + " - If not found, ask the user to provide it using the Task input.required prompt (e.g., 'What is Olga's email address so I can send the invite?').\n" + " - Do NOT invent or use placeholders like 'name@example.com'.\n" + " - If an email looks generic (e.g., olga@example.com), ASK for confirmation before proceeding.\n" + "- Timezones:\n" + " - Do NOT convert times unless told to. Use any provided IANA timezone (e.g., Europe/Paris) as-is.\n" + " - If the time includes a named zone (e.g., '10 AM EST'), map it to IANA (e.g., America/New_York) and use it in the `time_zone` parameter.\n" + " - If time is ISO 8601 with 'Z' or offset, pass it as-is without `time_zone`.\n" + " - If the user gives time without timezone info (e.g., '2 PM'), you MUST ask for an IANA timezone. Never assume UTC.\n" + "- After using `create_calendar_event` or `update_calendar_event`, verify success. Only say it's successful if tool confirms with 'Event created successfully and verified'. Report errors clearly.\n" + "- For updates/deletes: you MUST have `event_id`. If missing, ask the user or suggest listing/searching events.\n" + "- Adding attendees: if only names are given, ask for email addresses. Do NOT proceed with placeholders.\n" + "- If times/dates are vague (e.g., 'evening', 'next Tuesday'), ask for exact info.\n" + "If any of the following is missing: summary, start/end time, valid/confirmed attendee emails, timezone for local time, or event_id (for updates), DO NOT USE ANY TOOL. Ask the user instead. " + "If the request needs info from emails, refer the user to the Gmail Assistant. Only act on calendar tasks if all data is present." + ) + + + + # Check if this is a request to force re-authentication + force_reauth = "force reauth" in prompt.lower() or "re-authenticate" in prompt.lower() + + # If permission error is mentioned, suggest re-authentication + if "permission" in prompt.lower() or "403" in prompt or "insufficient authentication" in prompt: + update_prompt = f"{prompt}\n\nIt seems there might be authentication or permission issues. Try using force_reauth=True to re-authenticate with Google Calendar." + else: + update_prompt = prompt + + # Create a dynamic task based on user input + print(f"Calendar Prompt: {update_prompt}") + task = CrewTask( + description=f"{metaprompt}\\n\\nUser request: {update_prompt}", + expected_output="Retrieved or modified calendar information based on the user's request, or guidance to consult the Gmail assistant.", + agent=self.agent + ) + + # Update crew tasks and execute + self.crew.tasks = [task] + + # Run the crew in an executor to avoid blocking + loop = asyncio.get_event_loop() + result = await loop.run_in_executor(None, self.crew.kickoff) + # Extract string from CrewOutput object + if hasattr(result, 'raw'): + return str(result.raw) + return str(result) + + + +# Set up Elkar A2A server with CrewAI integration +async def setup_server(): + """Main function to set up the Elkar A2A server with CrewAI integration.""" + + # Create the CrewAI wrapper + crew_ai_wrapper = CrewAIWrapper() + + # Create the agent card - using proper AgentSkill objects + agent_card = AgentCard( + name="Google Calendar Assistant", + description="Manages Google Calendar. Can create, update, delete, and list events. Collaborates with the Gmail Assistant for tasks requiring email access (e.g., finding event details in emails).", + url="http://localhost:5002", + version="1.0.0", + skills=[ + AgentSkill(id="calendar-assistant", name="calendar-assistant"), + AgentSkill(id="google-calendar", name="google-calendar"), + AgentSkill(id="event-management", name="event-management"), + AgentSkill(id="scheduling", name="scheduling") + ], + capabilities=AgentCapabilities( + streaming=True, + pushNotifications=True, + stateTransitionHistory=True, + ), + ) + + # Create a simple function to check for placeholder emails in the user's input + def has_placeholder_in_prompt(prompt: str) -> Tuple[bool, str]: + """ + Check if the user's prompt contains any common placeholder emails. + Returns (has_placeholder, message) + """ + for placeholder_domain in ["example.com", "example.org", "test.com"]: + if placeholder_domain in prompt: + return True, f"I notice you're using an email with '{placeholder_domain}' which appears to be a placeholder. Please provide a valid email address instead." + + return False, "" + + # Define the task handler to process requests via CrewAI + async def task_handler( + task: TaskModifier, request_context: RequestContext | None + ) -> None: + """Handle tasks by using the CrewAI wrapper to process calendar requests.""" + + try: + # Access the task message using the pattern from crewai_a2a_server.py + task_obj = task._task + user_prompt = "" + print(f"Task: {task_obj}") + + # Extract text from message parts if they exist + if hasattr(task_obj, 'status') and hasattr(task_obj.status, 'message') and task_obj.status.message and hasattr(task_obj.status.message, 'parts'): + for part in task_obj.status.message.parts: + if hasattr(part, "text") and isinstance(part, TextPart): + user_prompt += part.text + # If no message in status, try to look in history + elif hasattr(task_obj, 'history') and task_obj.history and len(task_obj.history) > 0: + for msg in task_obj.history: + if hasattr(msg, 'parts'): + for part in msg.parts: + if hasattr(part, "text") and isinstance(part, TextPart): + user_prompt += part.text + + print(f"User prompt: {user_prompt}") + if not user_prompt: + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text="No text prompt was provided in the request.")], + ), + ), + is_final=True, + ) + return + + # Simple check for placeholder emails in the user's request + has_placeholder, placeholder_message = has_placeholder_in_prompt(user_prompt) + if has_placeholder: + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text=placeholder_message)], + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + return + + # Check if this is a request to re-authenticate + if "force reauth" in user_prompt.lower() or "re-authenticate" in user_prompt.lower(): + await task.set_status( + TaskStatus( + state=TaskState.WORKING, + message=Message( + role="agent", + parts=[TextPart(text="I'll try to re-authenticate with Google Calendar...")], + ), + ) + ) + + # Try forcing re-authentication first + try: + get_calendar_service(force_reauth=True) + await task.set_status( + TaskStatus( + state=TaskState.WORKING, + message=Message( + role="agent", + parts=[TextPart(text="Successfully re-authenticated with Google Calendar. Now processing your request...")], + ), + ) + ) + except Exception as e: + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text=f"Failed to re-authenticate: {str(e)}")], + ), + ), + is_final=True, + ) + return + else: + # Send initial status update + await task.set_status( + TaskStatus( + state=TaskState.WORKING, + message=Message( + role="agent", + parts=[TextPart(text="I'm processing your calendar request...")], + ), + ) + ) + + # Add a simple warning about placeholder emails + user_prompt += "\n\nNever use placeholder email domains like example.com." + + # Call CrewAI to process calendar query + result = await crew_ai_wrapper.process_calendar_query(user_prompt) + + # Check for error indicators in the response + if any(indicator in result.lower() for indicator in ["http error", "api error", "permission error"]): + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text=f"There was an issue with the calendar request: {result}")], + ), + ), + is_final=True, + ) + return + + # Check for placeholder domains in the result + if any(domain in result for domain in ["example.com", "example.org", "test.com"]): + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text="I notice there's a placeholder email address in the request. Please provide a valid email address.")], + ), + ), + is_final=False, + ) + return + + # Check if the agent is asking for more information + if result.strip().endswith("?") or any(phrase in result.lower() for phrase in ["please provide", "do you mean", "clarify"]): + await task.set_status( + TaskStatus( + state=TaskState.INPUT_REQUIRED, + message=Message( + role="agent", + parts=[TextPart(text=result)], # Pass the agent's question back + ), + ), + is_final=False, # Mark as not final, expecting more input from user + ) + else: + # Agent provided a final response or took an action + await task.upsert_artifacts( + [ + Artifact( + parts=[TextPart(text=result)], + index=0, + ) + ] + ) + + # Mark the task as completed + await task.set_status( + TaskStatus( + state=TaskState.COMPLETED, + message=Message( + role="agent", + parts=[TextPart(text="Calendar request processed.")], # Generic message, artifact has details + ), + ), + is_final=True, + ) + + except Exception as e: + # Handle any errors + error_message = f"Error processing calendar request: {str(e)}" + await task.set_status( + TaskStatus( + state=TaskState.FAILED, + message=Message( + role="agent", + parts=[TextPart(text=f"Error processing request: {str(e)}")], + ), + ), + is_final=True, + ) + + # Create the task manager with the handler + task_manager = TaskManagerWithModifier( + agent_card, send_task_handler=task_handler + ) + + # Configure the ElkarClientStore + api_key = os.environ.get("ELKAR_API_KEY", "") # Get API key from environment variables + if not api_key: + print("Warning: ELKAR_API_KEY not found in environment variables.") + store = ElkarClientStore(base_url="https://api.elkar.co/api", api_key=api_key) + + task_manager: TaskManagerWithModifier = TaskManagerWithModifier( + agent_card, + send_task_handler=task_handler, + store=store # Pass the configured store to the task manager + ) + + server = A2AServer(task_manager, host="0.0.0.0", port=5002, endpoint="/") + return server + + +if __name__ == "__main__": + # Set up and run the server using uvicorn directly, avoiding asyncio conflict + server = asyncio.run(setup_server()) + print("Starting Elkar A2A server on http://localhost:5002") + print("Press Ctrl+C to stop the server") + + # Run with uvicorn directly instead of server.start() + uvicorn.run(server.app, host="0.0.0.0", port=5002) \ No newline at end of file diff --git a/example/server_mcp.py b/example/server_mcp.py new file mode 100644 index 0000000..b0d8270 --- /dev/null +++ b/example/server_mcp.py @@ -0,0 +1,337 @@ +from mcp.server import Server +from mcp.server.models import InitializationOptions +import mcp.types as types +from typing import List, Dict, Any, Optional, Union +from dataclasses import dataclass +from mcp.server.fastmcp import FastMCP, Context +import os +import json +import datetime +from dotenv import load_dotenv +from pydantic import AnyUrl +import mcp.server.stdio +import logging +from collections.abc import AsyncIterator +from contextlib import asynccontextmanager +import asyncpg +import urllib.parse +import uuid +# Add Elkar A2A client imports +from elkar.a2a_types import Message, TextPart, TaskSendParams, Task, TaskState +from elkar.client.a2a_client import A2AClient, A2AClientConfig + + +load_dotenv() + +settings = dict( + debug=True, + log_level="DEBUG", + warn_on_duplicate_resources=True, + warn_on_duplicate_tools=True, + warn_on_duplicate_prompts=True, +) + + +mcp = FastMCP( + "A2A com using elkar", + instructions="""You are a helpful assistant.""", + # lifespan=breezy_lifespan +) + +logging.basicConfig(level=logging.INFO) + +logger = logging.getLogger(__name__) + + +@mcp.tool() +async def send_task_to_a2a_agent( + ctx: Context, + agent_url: str, + task_message: str, + task_id: Optional[str] = None, + headers: Optional[Dict[str, str]] = None, + timeout: int = 300, + metadata: Optional[Dict[str, Any]] = None +) -> Dict[str, Any]: + """Send a task to an external Elkar A2A agent and retrieve the results. + Be careful, task are completely stateless, and therefore they are independent and the agent will not remember the context of previous interaction. + + Args: + agent_url: The base URL of the A2A agent to call (e.g., 'http://localhost:5001') + task_message: The content of the task to send to the agent + task_id: Optional custom task ID (defaults to auto-generated UUID) + headers: Optional HTTP headers for authentication or other purposes + timeout: Timeout in seconds (default: 300) + metadata: Optional metadata to include with the task + + Returns: + The response from the A2A agent including task status, artifacts, and any error information + """ + try: + # Configure the A2A client + config = A2AClientConfig( + base_url=agent_url, + headers=headers, + timeout=timeout + ) + + # Create message from input text + message = Message( + role="user", + parts=[TextPart(text=task_message)] + ) + + # Prepare task parameters - use model_dump to serialize properly + task_params = TaskSendParams( + id=task_id or str(uuid.uuid4()), # Will generate a UUID if None + message=message, + metadata=metadata or {}, + sessionId="1" + ) + + logger.debug(f"Sending task parameters: {json.dumps(task_params.model_dump())}") + + # Use the client as a context manager for proper resource management + async with A2AClient(config) as client: + # Get the agent card to verify connection + try: + agent_card = await client.get_agent_card() + agent_info = { + "name": agent_card.name, + "description": agent_card.description, + "version": agent_card.version + } + except Exception as e: + return { + "error": f"Failed to connect to A2A agent: {str(e)}", + "status": "connection_error" + } + + # Send the task + try: + # Modified task_params before sending: + if hasattr(task_params, "model_dump"): + serialized_params = task_params.model_dump(mode='json') + else: + # Fall back to dict conversion + serialized_params = task_params.__dict__ + response = await client.send_task(task_params) # Use the original task_params object, not the serialized dict + logger.debug(f"Response: {response}") + # Handle error response + if response.error: + + return { + "error": response.error.message, + "status": "task_error", + "agent_info": agent_info + } + + # Handle successful response + task = response.result + if task is None: + return { + "error": "No task received from A2A agent", + "status": "task_error", + "agent_info": agent_info + } + logger.debug(f"Task: {task}") + result = { + "task_id": task.id, + "status": task.status.state.value if task.status and task.status.state else "unknown", + "agent_info": agent_info, + } + + # Include the message from the status if available + if task.status and task.status.message: + result["message"] = " ".join([ + part.text for part in task.status.message.parts + if hasattr(part, "text") + ]) + + # Include artifacts if available + if task.artifacts and len(task.artifacts) > 0: + artifacts_content = [] + for artifact in task.artifacts: + artifact_text = " ".join([ + part.text for part in artifact.parts + if hasattr(part, "text") + ]) + artifacts_content.append(artifact_text) + + result["artifacts"] = artifacts_content + + return result + + except Exception as e: + return { + "error": f"Error while sending task: {str(e)}", + "status": "request_error", + "agent_info": agent_info + } + + except Exception as e: + return { + "error": f"Failed to initialize A2A client: {str(e)}", + "status": "client_error" + } + +@mcp.tool() +async def get_a2a_agent_card( + ctx: Context, + agent_url: str, + timeout: int = 10, + headers: Optional[Dict[str, str]] = None +) -> Dict[str, Any]: + """Retrieve the agent card from a specific Elkar A2A agent. + + Args: + agent_url: URL of the A2A agent (e.g., 'http://localhost:5001') + timeout: Connection timeout in seconds (default: 10) + headers: Optional HTTP headers for authentication or other purposes + + Returns: + Agent card information or error details + """ + try: + # Configure the A2A client + config = A2AClientConfig( + base_url=agent_url, + headers=headers, + timeout=timeout + ) + + # Use the client as a context manager for proper resource management + async with A2AClient(config) as client: + try: + # Retrieve the agent card + agent_card = await client.get_agent_card() + + # Extract and return comprehensive information + result = { + "status": "success", + "url": agent_url, + "name": agent_card.name, + "description": agent_card.description, + "version": agent_card.version, + } + + # Include capabilities + if hasattr(agent_card, "capabilities"): + result["capabilities"] = {} + capabilities = agent_card.capabilities + + if hasattr(capabilities, "streaming"): + result["capabilities"]["streaming"] = capabilities.streaming + + if hasattr(capabilities, "pushNotifications"): + result["capabilities"]["pushNotifications"] = capabilities.pushNotifications + + if hasattr(capabilities, "stateTransitionHistory"): + result["capabilities"]["stateTransitionHistory"] = capabilities.stateTransitionHistory + + # Include skills + if hasattr(agent_card, "skills") and agent_card.skills: + result["skills"] = [ + {"id": skill.id, "name": skill.name} + for skill in agent_card.skills + ] + + return result + + except Exception as e: + return { + "status": "error", + "url": agent_url, + "error": f"Error retrieving agent card: {str(e)}" + } + + except Exception as e: + return { + "status": "error", + "url": agent_url, + "error": f"Failed to connect to A2A agent: {str(e)}" + } + +@mcp.tool() +async def discover_a2a_agents( + ctx: Context, + agent_urls: Optional[List[str]] = None, + timeout: int = 10, + headers: Optional[Dict[str, str]] = None +) -> Dict[str, Any]: + """Describe the available Elkar A2A agents and their capabilities. + + Args: + agent_urls: Optional list of URLs to check for A2A agents. If not provided, uses AGENT_URLS from environment. + timeout: Connection timeout in seconds (default: 10) + headers: Optional HTTP headers for authentication or other purposes + + Returns: + Dictionary with available agents and their capabilities + """ + # If agent_urls not provided, use environment variable + if agent_urls is None: + # Get from environment and split by comma + env_urls = os.environ.get('AGENT_URLS', 'http://localhost:5001') + agent_urls = [url.strip() for url in env_urls.split(',')] + + results = { + "available_agents": [], + "errors": [] + } + + for url in agent_urls: + try: + # Configure the A2A client for this URL + config = A2AClientConfig( + base_url=url, + headers=headers, + timeout=timeout + ) + + # Use the client as a context manager for proper resource management + async with A2AClient(config) as client: + try: + # Retrieve the agent card + agent_card = await client.get_agent_card() + + # Extract key information from the agent card + agent_info = { + "url": url, + "name": agent_card.name, + "description": agent_card.description, + "version": agent_card.version, + "capabilities": { + "streaming": agent_card.capabilities.streaming if hasattr(agent_card.capabilities, "streaming") else False, + "pushNotifications": agent_card.capabilities.pushNotifications if hasattr(agent_card.capabilities, "pushNotifications") else False, + "stateTransitionHistory": agent_card.capabilities.stateTransitionHistory if hasattr(agent_card.capabilities, "stateTransitionHistory") else False, + } + } + + # Include skills information if available + if hasattr(agent_card, "skills") and agent_card.skills: + agent_info["skills"] = [ + {"id": skill.id, "name": skill.name} + for skill in agent_card.skills + ] + + results["available_agents"].append(agent_info) + + except Exception as e: + # Add to errors if we can connect but can't get the agent card + results["errors"].append({ + "url": url, + "error": f"Error retrieving agent card: {str(e)}" + }) + + except Exception as e: + # Add to errors if we can't even connect to the URL + results["errors"].append({ + "url": url, + "error": f"Failed to connect to A2A agent: {str(e)}" + }) + + return results + +if __name__ == "__main__": + mcp.run("stdio") \ No newline at end of file diff --git a/src/elkar/client/a2a_client.py b/src/elkar/client/a2a_client.py index d3155ab..3480c09 100644 --- a/src/elkar/client/a2a_client.py +++ b/src/elkar/client/a2a_client.py @@ -73,7 +73,7 @@ async def _make_request( url = f"{self.config.base_url.rstrip('/')}" if endpoint: url = f"{url}/{endpoint.lstrip('/')}" - serialized_data = data.model_dump() if data else None + serialized_data = data.model_dump(exclude_none=True) if data else None async with self._session.request(method, url, json=serialized_data) as response: response.raise_for_status() return await response.json() @@ -110,7 +110,7 @@ async def send_task_streaming(self, task_params: TaskSendParams) -> AsyncIterabl if not self._session: raise RuntimeError("Client session not initialized. Use 'async with' context manager.") - async with self._session.post(self.config.base_url, json=request) as response: + async with self._session.post(self.config.base_url, json=request.model_dump(exclude_none=True)) as response: response.raise_for_status() return self._stream_response(response) @@ -141,7 +141,7 @@ async def resubscribe_to_task(self, task_params: TaskIdParams) -> AsyncIterable[ if not self._session: raise RuntimeError("Client session not initialized. Use 'async with' context manager.") - async with self._session.post(self.config.base_url, json=request) as response: + async with self._session.post(self.config.base_url, json=request.model_dump(exclude_none=True)) as response: response.raise_for_status() return self._stream_response(response) From 0f5c21ac2d2eca21da0f9d80c90869a2050b8d26 Mon Sep 17 00:00:00 2001 From: Matthieu M Date: Mon, 19 May 2025 17:00:01 +0200 Subject: [PATCH 2/6] Enhance documentation for calendar and email functions in server_cal.py and server.py. Improve descriptions, argument details, and return values for better clarity. Remove unused server_mcp.py file. --- example/server.py | 65 +++++++--- example/server_cal.py | 178 +++++++++++++++++++++------ {example => src/elkar}/server_mcp.py | 98 +++++++++++---- 3 files changed, 260 insertions(+), 81 deletions(-) rename {example => src/elkar}/server_mcp.py (67%) diff --git a/example/server.py b/example/server.py index 5a287e5..dc200a3 100644 --- a/example/server.py +++ b/example/server.py @@ -117,12 +117,23 @@ def get_gmail_service(force_reauth: bool = False): @tool def read_emails(max_results: int = 10, force_reauth: bool = False) -> str: """ - Reads a specified number of emails from the user's inbox. + Retrieves a list of recent emails from the user's primary Gmail inbox. + + This function connects to the Gmail API, fetches the most recent emails + (up to a specified maximum number), and extracts key information such as + sender, subject, date, and a snippet of the email body. + Args: - max_results: Maximum number of emails to retrieve - force_reauth: Whether to force re-authentication + max_results: The maximum number of emails to retrieve. Defaults to 10. + This helps control the amount of data returned and API usage. + force_reauth: If True, forces the Gmail API to re-authenticate by deleting + any existing token. This is useful if there are persistent + authentication issues. Defaults to False. + Returns: - A string with email information + A string containing the formatted information for each retrieved email, + separated by '---'. If no messages are found, it returns "No messages found.". + In case of an API error or other exception, it returns an error message. """ try: service = get_gmail_service(force_reauth=force_reauth) @@ -158,13 +169,26 @@ def read_emails(max_results: int = 10, force_reauth: bool = False) -> str: @tool def search_emails(query: str, max_results: int = 10, force_reauth: bool = False) -> str: """ - Searches emails based on a Gmail search query. + Searches the user's Gmail inbox for emails matching a specific query. + + This function utilizes the Gmail API's search capabilities to find emails + that match the provided search query string (e.g., 'from:boss@example.com', + 'subject:project update'). It then retrieves and formats details for + each matching email, including sender, subject, date, and a snippet. + Args: - query: Gmail search query (e.g., 'from:example@gmail.com') - max_results: Maximum number of results to return - force_reauth: Whether to force re-authentication + query: The Gmail search query string. This uses standard Gmail search + operators (e.g., 'from:', 'to:', 'subject:', 'is:unread'). + max_results: The maximum number of matching emails to retrieve. + Defaults to 10. + force_reauth: If True, forces the Gmail API to re-authenticate. + Defaults to False. + Returns: - A string with matching email information + A string containing the formatted information for each matching email, + separated by '---'. If no messages match the query, it returns + "No messages found matching query: ". In case of an API error + or other exception, it returns an error message. """ try: service = get_gmail_service(force_reauth=force_reauth) @@ -200,14 +224,25 @@ def search_emails(query: str, max_results: int = 10, force_reauth: bool = False) @tool def send_email(to: str, subject: str, body: str, force_reauth: bool = False) -> str: """ - Sends an email to a specified recipient with a given subject and body. + Composes and sends an email using the user's Gmail account. + + This function takes the recipient's email address, the subject line, and + the plain text body of the email. It then constructs a MIME message and + uses the Gmail API to send the email. Line breaks in the body are preserved. + Args: - to: The email address of the recipient. - subject: The subject of the email. - body: The plain text body of the email. - force_reauth: Whether to force re-authentication. + to: The email address of the recipient (e.g., 'recipient@example.com'). + subject: The subject line of the email. + body: The plain text content of the email. Line breaks (e.g., '\n') + will be rendered as new lines in the sent email. + force_reauth: If True, forces the Gmail API to re-authenticate. + Defaults to False. + Returns: - A string confirming the email was sent or an error message. + A string confirming that the email was sent successfully, including the + Message ID, (e.g., "Email sent successfully to . Message ID: "). + If an error occurs during the process (e.g., API error, invalid email + format), it returns an error message detailing the issue. """ try: import email.mime.text diff --git a/example/server_cal.py b/example/server_cal.py index dc55d5c..c33ce60 100644 --- a/example/server_cal.py +++ b/example/server_cal.py @@ -99,13 +99,29 @@ def get_calendar_service(force_reauth: bool = False): @tool def list_upcoming_events(max_results: int = 10, time_min_days: int = 0, force_reauth: bool = False) -> str: """ - Lists upcoming events from the user's primary calendar. + Retrieves a list of upcoming events from the user's primary Google Calendar. + + This function queries the Google Calendar API for events starting from a specified + number of days from the current moment (or today if `time_min_days` is 0). + It fetches event details such as summary (title), start time, location, + description, and event ID. The start time is formatted for readability. + Descriptions longer than 100 characters are truncated. + Args: - max_results: Maximum number of events to retrieve - time_min_days: Number of days from now to start looking for events (0 = today) - force_reauth: Whether to force re-authentication + max_results: The maximum number of upcoming events to retrieve. + Defaults to 10. + time_min_days: The number of days from the current time to start listing + events. For example, 0 means starting from today, + 1 means starting from tomorrow. Defaults to 0. + force_reauth: If True, forces the Google Calendar API to re-authenticate + by deleting any existing token. Useful for resolving + authentication issues. Defaults to False. + Returns: - A string with upcoming event information + A string containing formatted information for each upcoming event, + separated by '---'. If no events are found, it returns + "No upcoming events found.". In case of an API error or other + exception, it returns an error message. """ try: service = get_calendar_service(force_reauth=force_reauth) @@ -168,13 +184,27 @@ def list_upcoming_events(max_results: int = 10, time_min_days: int = 0, force_re @tool def search_calendar_events(query: str, max_results: int = 10, force_reauth: bool = False) -> str: """ - Searches for events in the user's primary calendar based on a query. + Searches for events in the user's primary Google Calendar that match a given query. + + This function uses the Google Calendar API to find events based on a textual query. + It looks for matches in event titles, descriptions, attendees, etc. For each + matching event, it retrieves and formats details like summary, start time, + location, description, and event ID. Start times are formatted for clarity, + and long descriptions are truncated. + Args: - query: Search query (e.g., 'meeting', 'dinner', etc.) - max_results: Maximum number of events to retrieve - force_reauth: Whether to force re-authentication + query: The search term or query string to use for finding events. + For example, "meeting with John" or "dentist appointment". + max_results: The maximum number of matching events to retrieve. + Defaults to 10. + force_reauth: If True, forces the Google Calendar API to re-authenticate. + Defaults to False. + Returns: - A string with matching event information + A string containing formatted information for each matching event, + separated by '---'. If no events match the query, it returns + "No events found matching query: ". In case of an API error + or other exception, it returns an error message. """ try: service = get_calendar_service(force_reauth=force_reauth) @@ -234,12 +264,24 @@ def search_calendar_events(query: str, max_results: int = 10, force_reauth: bool @tool def get_calendar_details(days: int = 7, force_reauth: bool = False) -> str: """ - Gets detailed information about the user's primary calendar and upcoming events. + Retrieves general details about the user's primary Google Calendar and a summary of upcoming events. + + This function fetches metadata about the primary calendar, including its name, + ID, time zone, and description. It also calculates and returns the number + of events scheduled within a specified number of upcoming days. + Args: - days: Number of days to look ahead for events - force_reauth: Whether to force re-authentication + days: The number of days into the future to look for counting events. + For example, a value of 7 will count events in the next 7 days. + Defaults to 7. + force_reauth: If True, forces the Google Calendar API to re-authenticate. + Defaults to False. + Returns: - A string with calendar information + A string containing the calendar's name, ID, time zone, description, + and the count of events in the specified upcoming period. Each piece + of information is on a new line. In case of an API error or other + exception, it returns an error message. """ try: service = get_calendar_service(force_reauth=force_reauth) @@ -280,12 +322,21 @@ def get_calendar_details(days: int = 7, force_reauth: bool = False) -> str: @tool def delete_calendar_event(event_id: str, force_reauth: bool = False) -> str: """ - Deletes an event from the user's primary calendar. + Deletes a specific event from the user's primary Google Calendar. + + This function uses the Google Calendar API to remove an event identified by its unique ID. + Args: - event_id: ID of the event to delete - force_reauth: Whether to force re-authentication + event_id: The unique identifier of the calendar event to be deleted. + This ID can typically be obtained from functions like + `list_upcoming_events` or `search_calendar_events`. + force_reauth: If True, forces the Google Calendar API to re-authenticate. + Defaults to False. + Returns: - A confirmation message + A string confirming the successful deletion of the event, e.g., + "Event with ID deleted successfully!". If an error occurs + (e.g., event not found, permission issues), it returns an error message. """ try: service = get_calendar_service(force_reauth=force_reauth) @@ -374,19 +425,45 @@ def create_calendar_event(summary: str, start_time: str, end_time: str, create_conference: bool = True, force_reauth: bool = False) -> str: """ - Creates a new event in the user's primary calendar. + Creates a new event in the user's primary Google Calendar with specified details. + + This function allows for the creation of a new calendar event, including its title (summary), + start and end times, description, location, and attendees. It can also automatically + generate a Google Meet conference link for the event. + + Time Handling: + - If `time_zone` is provided (e.g., 'Europe/Paris'), `start_time` and `end_time` should be + in 'YYYY-MM-DDTHH:MM:SS' format and are considered local to that timezone. + - If `time_zone` is NOT provided, `start_time` and `end_time` MUST be in full ISO 8601 + format with a UTC offset (e.g., '2023-10-26T10:00:00-07:00') or 'Z' for UTC + (e.g., '2023-10-26T17:00:00Z'). + - For all-day events, `start_time` and `end_time` should be in 'YYYY-MM-DD' format, + and `time_zone` is not applicable in the same way (the event spans the whole day + regardless of timezone for display, but Google Calendar handles the underlying UTC). + Args: - summary: Title of the event. - start_time: Start time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD'. - end_time: End time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD'. - description: Optional description of the event. - location: Optional location of the event. - time_zone: Optional IANA time zone name (e.g., 'Europe/Paris', 'America/New_York'). If provided, start_time and end_time are considered local to this timezone. Otherwise, they must include timezone information (offset or 'Z'). - attendees: Optional list of email addresses for attendees to invite. - create_conference: Whether to automatically create a video conference link for the event (defaults to True). - force_reauth: Whether to force re-authentication. + summary: The title or summary of the event (e.g., "Team Meeting"). + start_time: The start date/time of the event. Format depends on whether `time_zone` + is provided and if it's an all-day event. See Time Handling notes. + end_time: The end date/time of the event. Format follows the same rules as `start_time`. + description: An optional detailed description for the event. + location: An optional location for the event (e.g., "Conference Room 4" or an address). + time_zone: Optional. The IANA time zone name for `start_time` and `end_time` + (e.g., 'America/New_York', 'Europe/London'). If not provided, times must + include offset information. + attendees: An optional list of email addresses of people to invite to the event. + (e.g., ['user1@example.com', 'user2@example.com']). + create_conference: If True (default), attempts to create a Google Meet conference + link for the event. Set to False to not create one. + force_reauth: If True, forces the Google Calendar API to re-authenticate. + Defaults to False. + Returns: - A string with the created event information. + A string confirming the successful creation of the event, including its ID, a web link + to the event, and a conference link if generated (e.g., "Event created successfully and verified!\nID: \nLink: \nConference Link: "). + If `create_conference` was True but no link was generated, a note is added. + Returns an error message if the event creation fails due to API errors, permission + issues, invalid parameters, or event conflicts. """ try: service = get_calendar_service(force_reauth=force_reauth) @@ -504,20 +581,39 @@ def update_calendar_event(event_id: str, summary: str = "", start_time: str = "" attendees_to_remove: Optional[List[str]] = None, force_reauth: bool = False) -> str: """ - Updates an existing event in the user's primary calendar. Can update details and/or attendees. + Updates an existing event in the user's primary Google Calendar. + + This function allows modification of various properties of an existing calendar event, + identified by its `event_id`. Fields that are not provided or are empty strings (for textual + fields) will not be updated. Attendee lists can be modified by providing lists of emails + to add or remove. + + Time Handling for Updates: + - Similar to `create_calendar_event`, if `start_time` or `end_time` are being updated AND + `time_zone` is provided, the times are local to that zone. + - If `time_zone` is not provided when updating times, the new `start_time` and `end_time` + must include UTC offset or 'Z'. + - To convert an event to all-day or change an all-day event's date, provide `start_time` + and `end_time` in 'YYYY-MM-DD' format. + Args: - event_id: ID of the event to update. - summary: New title of the event (optional). - start_time: New start time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD' (optional). - end_time: New end time in 'YYYY-MM-DDTHH:MM:SS' format (if time_zone is provided) or full ISO 8601 format with offset/Z (if time_zone is not provided). For all-day events, use 'YYYY-MM-DD' (optional). - description: New description of the event (optional). - location: New location of the event (optional). - time_zone: Optional IANA time zone name (e.g., 'Europe/Paris'). If provided and start/end times are being updated, these times are local to this zone. - attendees_to_add: Optional list of email addresses for attendees to add to the event. - attendees_to_remove: Optional list of email addresses for attendees to remove from the event. - force_reauth: Whether to force re-authentication. + event_id: The unique ID of the event to be updated. + summary: Optional. The new title for the event. If empty, not updated. + start_time: Optional. The new start date/time. See Time Handling notes. If empty, not updated. + end_time: Optional. The new end date/time. See Time Handling notes. If empty, not updated. + description: Optional. The new description for the event. If empty, not updated. + location: Optional. The new location for the event. If empty, not updated. + time_zone: Optional. The IANA time zone for updated `start_time` and `end_time`. + attendees_to_add: Optional. A list of email addresses to add as attendees. + attendees_to_remove: Optional. A list of email addresses to remove from attendees. + force_reauth: If True, forces the Google Calendar API to re-authenticate. + Defaults to False. + Returns: - A string with the updated event information or an error message. + A string confirming the successful update of the event, including its ID and web link + (e.g., "Event updated successfully!\nID: \nLink: "). + Returns an error message if the update fails (e.g., event not found, API error, + permission issues). """ try: service = get_calendar_service(force_reauth=force_reauth) diff --git a/example/server_mcp.py b/src/elkar/server_mcp.py similarity index 67% rename from example/server_mcp.py rename to src/elkar/server_mcp.py index b0d8270..b9cae49 100644 --- a/example/server_mcp.py +++ b/src/elkar/server_mcp.py @@ -53,19 +53,35 @@ async def send_task_to_a2a_agent( timeout: int = 300, metadata: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: - """Send a task to an external Elkar A2A agent and retrieve the results. - Be careful, task are completely stateless, and therefore they are independent and the agent will not remember the context of previous interaction. - + """Sends a task to a specified Elkar A2A (Agent-to-Agent) compliant agent and awaits its response. + + This function facilitates communication with external AI agents that adhere to the Elkar A2A protocol. + It constructs a task with the given message and parameters, sends it to the target agent's URL, + and then processes the agent's response. The interaction is stateless, meaning each call is independent, + and the remote agent does not retain context from previous calls unless explicitly managed within the task itself. + + The function first attempts to retrieve the agent's 'card' (a summary of its capabilities and identity) + to ensure connectivity and gather basic information. If successful, it proceeds to send the actual task. + The response from the agent, which includes the task's final status, any generated artifacts (results), + and potential error messages, is then parsed and returned. + Args: - agent_url: The base URL of the A2A agent to call (e.g., 'http://localhost:5001') - task_message: The content of the task to send to the agent - task_id: Optional custom task ID (defaults to auto-generated UUID) - headers: Optional HTTP headers for authentication or other purposes - timeout: Timeout in seconds (default: 300) - metadata: Optional metadata to include with the task - + ctx: The MCP (Multi-Capability Platform) context object, providing access to platform features. + agent_url: The complete base URL of the target Elkar A2A agent (e.g., 'http://localhost:5001'). + task_message: The primary content or instruction for the task to be performed by the remote agent. + task_id: An optional custom identifier for the task. If not provided, a unique UUID will be automatically generated. + headers: Optional dictionary of HTTP headers to include in the request, typically for authentication (e.g., API keys) or custom routing. + timeout: The maximum time in seconds to wait for a response from the A2A agent. Defaults to 300 seconds (5 minutes). + metadata: Optional dictionary of arbitrary key-value pairs to be sent along with the task, which the remote agent might use. + Returns: - The response from the A2A agent including task status, artifacts, and any error information + A dictionary containing the outcome of the task interaction. This includes: + - 'task_id': The ID of the task. + - 'status': The final state of the task (e.g., 'completed', 'failed', 'input_required'). + - 'agent_info': Basic information about the agent (name, description, version). + - 'message': (Optional) A message from the agent related to the task's status. + - 'artifacts': (Optional) A list of strings, where each string is the content of an artifact produced by the agent. + - 'error': (Optional) An error message if any part of the process failed (e.g., connection error, task error, client initialization error). """ try: # Configure the A2A client @@ -182,15 +198,32 @@ async def get_a2a_agent_card( timeout: int = 10, headers: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: - """Retrieve the agent card from a specific Elkar A2A agent. - + """Retrieves and returns the 'Agent Card' from a specified Elkar A2A agent. + + The Agent Card provides metadata about an A2A agent, including its name, description, version, + capabilities (like streaming support, push notifications, state history), and the skills it offers. + This function is useful for discovering an agent's functionalities before sending it tasks. + Args: - agent_url: URL of the A2A agent (e.g., 'http://localhost:5001') - timeout: Connection timeout in seconds (default: 10) - headers: Optional HTTP headers for authentication or other purposes - + ctx: The MCP context object. + agent_url: The base URL of the Elkar A2A agent from which to fetch the card (e.g., 'http://localhost:5001'). + timeout: The maximum time in seconds to wait for a response from the agent. Defaults to 10 seconds. + headers: Optional dictionary of HTTP headers to include in the request, often used for authentication. + Returns: - Agent card information or error details + A dictionary containing the agent card information upon success, or error details upon failure. + Successful response structure: + - 'status': 'success' + - 'url': The agent_url queried. + - 'name': Name of the agent. + - 'description': Description of the agent. + - 'version': Version of the agent. + - 'capabilities': (Optional) Dictionary of agent capabilities (streaming, pushNotifications, stateTransitionHistory). + - 'skills': (Optional) List of skills, each with 'id' and 'name'. + Error response structure: + - 'status': 'error' + - 'url': The agent_url queried. + - 'error': A message detailing the error (e.g., connection failure, error retrieving card). """ try: # Configure the A2A client @@ -259,15 +292,30 @@ async def discover_a2a_agents( timeout: int = 10, headers: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: - """Describe the available Elkar A2A agents and their capabilities. - + """Discovers available Elkar A2A agents by querying a list of URLs and summarizing their capabilities. + + This function iterates through a provided list of agent URLs (or a default list from environment variables) + and attempts to retrieve the Agent Card from each. It compiles a report of successfully contacted agents + along with their key information (name, description, version, capabilities, skills) and a list of any + URLs that resulted in errors (e.g., connection failed, agent card not retrieved). + + This is useful for dynamically finding and understanding the A2A agents available in a network. + Args: - agent_urls: Optional list of URLs to check for A2A agents. If not provided, uses AGENT_URLS from environment. - timeout: Connection timeout in seconds (default: 10) - headers: Optional HTTP headers for authentication or other purposes - + ctx: The MCP context object. + agent_urls: An optional list of base URLs for the Elkar A2A agents to discover. + If not provided, it defaults to the URLs specified in the `AGENT_URLS` + environment variable (comma-separated). + timeout: The maximum time in seconds to wait for a response from each agent. Defaults to 10 seconds. + headers: Optional dictionary of HTTP headers to include in requests, typically for authentication. + Returns: - Dictionary with available agents and their capabilities + A dictionary with two keys: + - 'available_agents': A list of dictionaries, where each dictionary represents a successfully + contacted agent and contains its 'url', 'name', 'description', 'version', + 'capabilities' (streaming, pushNotifications, stateTransitionHistory), and 'skills'. + - 'errors': A list of dictionaries, where each entry details an error encountered while trying + to contact an agent, including the 'url' and an 'error' message. """ # If agent_urls not provided, use environment variable if agent_urls is None: From d357575d064037a266669ae45d4c8bcdfef770a2 Mon Sep 17 00:00:00 2001 From: Matthieu M Date: Tue, 20 May 2025 17:01:23 +0200 Subject: [PATCH 3/6] Update .gitignore to exclude sensitive credential files and enhance server_cal.py to allow event retrieval within a specified date range. Refactor function names and improve documentation for clarity. Update server.py to include detailed descriptions for email assistant skills. --- .gitignore | 3 + example/claude_desktop_config.json | 4 +- example/server.py | 37 +++++++++-- example/server_cal.py | 103 +++++++++++++++++++++-------- src/elkar/cli.py | 74 +++++++++++++++++++++ src/elkar/server_mcp.py | 23 +++---- 6 files changed, 195 insertions(+), 49 deletions(-) create mode 100644 src/elkar/cli.py diff --git a/.gitignore b/.gitignore index 00e9338..7e50e63 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ *.DS_Store sandbox/ dist/ +example/credentials.json +example/gmail_token.json +example/token.json diff --git a/example/claude_desktop_config.json b/example/claude_desktop_config.json index 079fea8..e4c0253 100644 --- a/example/claude_desktop_config.json +++ b/example/claude_desktop_config.json @@ -1,9 +1,7 @@ "a2a_elkar": { "command": "/opt/anaconda3/bin/python", - "args": ["/Users/mm/elkar-a2a/example/server_mcp.py"], + "args": ["path to your server_mcp.py"], "env": { - "ANTHROPIC_API_KEY": "sk-xxx", - "OPENAI_API_KEY": "sk-yyy", "AGENT_URLS": "http://localhost:5001,http://localhost:5002" } }, diff --git a/example/server.py b/example/server.py index dc200a3..e571b86 100644 --- a/example/server.py +++ b/example/server.py @@ -15,6 +15,7 @@ # pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib from crewai import Agent, Task as CrewTask, Crew, Process from langchain_openai import ChatOpenAI +# from crewai.tools import tool from langchain_core.tools import tool from dotenv import load_dotenv import uvicorn @@ -391,8 +392,36 @@ async def setup_server(): url="http://localhost:5001", version="1.0.0", skills=[ - AgentSkill(id="email-assistant", name="email-assistant"), - AgentSkill(id="gmail-integration", name="gmail-integration") + AgentSkill( + id="email-assistant", + name="email-assistant", + description="Core capability for managing Gmail tasks through natural language. Understands requests to read, search, draft, and send emails. Interacts with users to clarify details and confirm actions before execution, especially for sending emails. Defers calendar-related tasks to the Google Calendar Assistant.", + tags=["email", "gmail", "assistant", "natural language processing", "communication", "inbox management", "email composition"], + examples=[ + "Read my latest unread emails.", + "Search for emails from 'marketing@example.com' about 'new campaign'.", + "Draft an email to 'john.doe@example.com' with subject 'Project Update' and body 'The project is on track.'", + "Can you send the draft I just made?", + "What did Sarah say about the meeting notes?" + ], + inputModes=["text"], + outputModes=["text"] + ), + AgentSkill( + id="gmail-integration", + name="gmail-integration", + description="Provides the technical interface to the Gmail API. Manages authentication, executes commands to search mailboxes, retrieve email content (threads and individual messages), and send emails. Ensures secure and reliable communication with Google's Gmail services.", + tags=["gmail", "api", "google api", "email service", "integration", "authentication", "email search", "email retrieval", "email sending"], + examples=[ + "Execute API call to list emails matching 'subject:Urgent'.", + "Retrieve full content of email thread ID 'xyz123'.", + "Use Gmail API to send an email to 'recipient@example.com'.", + "Handle Gmail API authentication flow.", + "Force re-authentication with Gmail." + ], + inputModes=["text"], # Represents instructions that translate to API calls + outputModes=["text", "json"] # API responses, status messages, or structured email data + ) ], capabilities=AgentCapabilities( streaming=True, @@ -724,8 +753,8 @@ async def task_handler( if __name__ == "__main__": # Set up and run the server using uvicorn directly, avoiding asyncio conflict - load_dotenv() - print(os.getenv("OPENAI_API_KEY")) + # load_dotenv() + # print(os.getenv("OPENAI_API_KEY")) server = asyncio.run(setup_server()) print("Starting Elkar A2A server on http://localhost:5001") diff --git a/example/server_cal.py b/example/server_cal.py index c33ce60..96db649 100644 --- a/example/server_cal.py +++ b/example/server_cal.py @@ -97,46 +97,44 @@ def get_calendar_service(force_reauth: bool = False): @tool -def list_upcoming_events(max_results: int = 10, time_min_days: int = 0, force_reauth: bool = False) -> str: +def list_calendar_events(max_results: int = 10, time_min_days: int = -7, time_max_days: int = 7, force_reauth: bool = False) -> str: """ - Retrieves a list of upcoming events from the user's primary Google Calendar. + Retrieves a list of events from the user's primary Google Calendar within a specified time range. - This function queries the Google Calendar API for events starting from a specified - number of days from the current moment (or today if `time_min_days` is 0). - It fetches event details such as summary (title), start time, location, - description, and event ID. The start time is formatted for readability. - Descriptions longer than 100 characters are truncated. + This function queries the Google Calendar API for events within a time window specified by + time_min_days and time_max_days relative to the current time. For example: + - time_min_days=-7, time_max_days=7 will show events from 7 days ago to 7 days in the future + - time_min_days=-1, time_max_days=0 will show events from yesterday to now + - time_min_days=0, time_max_days=7 will show events from now to 7 days in the future Args: - max_results: The maximum number of upcoming events to retrieve. - Defaults to 10. - time_min_days: The number of days from the current time to start listing - events. For example, 0 means starting from today, - 1 means starting from tomorrow. Defaults to 0. - force_reauth: If True, forces the Google Calendar API to re-authenticate - by deleting any existing token. Useful for resolving - authentication issues. Defaults to False. + max_results: The maximum number of events to retrieve. Defaults to 10. + time_min_days: The number of days before current time to start listing events. + Negative values look into the past. Defaults to -7 (one week ago). + time_max_days: The number of days after current time to end listing events. + Defaults to 7 (one week ahead). + force_reauth: If True, forces the Google Calendar API to re-authenticate. + Defaults to False. Returns: - A string containing formatted information for each upcoming event, + A string containing formatted information for each event, separated by '---'. If no events are found, it returns - "No upcoming events found.". In case of an API error or other + "No events found in the specified time range.". In case of an API error or other exception, it returns an error message. """ try: service = get_calendar_service(force_reauth=force_reauth) - # Calculate time_min as current time + # Calculate time range now = datetime.utcnow() - if time_min_days > 0: - now = now + timedelta(days=time_min_days) - - time_min = now.isoformat() + 'Z' # 'Z' indicates UTC time + time_min = (now + timedelta(days=time_min_days)).isoformat() + 'Z' + time_max = (now + timedelta(days=time_max_days)).isoformat() + 'Z' # Call the Calendar API events_result = service.events().list( calendarId='primary', timeMin=time_min, + timeMax=time_max, maxResults=max_results, singleEvents=True, orderBy='startTime' @@ -145,7 +143,7 @@ def list_upcoming_events(max_results: int = 10, time_min_days: int = 0, force_re events = events_result.get('items', []) if not events: - return "No upcoming events found." + return f"No events found in the specified time range ({time_min_days} to {time_max_days} days from now)." event_info = [] for event in events: @@ -711,7 +709,7 @@ def __init__(self, verbose: bool = True, model_name: str = "gpt-4-turbo"): verbose=verbose, allow_delegation=False, tools=[ - list_upcoming_events, + list_calendar_events, search_calendar_events, get_calendar_details, create_calendar_event, @@ -812,10 +810,59 @@ async def setup_server(): url="http://localhost:5002", version="1.0.0", skills=[ - AgentSkill(id="calendar-assistant", name="calendar-assistant"), - AgentSkill(id="google-calendar", name="google-calendar"), - AgentSkill(id="event-management", name="event-management"), - AgentSkill(id="scheduling", name="scheduling") + AgentSkill( + id="calendar-assistant", + name="calendar-assistant", + description="Overall capability to manage and interact with Google Calendar. This includes understanding natural language requests related to calendar operations.", + tags=["calendar", "assistant", "google calendar", "natural language processing"], + examples=[ + "What's on my calendar for tomorrow?", + "Can you schedule a meeting for me?", + "Remind me about my doctor's appointment." + ], + inputModes=["text"], + outputModes=["text"] + ), + AgentSkill( + id="google-calendar", + name="google-calendar", + description="Direct interaction with the Google Calendar API. This skill covers the technical aspects of connecting to and using Google Calendar services.", + tags=["google calendar", "api", "integration", "events"], + examples=[ + "List events from my primary calendar.", + "Authenticate with Google Calendar.", + "Check permissions for calendar access." + ], + inputModes=["text"], # Can also be API calls internally + outputModes=["text", "json"] # API responses + ), + AgentSkill( + id="event-management", + name="event-management", + description="Specific skills for creating, reading, updating, and deleting (CRUD) calendar events. Handles event details like summaries, times, attendees, and locations.", + tags=["event", "create", "update", "delete", "list", "manage", "crud"], + examples=[ + "Create an event titled 'Team Meeting' for next Monday at 10 AM.", + "Update my 'Project Deadline' event to be on Friday.", + "Delete the 'Lunch with Alex' event.", + "Show me all events for next week." + ], + inputModes=["text"], + outputModes=["text"] + ), + AgentSkill( + id="scheduling", + name="scheduling", + description="Assists with scheduling appointments, meetings, and managing time slots. This can involve finding free time, coordinating with attendees (if supported), and setting reminders.", + tags=["schedule", "appointment", "meeting", "time management", "coordination"], + examples=[ + "Schedule a 30-minute call with Sarah next Tuesday afternoon.", + "Find a free slot for a 1-hour meeting next week.", + "Add a recurring weekly reminder for project updates." + ], + inputModes=["text"], + outputModes=["text"] + ) ], capabilities=AgentCapabilities( streaming=True, diff --git a/src/elkar/cli.py b/src/elkar/cli.py new file mode 100644 index 0000000..9e25c69 --- /dev/null +++ b/src/elkar/cli.py @@ -0,0 +1,74 @@ +import typer +from typing_extensions import Annotated +import logging +import uvicorn +from fastapi import FastAPI + +# Assuming your FastMCP instance is in server_mcp.py and named 'mcp' +from .server_mcp import mcp + +# Attempt to import the SSE route utility +try: + from mcp.server.fastapi import add_sse_route +except ImportError: + add_sse_route = None + logging.warning( + "Could not import 'add_sse_route' from 'mcp.server.fastapi'. " + "SSE mode might not work correctly if this utility is required. " + "Please ensure your FastMCP library is installed correctly and provides this utility, or adjust the import path." + ) + +app = typer.Typer( + name="elkar-mcp-cli", + help="CLI for running the Elkar MCP server with different transport protocols.", + add_completion=False, +) + +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +@app.command() +def stdio(): + """ + Starts the MCP server with STDIO transport. + Reads from stdin and writes to stdout. + """ + logger.info("Starting MCP server with STDIO transport...") + mcp.run(transport="stdio") + +@app.command() +def sse( + port: Annotated[int, typer.Option(help="Port to run the SSE server on.")] = 8000, + host: Annotated[str, typer.Option(help="Host to bind the SSE server to.")] = "0.0.0.0", +): + """ + Starts the MCP server with SSE (Server-Sent Events) transport. + Runs an HTTP server for SSE communication. + """ + logger.info(f"Starting MCP server with SSE transport on {host}:{port}...") + + if add_sse_route is None: + logger.error("Cannot start SSE server because 'add_sse_route' utility could not be imported.") + logger.error("Please check your FastMCP installation and the import path in cli.py.") + return + + try: + # Create a FastAPI app + fastapi_app = FastAPI( + title=f"{mcp.name} - SSE Server", + description=mcp.instructions, + # lifespan=mcp.lifespan # If your mcp has a lifespan, pass it here + ) + + # Add the MCP SSE route to the FastAPI app + add_sse_route(fastapi_app, mcp) + + # Run the FastAPI app with Uvicorn + uvicorn.run(fastapi_app, host=host, port=port) + + except Exception as e: + logger.error(f"An unexpected error occurred while trying to start the SSE server: {e}") + logger.error("Ensure FastAPI is installed and mcp object is correctly initialized.") + +if __name__ == "__main__": + app() \ No newline at end of file diff --git a/src/elkar/server_mcp.py b/src/elkar/server_mcp.py index b9cae49..607db24 100644 --- a/src/elkar/server_mcp.py +++ b/src/elkar/server_mcp.py @@ -55,8 +55,10 @@ async def send_task_to_a2a_agent( ) -> Dict[str, Any]: """Sends a task to a specified Elkar A2A (Agent-to-Agent) compliant agent and awaits its response. - This function facilitates communication with external AI agents that adhere to the Elkar A2A protocol. - It constructs a task with the given message and parameters, sends it to the target agent's URL, + As an MCP tool, this function communicates with a running A2A agent (server) to request task execution. + It leverages the Elkar A2A protocol; this framework is utilized for debugging, logging of task interactions, + and maintaining the status of the submitted task. + The function constructs a task with the given message and parameters, sends it to the target agent's URL, and then processes the agent's response. The interaction is stateless, meaning each call is independent, and the remote agent does not retain context from previous calls unless explicitly managed within the task itself. @@ -193,10 +195,10 @@ async def send_task_to_a2a_agent( @mcp.tool() async def get_a2a_agent_card( - ctx: Context, + # ctx: Context, agent_url: str, - timeout: int = 10, - headers: Optional[Dict[str, str]] = None + # timeout: int = 10, + # headers: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: """Retrieves and returns the 'Agent Card' from a specified Elkar A2A agent. @@ -292,7 +294,7 @@ async def discover_a2a_agents( timeout: int = 10, headers: Optional[Dict[str, str]] = None ) -> Dict[str, Any]: - """Discovers available Elkar A2A agents by querying a list of URLs and summarizing their capabilities. + """Discovers available Elkar A2A agents by querying a list of URLs and summarizing their capabilities, potentially enabling new features by identifying agents with specific skills. This function iterates through a provided list of agent URLs (or a default list from environment variables) and attempts to retrieve the Agent Card from each. It compiles a report of successfully contacted agents @@ -301,14 +303,6 @@ async def discover_a2a_agents( This is useful for dynamically finding and understanding the A2A agents available in a network. - Args: - ctx: The MCP context object. - agent_urls: An optional list of base URLs for the Elkar A2A agents to discover. - If not provided, it defaults to the URLs specified in the `AGENT_URLS` - environment variable (comma-separated). - timeout: The maximum time in seconds to wait for a response from each agent. Defaults to 10 seconds. - headers: Optional dictionary of HTTP headers to include in requests, typically for authentication. - Returns: A dictionary with two keys: - 'available_agents': A list of dictionaries, where each dictionary represents a successfully @@ -382,4 +376,5 @@ async def discover_a2a_agents( return results if __name__ == "__main__": + load_dotenv() mcp.run("stdio") \ No newline at end of file From dfaa571ab822fc7305f1d45407b26c0382ec1f10 Mon Sep 17 00:00:00 2001 From: Matthieu M Date: Tue, 20 May 2025 17:53:48 +0200 Subject: [PATCH 4/6] Refactor server_cal.py to use environment variables for token and credentials file paths. Enhance logging for credential management and OAuth flow, improving error handling and service initialization. Update server.py to maintain consistent import structure. --- example/server.py | 3 +- example/server_cal.py | 72 ++++++++++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 24 deletions(-) diff --git a/example/server.py b/example/server.py index e571b86..f6864ea 100644 --- a/example/server.py +++ b/example/server.py @@ -15,8 +15,7 @@ # pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib from crewai import Agent, Task as CrewTask, Crew, Process from langchain_openai import ChatOpenAI -# from crewai.tools import tool -from langchain_core.tools import tool +from crewai.tools import tool from dotenv import load_dotenv import uvicorn import httplib2 diff --git a/example/server_cal.py b/example/server_cal.py index 96db649..ebceaf8 100644 --- a/example/server_cal.py +++ b/example/server_cal.py @@ -15,7 +15,7 @@ # pip install google-api-python-client google-auth-httplib2 google-auth-oauthlib from crewai import Agent, Task as CrewTask, Crew, Process from langchain_openai import ChatOpenAI -from langchain_core.tools import tool +from crewai.tools import tool from dotenv import load_dotenv import uvicorn import httplib2 @@ -27,6 +27,7 @@ from elkar.task_modifier.base import TaskModifierBase from elkar.task_modifier.task_modifier import TaskModifier from pydantic import SecretStr +import logging # Load environment variables load_dotenv() @@ -38,8 +39,8 @@ # 'https://www.googleapis.com/auth/calendar.readonly', # Read-only access to calendars # 'https://www.googleapis.com/auth/calendar.events.readonly' # Read-only access to events ] -CALENDAR_TOKEN_FILE = 'example/token.json' -CALENDAR_CREDENTIALS_FILE = 'example/credentials.json' +CALENDAR_TOKEN_FILE = os.environ.get('CALENDAR_TOKEN_FILE', 'example/token.json') +CALENDAR_CREDENTIALS_FILE = os.environ.get('CALENDAR_CREDENTIALS_FILE', 'example/credentials.json') # Google API imports from googleapiclient.discovery import build @@ -60,40 +61,67 @@ def get_calendar_service(force_reauth: bool = False): """ creds = None + logging.info(f"Using credentials file: {CALENDAR_CREDENTIALS_FILE}") + logging.info(f"Using token file: {CALENDAR_TOKEN_FILE}") + # If force_reauth is True, delete the token file if it exists if force_reauth and os.path.exists(CALENDAR_TOKEN_FILE): os.remove(CALENDAR_TOKEN_FILE) - print(f"Deleted existing token file to force re-authentication") + logging.info(f"Deleted existing token file to force re-authentication") # The file token.json stores the user's access and refresh tokens if os.path.exists(CALENDAR_TOKEN_FILE): - with open(CALENDAR_TOKEN_FILE, 'rb') as token: - creds = pickle.load(token) - print(f"Loaded credentials from {CALENDAR_TOKEN_FILE}") + try: + with open(CALENDAR_TOKEN_FILE, 'rb') as token: + creds = pickle.load(token) + logging.info(f"Loaded credentials from {CALENDAR_TOKEN_FILE}") + logging.info(f"Credentials valid: {creds.valid}") + if creds.expired: + logging.info("Credentials are expired") + if creds.refresh_token: + logging.info("Refresh token is available") + except Exception as e: + logging.info(f"Error loading token file: {str(e)}") # If credentials don't exist or are invalid, prompt the user to log in if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: - print(f"Refreshing expired credentials") - creds.refresh(Request()) + logging.info(f"Refreshing expired credentials") + try: + creds.refresh(Request()) + logging.info("Successfully refreshed credentials") + except Exception as e: + logging.info(f"Error refreshing credentials: {str(e)}") else: # Check if credentials file exists if not os.path.exists(CALENDAR_CREDENTIALS_FILE): raise FileNotFoundError(f"Credentials file not found: {CALENDAR_CREDENTIALS_FILE}. Please set up OAuth credentials.") - print(f"Starting new OAuth flow with scopes: {CALENDAR_SCOPES}") - flow = InstalledAppFlow.from_client_secrets_file( - CALENDAR_CREDENTIALS_FILE, CALENDAR_SCOPES) - creds = flow.run_local_server(port=0) - print(f"New authentication completed") + logging.info(f"Starting new OAuth flow with scopes: {CALENDAR_SCOPES}") + try: + flow = InstalledAppFlow.from_client_secrets_file( + CALENDAR_CREDENTIALS_FILE, CALENDAR_SCOPES) + creds = flow.run_local_server(port=0) + logging.info(f"New authentication completed") + except Exception as e: + logging.info(f"Error during OAuth flow: {str(e)}") + raise # Save the credentials for the next run - with open(CALENDAR_TOKEN_FILE, 'wb') as token: - pickle.dump(creds, token) - print(f"Saved credentials to {CALENDAR_TOKEN_FILE}") + try: + with open(CALENDAR_TOKEN_FILE, 'wb') as token: + pickle.dump(creds, token) + logging.info(f"Saved credentials to {CALENDAR_TOKEN_FILE}") + except Exception as e: + logging.info(f"Error saving token file: {str(e)}") - service = build('calendar', 'v3', credentials=creds) - return service + try: + service = build('calendar', 'v3', credentials=creds) + logging.info("Successfully built calendar service") + return service + except Exception as e: + logging.info(f"Error building calendar service: {str(e)}") + raise @tool @@ -126,9 +154,9 @@ def list_calendar_events(max_results: int = 10, time_min_days: int = -7, time_ma service = get_calendar_service(force_reauth=force_reauth) # Calculate time range - now = datetime.utcnow() - time_min = (now + timedelta(days=time_min_days)).isoformat() + 'Z' - time_max = (now + timedelta(days=time_max_days)).isoformat() + 'Z' + now = datetime.now(timezone.utc) + time_min = (now + timedelta(days=time_min_days)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + time_max = (now + timedelta(days=time_max_days)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' # Call the Calendar API events_result = service.events().list( From 734225c077b1cad238da25b1f7482e4568ccf807 Mon Sep 17 00:00:00 2001 From: Matthieu M Date: Wed, 21 May 2025 11:41:48 +0200 Subject: [PATCH 5/6] Enhance list_calendar_events function in server_cal.py to support retrieving events for today only by adjusting time range calculations. Update documentation to reflect new functionality and improve user feedback when no events are found for the specified date range. --- example/server_cal.py | 18 +++++++++++++++--- requirements.txt | 29 +++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 requirements.txt diff --git a/example/server_cal.py b/example/server_cal.py index ebceaf8..0f832b6 100644 --- a/example/server_cal.py +++ b/example/server_cal.py @@ -133,6 +133,7 @@ def list_calendar_events(max_results: int = 10, time_min_days: int = -7, time_ma time_min_days and time_max_days relative to the current time. For example: - time_min_days=-7, time_max_days=7 will show events from 7 days ago to 7 days in the future - time_min_days=-1, time_max_days=0 will show events from yesterday to now + - time_min_days=0, time_max_days=0 will show events for today only - time_min_days=0, time_max_days=7 will show events from now to 7 days in the future Args: @@ -155,8 +156,17 @@ def list_calendar_events(max_results: int = 10, time_min_days: int = -7, time_ma # Calculate time range now = datetime.now(timezone.utc) - time_min = (now + timedelta(days=time_min_days)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' - time_max = (now + timedelta(days=time_max_days)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + + # For today's events (time_min_days=0, time_max_days=0), set the time range to cover the entire day + if time_min_days == 0 and time_max_days == 0: + # Set time_min to start of today + time_min = now.replace(hour=0, minute=0, second=0, microsecond=0).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + # Set time_max to end of today + time_max = now.replace(hour=23, minute=59, second=59, microsecond=999999).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + else: + # For other time ranges, use the original calculation + time_min = (now + timedelta(days=time_min_days)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' + time_max = (now + timedelta(days=time_max_days)).strftime('%Y-%m-%dT%H:%M:%S.%f')[:-3] + 'Z' # Call the Calendar API events_result = service.events().list( @@ -171,6 +181,8 @@ def list_calendar_events(max_results: int = 10, time_min_days: int = -7, time_ma events = events_result.get('items', []) if not events: + if time_min_days == 0 and time_max_days == 0: + return "No events found for today." return f"No events found in the specified time range ({time_min_days} to {time_max_days} days from now)." event_info = [] @@ -716,7 +728,7 @@ def update_calendar_event(event_id: str, summary: str = "", start_time: str = "" class CrewAIWrapper: """Wrapper for CrewAI agents that handles calendar tasks.""" - def __init__(self, verbose: bool = True, model_name: str = "gpt-4-turbo"): + def __init__(self, verbose: bool = True, model_name: str = "gpt-3.5-turbo-0125"):#gpt-4o-mini"): """Initialize the CrewAI wrapper with a calendar agent.""" # Setup LLM openai_api_key_str = os.environ.get("OPENAI_API_KEY", "") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7a83444 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,29 @@ +# Core dependencies +crewai>=0.11.0 +langchain-openai>=0.0.5 +langchain-core>=0.1.15 +elkar>=0.1.0 +python-dotenv>=1.0.0 + +# Google API dependencies +google-api-python-client>=2.108.0 +google-auth-httplib2>=0.1.1 +google-auth-oauthlib>=1.1.0 + +# Web server dependencies +uvicorn>=0.24.0 +fastapi>=0.104.1 + +# Database dependencies +asyncpg>=0.29.0 + +# Utility dependencies +pydantic>=2.5.2 +httplib2>=0.22.0 +uuid>=1.30 + +# MCP dependencies +mcp>=0.1.0 + +# Type hints +typing-extensions>=4.8.0 \ No newline at end of file From 23960b9b527004814055b9862da649ad857ac323 Mon Sep 17 00:00:00 2001 From: Matthieu M Date: Wed, 21 May 2025 13:47:07 +0200 Subject: [PATCH 6/6] Add installation instructions to README.md and update server_mcp.py path reference --- example/README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/example/README.md b/example/README.md index 97f90e2..da77836 100644 --- a/example/README.md +++ b/example/README.md @@ -8,6 +8,27 @@ This project integrates Google Calendar and Gmail services with Elkar A2A, provi - Google Cloud Platform account - Claude Desktop application +## Installation + +1. Clone the repository: +```bash +git clone https://github.com/yourusername/elkar-a2a.git +cd elkar-a2a +``` + +2. Install the required dependencies: +```bash +pip install -r requirements.txt +``` + +This will install all necessary packages including: +- Core dependencies (crewai, langchain, elkar) +- Google API dependencies +- Web server dependencies (uvicorn, fastapi) +- Database dependencies +- Utility dependencies +- MCP dependencies + ## Google Services Setup ### 1. Enable Google APIs @@ -51,7 +72,7 @@ When you run the application for the first time: "mcpServers": { "a2a_elkar": { "command": "/opt/anaconda3/bin/python", - "args": ["/Users/mm/elkar-a2a/example/server_mcp.py"], + "args": ["path to your server_mcp.py"], "env": { "ANTHROPIC_API_KEY": "sk-xxx", "OPENAI_API_KEY": "sk-yyy",