From 17ae352439a853cd86ee163914ea703a195cae4d Mon Sep 17 00:00:00 2001 From: sentil mohan Date: Fri, 22 May 2026 00:54:08 +0100 Subject: [PATCH 1/6] Adding AgentCoreToolSearch plugin --- .../integrations/strands/plugins/__init__.py | 5 + .../plugins/agentcore_tool_search/README.md | 122 ++++++++++++++++++ .../plugins/agentcore_tool_search/__init__.py | 3 + .../images/agentcore_tool_search_plugin.png | Bin 0 -> 143034 bytes .../intent_providers/__init__.py | 6 + .../default_intent_provider.py | 58 +++++++++ .../intent_providers/intent_provider.py | 24 ++++ .../plugins/agentcore_tool_search/plugin.py | 109 ++++++++++++++++ 8 files changed, 327 insertions(+) create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/__init__.py create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/images/agentcore_tool_search_plugin.png create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/__init__.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/__init__.py new file mode 100644 index 00000000..9a6da0f7 --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/__init__.py @@ -0,0 +1,5 @@ +"""Gateway Strands plugins.""" + +from .agentcore_tool_search import AgentCoreToolSearchPlugin + +__all__ = ["AgentCoreToolSearchPlugin"] diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md new file mode 100644 index 00000000..e7f83b66 --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md @@ -0,0 +1,122 @@ +# Strands AgentCore Tool Search Plugin + +A semantic tool discovery plugin for [Strands Agents](https://github.com/strands-agents/sdk-python) that uses the [Amazon Bedrock AgentCore Gateway](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-using-mcp-semantic-search.html) `x_amz_bedrock_agentcore_search` tool. This enables agents to dynamically load only the relevant tools for each invocation by deriving user intent from conversation history, even when hundreds of tools are registered on the gateway. + +## Features + +- **Semantic tool discovery** — uses AgentCore Gateway's built-in search to find relevant tools +- **Intent-based loading** — derives user intent via LLM before searching +- **No list_tools call** — tools are built directly from search results +- **Pluggable intent provider** — swap the default intent provider with your own +- **Agent model reuse** — by default, the intent classifier uses the same model as the parent agent + +## Installation + +```bash +pip install agentcore-tool-search-plugin +``` + +## Usage + +```python +from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client +from strands import Agent +from strands.tools.mcp import MCPClient +from agentcore_tool_search_plugin import AgentCoreToolSearchPlugin + +mcp_client = MCPClient(lambda: aws_iam_streamablehttp_client( + endpoint="https://.gateway.bedrock-agentcore..amazonaws.com/mcp", + aws_region="us-east-1", + aws_service="bedrock-agentcore", +)) + +mcp_client.start() + +agent = Agent(plugins=[AgentCoreToolSearchPlugin(mcp_client=mcp_client)]) + +agent("Find me afternoon flights to New York") +``` + +Or using a context manager: + +```python +with mcp_client: + agent = Agent(plugins=[AgentCoreToolSearchPlugin(mcp_client=mcp_client)]) + agent("Find me afternoon flights to New York") +``` + +## How It Works + +![Tool Search Flow](images/agentcore_tool_search_plugin.png) + +On each agent invocation: + +1. **User query** — The user sends a query to Strands agent. +2. **Hook** — The agent triggers the `AgentCoreToolSearchPlugin` before model invocation +3. **Derive intent** — The `IntentProvider` sends the last N messages from conversation history to the configured LLM to produce a concise intent string +4. **Search gateway** — The intent is passed to AgentCore Gateway's `x_amz_bedrock_agentcore_search` tool to obtain most relevant tools. +5. **Invoke LLM** — The agent invokes the LLM with the user query along with the matched tools from registered MCP targets (Lambda, API Gateway, MCP Server) + +Previously loaded tools are cleared before each search, so the agent always has the most relevant tools available. + +## Intent Provider + +An `IntentProvider` is responsible for analyzing conversation messages and producing a concise intent string that drives tool search. The plugin calls `derive_intent(messages, model)` before each invocation to determine what tools to load. + +### Default Intent Provider + +`DefaultIntentProvider` uses an LLM to classify the last few conversation messages into a concise intent string. By default it uses the agent's model. + +**Basic usage (uses the agent's model automatically):** + +```python +from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin + +agent = Agent(plugins=[ + AgentCoreToolSearchPlugin(mcp_client=mcp_client) +]) +``` + +**With a custom model for intent classification:** + +```python +from strands.models.bedrock import BedrockModel +from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import DefaultIntentProvider + +intent_model = BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0") +agent = Agent(plugins=[ + AgentCoreToolSearchPlugin( + mcp_client=mcp_client, + intent_provider=DefaultIntentProvider(model=intent_model), + ) +]) +``` + +### Custom Intent Provider + +You can provide your own intent derivation strategy by subclassing `IntentProvider`: + +```python +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import IntentProvider + +class MyIntentProvider(IntentProvider): + def derive_intent(self, messages: list[dict], model=None) -> str: + # custom logic to derive intent + return "intent string" + +agent = Agent(plugins=[ + AgentCoreToolSearchPlugin( + mcp_client=mcp_client, + intent_provider=MyIntentProvider(), + ) +]) +``` + +## Prerequisites + +- An AgentCore Gateway with **[semantic search](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-using-mcp-semantic-search.html) enabled** +- Tools registered on the gateway with descriptions +- AWS credentials with access to the gateway + +For more details, see the [AgentCore Gateway Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-building.html). \ No newline at end of file diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py new file mode 100644 index 00000000..37d63a69 --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py @@ -0,0 +1,3 @@ +from .intent_providers import IntentProvider, DefaultIntentProvider + +__all__ = ["IntentProvider", "DefaultIntentProvider"] diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/images/agentcore_tool_search_plugin.png b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/images/agentcore_tool_search_plugin.png new file mode 100644 index 0000000000000000000000000000000000000000..4e29dc45ca9a3a8ff608e22f6b56ff44d18b7396 GIT binary patch literal 143034 zcmeEuXH=9)(5@mbV?t0Ea#m4bKqO}b0YwGLIZGJHIfE#IG6WR~O3pbCISLL)Msgkj z$vJ1ZJ-Y77?&3Z7&;4=lx952F)p_3z)z#HiPd(MI9!W`DI7@o=#EBCZ?%lm3d*TER z?8FJIHpm~~Utqa&nbWT zu=CTo7M;+5!@cu07fnyfrRbix9z?+>z(%2=sp97qd8S?Bq9tvjg3OtkyRTlfZkMMP z8Xc68EZgrN92WBC7W-UXedyFHTX3pBIy-0ndX&AVvnh|{R@J1-^8nWt_ATUs%3b;w zEH6qkp1mimAoAB&U2{1X;-l7Gci~|Djk=?yra_2}&>uH!90Pq>8Tuh9T| zYHEI~#|FHzcf|kv9Q;jy*2u=jf|rHG-rkm8iKX=+j2<@1^_Z*nd5!krqxds12x_0D zSIXDjg;Qf?#Xdx1vlG0@Bse+Ut~R9&Dc^+N-CZahQEwe=y7Iohskx~+ucxFqX~DY1 z+Dw_rt<0J0VKOovpIZ3oiIZ5^5WF7`7j7(d8(wZ#i}EvH?+gA=Y?!t?_vig7un~_yrE9Cyk*_YmZl;`V9Q`On zLKGDGhYkb3%jLhjx@n5-8Qf7jq;TfXk^LB=aMD@i)t9~&d9XVB8Nb=^Uwe^_t`86H z7|e@)tv43l7^J^%$<=C293?>i`f75Ta8`+CGwP=> z{`}3O1j2ri`jAK)N)%9)yUmKCJo;7T(-ECn^97$@oBDeqf6DhAn9kMnh~K``sU>Wr z;%=>U<>;Cm{U-VfHIhk3`S%nRkz zYa<`7SH#c$s?*v&r$1bi?{kQ#5Fi|7t zv>y%qDxH6|+Q#XjY05VU24$02B`PiWzSf_t=Y2>U*Rf4U%V8UQu&iY_`XV;aRoIrR zVIoD_=7>cX>6E(4+rxJJcY)Z7T9zogO(vuXSH$Hr58Sam#FjXuAgUi>J)dfbO? zsa4KYZlTPjBVLM|()GA8Usk;~#Pju{ZP1ro;VtChnFyZ%QFp zDjawxLiJ=lL*@Ew{VBrSHcQb7`3wf-PV17ZO>ELLeR+ne`aufq=!1!N(OOR*MP4XldJcwsG<=ZdzRxJI_4Rqze9jX1A$}}C zNN`bT{GHr~c|;K3Vj0__^Gr&T?BT)w>cMPgDRV~IO2hO_T9%Y`tAKsKQJi0UZXtF1~sac5V^&|Bh~m0;Yd19~I=d<)5$m0P2T=~oS| zA!?qUUue~Rp+NzB^GG>O!R1evJJ?$J12c;!KCDf}1x#4emATC2)U|(+po$21-$_-v zP-5&u%~9O|xe-UZ7OZA=58px4Wf`^IIa1@D)FCb8Dm&DBtYvs00vx6@b1Y@qeQ>SAou1X5jKbl56K7AaFQbD- zT!R*(2h0c8y+kzJ_t#ewA<#IHTV9s?BNyF6KYaM$Bihk^pC!VdcP6=vGAJL_-Ne@V;BfJ9zj8DiTG=YN zJ6E><^c3#h)m9;QMBUz6l29|v)W(2$lxkSbEw5Mm>sjs*lU=^fgC=>+?Xve_L6haa zPxFl?Bc07C8#X%Sq+y)ni$t0ZH0Ed>Z;I4`xK`o`yIK;%2aFj=S<;)@!m4J)<5nE- zb?S4+%{&aUHgj!rn)3`^GK9cGUtXg2W)?+POYvOpph8n1$%uZ=u*|;1OJ(eLYUbs( zA507SWaSxZ$=|Vld(tF8V)}wm@$-NX*oFM(XD{7ndZXzyoxuNY7HqRfuk$2tq3RYN z)l-O9)y1{#(XN~0)_R|NXqA1RG)xJl!f-B9hNr}Yu4Poo+qW~+-dcUukHE)A{A-C}M*L}09@l7_8@9 zR*!kdeA)Ut_sPu5-uMGCxk!0du--A1*QmuSBR$Ua_yg0ku+=@dYI6fm;q@0@#%!iX zafiuMbDtfV5BDa8CKv4c;Yp*nFy^hfF)7N;iImUI#MYTLOP>|}7(4Qd&%Dfg;*8MA z6<|E-XJziGn6aLp1?Dlr>jGJ@7cRwzns$g(T*jn?nY#u z_SvTAyBrgjwG6atBKqdScCTK*Lwx31dM_?N=xtq@ha|JWUYfa#o8lYPx&-5sLPbAA zce9E{eaP*gt7@5DBDPa=d99Il!;X_{J(UL>Vko`y5@duvGCU+b;EZ}G=XlWQGQx83 zGDa1wRaY5Qv{NcoV1KQ{@nsKjZb1Q|wW8a+u~ynpP{mpT%eblZgG8$WyQ1FM*3mty zI##7b@qhWSKW0-B20Z7|a@NiJ(P3xQ9Y$Vs@%dXYPel7lv#vdv+iTQ1kQFefzZ%k{ z@CN@TX7PE{2VGX1S6^pYJ_6@g2M|@nWU=r57u||R;+=dWeAxwXmh;?YDnQwWV-#IP8=LjAY6MZhGFTKaM-3PXkv}V=1BNDi-X_ozsscaQph(Ie7I3-Wx`~ zFZFxUu!(lB?3;WeL0P|xF_dNHMQ)z&zTomsMemg_*OE;xjs_&Z_Enr?X*DDnh;mt} zx7k~+O9JOl-OL@+uT>sAk9{e`lDWU!4!Qt*hL*4Za2l{1G=&D^-b00l%LMV%ky}5o zX&ObaL>306-9tQIsMuX7Q>Gl{;Ota%7X%&&>PEAF<@Q8am}x*z9hJLFtSRR)F0n@@ zGjN$_%s=nrZ;3<`7vzTy=Jp4i(&6s##eeu)ey}Pjxxm-M1UHT2|GeWC)+nx&k6q7~40!-lNsJDt_=4N9glMEqSe4@({6Pl39#5 zXxV=U&DL~Nlwn~u7Qrkj!$C4BAxzR0CFW4lqv@Pr?!G&4If9CemS6~YU#k@yJ?d-` zmbD}|819c9ER?Ob@GDJnRV@3J2}LsYkB4YQ+6~$E*nGm5h=({Y7EeZ&m2c5K8@}ns z`S|neJrj(9@;Tr*PE{#RJK5HS*|lOPDtXX8V5&y>CwrymmrWE5@WwY-&GqI?11r!n zWL~+yksp$hX`bJuIQ{kcMJeF7R9ba?1sg8}s;uq7Fa5a5SOwdu>oUVR{HJC)C@9a~ z0B{>+(G{+Zr69nQ8{VANI^?AQr_v3qaw|29kEc6Y-49lg;9!5pRajIu>ccqkq?wzO zfsD?ANwrCiqy}wPIAn)PG(ZbDxf~q62)C#{<0#c4CoRAsR;7X@{=>dETPKCvtc7Rt z0gxEFopZn>u&~Y&UlQZ4!DllHnWY-h^ieKWq+Dj|yp!S9sjc`r6Y!*nVA}Yue;`Fa z4+CNQOGY0a!H1^3h8R7>p$^Hnh{jIIantKGnfJZ1R+p-Ooo!j}*Q1)IR{jhe(p;$U z*I)uBGd)>aszxy{R$e2^o$~h&`#_AsC5xTHdU4`bBU2#lp6^o|Ni})5TK-5&sq=mV zszHY5Dacl>HWtxBT6(hkl}pvbM^I(~tf$>e^UK=vDKM$kdzq4`axUgA{u?;^afd`hf0ppH3Hn0O*#v|nt zo~jgKRN!x6qEOY7G2@+QdH)}7%0p9bz+?3c-jss}3#|C@Ojn*^ojre$Knquox?JD$ zj^pKX`5XjB&zbY0ik)by{nGvT3fG+ntGgDb{xU)TeDH9B5K|UVHe}vD=9K)dzrtyl zupNE`=l$18MpJ^9oUWJt@l(hD^#-Pk4B*tOCFC3*;bRoR2|ymRF>|E<9PN>okqp#G zlb~AMV=T@O8$3fpjZF8>H9qFh`O#ooD)0mpNcoQ0t3T$E>egJa zRS9}++I-+4S?A%FQGS8YoBVU}u5(w9`TDz_LMJUSOto@rZW+Q(&DDBUiwIoOdN z2N`XM(NFK8WCPCp3=ingpx>$yCP%Jh79g;p?9<9K86JoUWNnr{;v^*|Cc1oneo-(q z(rG4TWu!Tn?!=H;>3sY4WE9%OIcEi+W70O#sEYo@gb>XVJ2~$RFudKaU5AC@N#1lV z3XWi4Idg`Sl4y?GzBAMoAjnMsW6N_&=)aj;GKP3@3YR-tMVklsmISv)$~T9@C;_JB z(n;e@{?=)2>J27{>F?+`%b5O}k%wc6}-2?|?z%>clA1cX+KEg(Jfv?*h<5REH(-W1ZqQBUC#s&!P7&-0E7g}c* zH!!5JpW{x$QG!ys?=ACp&If{Yi)hz)uo`nlez)r7A@}X@6)X8@O@QA`S03)0e{et8 zmh&vW56hR&??<|%kJm!+R5>?x*KLk$+h0~e80YMraozK$c0mX?(F%~0PyoHEWN0&O zgs`YkR&X%TBC2lrzlzbm$iGy@?y|MCR!e67t%J5o4Zzur8tiJ3@Wl5k+1X-7+iM-t zTm$IgAbdLUZ>i0}Uhx7Tys|!Rl%*LZabK^_zxlSYNtP)-uclS~)f-q%_ww(vl(Wx+ zjpJz6x?(AF+6jbZNf)>Vv&;Ido}XJYTAEySIBwV{Ja8|+q-GY$H~Nn5fS|K<)!x%~ zwV6vxllc(eS`lgruzY4{8d^qyaqQ6bkxXjx8Z#HV^5|;*=P(DwZGoeT0RI`2k4VtZm*{ED=Q-&i?3DvLvlz; zYo$oLe)tV9hy4kz%J)(N>zy->#+k(iw#DV=@b5xT>hjkCOA;nphhQ#A?jOgOAb$hU zk_dhilcp~!NXE<}S}L-ddhsn{*p830X-1HD&prmt(uE9hyMuj|5oy(YO)L9l)sBOd@fAzQFuvx&V`9q~pYU zdW`*nEf_b}g7GFgZ2|B!&>+mTpsb>qb08iQXI%6kyZvo{6CG9SN2VmeInnnWL)pPV zK9g-sB7?*g8vr~^p;XgU260VlT2i=`q>r}!^9!7JE$4|DxV)PE0yy^}$^RQM^9pN& zh_SK!&eKg`jwY*MoS$5RVfl~^pN*A979xb)I)!Ko2A#N(iceP*-miGxmTVuOmAuU* zjIA(|Rm0%Pwm}d~g;A&pZURKxtRSl_ccb4piCC)82!D^#rtS8Pf{Va}Yq+j=i8Nvv z@@_*_rRZ7u^^T2uDCn|R8W@%Mc(=a=u3CA%1p01c>+DD~@sX(?WOn446f5g|NdjY? z*!&T#AXOkYq{Q^ek@ezZ7oTF5R*?2Vex^kbct7Z}XN`Jnb7?|$028svC= z$RRNqO2P3&erGl6FkDet=fzA+jn{=VW;G+1lh&%72Yefz$8CIJoO9_QapLG!7drsp ziah1;sohfaz8UX`=jC<~u?NLoVy!MCKFd*E51I4%$|f}kg6E|A>}0SFG#Dl(e7D(% zG;F>gtFX?+1Cg_#tp^7%R!>nyKZxMA0TU*jb7NwbA7JMl;D`(MZV=d+iW_SijBV{! zZ8}40ts;IdM9-h+T5B^&%{;WG!3~)1j0iL5Qy{D%TS{=ca_r8sKJDWOL!_lw?98A> z@1Q|CuVn;15oR8FstUr4F|V)uF+fJUwA)JHtAZXqQBQw-T5)Y|t8bqPg5vV$;{&B6 zU3b>%!vcg3wvYhoo*0LakI9+Iuokbj@P%-3s10M%4Kd>wScf30zW``Kr&;cB6&9@L z2G`OlXZGWU_EcC6+i7Et=gSBH`OFGn40v(tGwYR3IV+Ab?|F1Ty_Bb9C>Xpg&xOe& zq_{{Ky9TuyH(C`}_OI%qVTFTl0oJTq%80T+0#wNq<4C0K(mnzR_Tl?Y1unOo><%fu z1mL#hzZaWl#b40`M=varN+DcF=KM!B=gjq50t^)5w`~;ZBfNRpVXB_K<5Yl9WWm7W zz|Kv?p<2SOzze?r!ql{#phZhDrI@Ip8GDwQ^ZLF;^Lmn@KQu~!0j77*5TfPA1IztP z3kN9f1F+oIh&m~R-(vZurqk#v`gVYb22N&?b6aTV-OP899;@|hT$QQC%d&3aF(hR- zdi24zTcy$aft4c$omy^iCkoKfiSuE@Y9!{g_aZR}{pP3vpWMDU{GQqQ+kwmdFkDsE z`IR6Vjp>aUE0#q?w~mk@3sKebTWibF4QDV;|-t3Sp*!wW8u(1D< z-Ouo+jT=PmwT>Z?p2zyXfb&pDfTwEL*fWuMJoJDK0i$ZmwH!{MKi1C!Q*VQTaA0U9 zzZm|zq5FYz)g;`0`rdDZ1+lpg!jD21Q?n1h8~PZ)68=Y2|061(BmbjS{|6heEzOb= z6?h~{KeEkuHNOt^T#H}D-A0b_MdCWm@yW^ck9i7qd<=B@lma+JQW?FlN#@De~p-`YQwgwq07!xWRGr#y~vmk-}NE z&R3-G{4~M8#A81ipVGuaFrn!(_-wq^245~uCywax?oZfnD@3_u6g?8i?3up!4Qogf z40~QH2Is#e408O6CEWF$N@lKiBs@BLF{AqW+ei?f!ZToJ7~Ae)WxNRDQkux}ZM(H7y&hotrG=WI6 zLckHG+5Ru`@?ZV1mkHZnU^Le9UCLGbs6d1ZLv%-3_FFU<>Nj(;X&%5;ntN@qTdNte_}+Bl%dHORz}VpD?w`eHL1(6 zP&m3A=;KBMPr$Kqd_-rlx|EZ``8ACvUM&mS%4xApc_Z~c2NuQEuRWbIZ+DJ8r4yY! zh?#Ajj9L{^?}68EfVAx0hpxv45SUrC8Jw)CDR|#>=KGYQb56IEF8bHH2}}PVFeS!R zg%BxoyTU6@Cyh`P9igF?Bm$8eR}k5Yecum>@2lqHBBjjB4>ctPJ|y-De0otAKoR1! zJ}vNY@gwTH<_SxHC}69dVkqI@SHZX80hxF?_)-4ABW=>F$N~i4Q}Q}H)YDtppC~=aQ~(HHtrrZnICd?@G$CXMk;s>mg^FLY|Pq`FQlsIqj%&fKvfvUF|0no z*iXfE{5tGxO(`FeQ`K*_i(0(%y}dRCY+7XJ-*ZKZ zMJER{MagBxGMQHv%f4{5cB~<492W$%E}0yjC!dNW^$ny6L$tWJXLx~3GFO=ge{epH zOgs`B$ls&b-(O}3;H5ce`@7Abiu>fW&udG`g2M6MsVr`{p)0hh>-mDFr}t=+GAh%) z?;?+B5p9vENcdiLUVP5|);F~-ep&3UN=hPm<()%zyK%?%P+Ou0mxCkN5+sG+tLd}%CKd|b6Nq%< zS}&W-FBytF_g^aAwvORc98wgE{*b)wY1-^`d3m?o=C|-U|Hi~+M9YV3Ym0#&cGem? zlsHkypfURRM0cSmrwi`1>uyVaonskaQl{frH8$T5l3?upPr?3tu*DGOBL+|ZQ03K4;HWkIe!>~>IloOWPd{g*yl2jTpzZ|ny&X7< z?ys-Oi#+(Hg5uAPqV-NgRVpM%Idom>>{7X~Y=Ph2jX#j^y>DUBGxA=Z6#F%V9a|iK z$hS8xr%Z1swYzXt1aUr5y>($u$b5z`(@=k>f$~O1wi9V-lTtfisRnBT$it?YqbD9P z_gMU>7PVr@W;Va-{G899GxUHnWPe@}=wPm#3G|}aN?Ee{P;rLHJPYjZa;ugfRHBYj z;nqa<2{pvz*+!BUrz`7xqnYeG9hTD#iKXpx`@2b}iPUexkZmGx03Az#6u!jwgf~DO zzDMI;7pWpoHBCpft^P|RcqO%mVxlAj@in1@A*n2Ul1f7Bn2d>NAIFcmpE~LVJ%ECXtrs*0_F8O88{anVx z2h^$8lo$q?cb%81{1n|WK4pKm8uk_%FT>piScoibF`E ztb|p_1+#MnRI%TQA$%H*WIX!Xc~F)WXKV;h9-^|-%Fb;5NY0gnRc)O~+0>%UdZ#FL zSFce0maD~V0er13p~I7e@f45zM5!-ybmDNou?6U-<8>o7iO_o2VZ{?a=(#T;Cgf0>?(~U24%E4 z+}}(BnrHVxun(hvf(Kh;i6P=Mm{+@gUq6q9YmeSn=GM(pCB1jEGbZJl%UN4C?~#I|96LO-nnJ|g z9f#KjlPpWkfH zV7RcVfPC664OmCWVW-)yZU27z;zPiMEPi4Ol0Nr^w(F()d+wbxqySL2q_X7|2_eQKW&kh)GGWz^2R;J2B{Ko0V#PDf4&xV1(z9*jk(5 z7El%zJg}L=uHD1a&Su>FE>hLz!H!MhnduK%Zf_UV-E3wYhhMHur%A_aMoIC@xCjvI z-tB+L*>?rQ$J@3N^*VnKLn|s-tk|`BM5Hqmb+DF%955?YFb6E$Z7HX3ZzS613(>;~ z47cc9$^kL9ogpn0A`Ps(sGk=md2Tw(npBxM;O{^Fi<=j3{^Z^hfp{Iyi2s6bl@wR~k?1&o>iT zD|MR%taE22*M`fZ4DL_%i$_O-Bhl$eDfGu;Q+t5K-5Q3?CMGsIXx$nSHCJpXkuaNC z!mHuBR}4>_&wQ|7PV!-JN^eglYZGHTNDKQ zb*^ITa1---4*u8xT`kWcOOtKK8X7m2R{qrodGjcaQ9g!eV;Xh&!=Tz=C6iziuV)L) z9dH`m^ql1zgVjvZT!nO?i}OB=nRhP=tUX%DbCF`ITP!WID+?={>{i|59OGS<$?yO0 z_6X5Al-HyFbch&nU%vu&a7KOaFd@eGSk5S-|KWRF0A8Q3j-vDCLIiCS!Vw#2#@-s( zI*9>-$=HlR42{EumEi^lC{y^vg~P}X3l29Dh6OqdOJO0k=OYUEnun>keH%ejleJmZ=_lx zW(FW*72D0`6T7O$8TsvC0YkkB{l|2*zEs0@UT(`0X}K&DEVB7)0DiEwHkoyRmuOR1 z$+x%n6CEmOiJ5j@()j4Le0J!u%*gh!SHTXz&=prWbeTH78=q{tD-Ko0hZOdYza9?Zz1MoOxv&C|8Uo16#yt(LFMKjJP@G3rzT zz$2(t=YT5c;}44^D`YY^w({D!%xqVC!@Fa8;@3nHoYIv7*xjaiW*YElWRO(*QxelS zLR+_ePSA_w$BGdb1CGPDsvFg@z#1^)@aG7Llq6q+smimPfs7kZ`Vh$XS@jZidEkG& z6I}p|eIl{{hM~}|ie`Z0EeViK4v(zVb63~PD&J_(>BH0@FrFD3j0Hy67TyReY&xks zXiZLavI`_H6v^LK##v=Sa%oKvkJvO8KX2FkddGW z%Ay)kQm1>C0ZBo;&f*qy!93$|PVF?|lPv~3Ap+lCi>_>h2Ci+51!P51_Pl(0%T0~7a4ID! z^24OIA2nMhQBe47dY+V^U;!av1SYqif0o;ekGUU{HX({@HOis1?7R#rK^-j<5ajMk~mk$5%**)Ob@jl8oog*f6GtMfvBjtCn za00~d)ulkzPmi(5EFYM$dXIaax#1VZ$+ZV4%|aKgNpzZ~lcFMpEa~wZTmaqvKBmfP zXC;Jj)l^YcUPzpp=%bV;Txf6PqMSTIu$reazJ-_(**wUwHlXp>Fj-rn0=s>qx)*}fUFa~vk3}tBscsF$;fTyLlF0yP4gG|v4rPq-d3&_?WG$k^(6z!s2VZ88m~f_WwQ*|Voq-Fk*f&PBu+)=>A;BADK$P3ji&6PPx! zk-(~JL4)huf>;Gsj9#{WOF0lGJ@=xzy%_wvwS#`+P-_H~#=u2l-53ec7#N^^fSAo+ zdxbVGe&LR-x#jw47omv#B1c_*A6n^` z9DF_iB0=|#Nmc%qw=EKKCC+t>3=OKx9%;xc)mknO+?cok^LpkNV^8PckCsJZyk~bJ zcY;j?ez_ch3IxrY#sOMwKMOvr#wDLw2veg-@UNiiBX^JF_)ZABPu1k1LV`?Wt@WSb zr|spJ!c5iq$*CBYSo_%^p1ojlr2LG)s&5MVnRPs+RwPGxZd3L2HmWkbR_VY`Pb5H6 zK|?>eS2IK~!U?_Un5|eCwm&K&vrQD0OpT!7$^t(18t}=lDhY@ulUScAAn5KKo>T$_ zUm8&Fn^SWPQ=gda^T)$to@ALTCdF%(wMbSY^yhk^RWnmt$Gw@#wro?c|8NEuu< zx_D5(k&Nemf+KSs=+uOD>{*+M_lC^&QEBmXHomiEfJoHkx@`X#RBfha5}o^EE)S7F zxaqwd4@Ju#sfO}^(=Ua$S@n;UT6tqY;Ba~G?@@;K&Lv_#GZUwg45f}_uDZm;Ve_QE zZY}2W!n^3Tk4&h;X(f_g9T$D0UV5~YE3*s3xifMYrWM@ih(xiKHt?rt0L;g|We82485GiHr=f=*!xr2v#CpQ${6t_X1*__yfDD zX*SUSEfaHLGO=$%Eu^aj<1T{Q2R0Uon`BYR-w=3t1+Ku=?$R;U(~>9XE`Vsq(cXI2 zz%8iS;5372a5(o=HRqX71*aqPu^MN42YFW4HyHsA|=`Qw_7Gh^sz&2P4KhHK_I^E19(-tce+e+=)8t}zve(J`mLO0NK~4_ zh-(~b%lrVwQnZ28_5Yka?E5H<+^Uv5U>vHWCtoF&@YpkN*%6>rvQ1--pb|L|lxA~Z ze5EEx9~>vi63K;C6dIRXfXYV7xH^rzAevZ3%(Ox5^jaFN(k}4FiLg&-o=4~kwv29t zo?8JyPyL8mP6nf!#0m0DRuePNLNS!FJdyol##DJd?*37G)itUZTlLcHfO*zQK#z!0 zzfc1Zrz$otV?VSO{9s&Zvd_O9*iru1wFBnb+Y z^E4xy72v^HT5k;m0eV-XXFgfX)KMgMUDO>_^Kbxw|GPDr{GcKiRZM5+|Pp_-WcM-y{+C!h`4YmN{6M=&5_}Cw~ z2A~GkIvC1{?*_`f)Vk=h040e0sUnv=R5x_U z2>(ftn!ITy2s*Yu#?{L38c9;x4 znP{E31u2Zd7wuNMvZ;2&L|~%u2MGXwVK^D_<64_UH4n+HUh7X`DRqog36VjP4Hary zK?WMTvFsfI=2EV$&a~?#llh%vr+YjF9P#HOs7jJ|_U6U}ldr`suW(o)aVDFp`Yy6Vs`2C~#%FesG6}eu zR)8W8mbpQc!Cko#N=uHoIgdHzDL=4J-19AHmt;&3#pStj(U8=dp}PPua9O-FSr2uZ zj7by_%ltZ*VAW#XZehn6d`wG0xfzFM2=eVlfM%k1YT`(HJTNsE53FmGAZmI%)>h;uYos zuyY-G$iZDh5N>-w?j-+1>?y(OAttPD_VS1AJelWf3DDuDQ|Dc46J6E8?-#QjgsPdy z2*4Olf>>Y*!>(4E>|(MLzgfC7lSY)*wWv#34RFCk)QHtsRp{8w1sW_Qv929Jqo+Sz zmTt0sez0I)+4xZpcqmU~nm0lDaRINnu1SCe$Ht3?F0rT$i2NqNp~?hPgaD(f&%{1d zBAML7!m|}a9olIRspy;w)lJygCm2xMVP+6CZap&=n#U^gsmK7C?B>TKx(FQgy*&VH zsb!L8l=qDCirrLGkL%Uk1*B!?q~ut?rELDBnSuecu=45*Bm7~&IVH>@Gs(W4!&4ch zd$qdN3PIx(cb3iTyx`ZTr&3F4@ybEHUHj9Mr&bC^1=XA*XI=%pSn_$zKYUw$exYPG zGoxi)MX>eOpXrOg!rN#LkHj-U-ThJnpaedvN;F7kmC|8v8L2TYB)&1%PM|-qws)GE8|mo00tm}bs{Bjd5HqX;==dR>gn!d2gLp5LFbF_&=7^A&FUU*59K(=ScQzNJo|kk_Q2Orr zGJFEC1q=s)K{3)i9?x_1=TVA&5)ab$R06~!d2MfEha#9sEX~~_ZHdUV2Y3$seCL(-6yM{=jkg$*gFjUsLd)g9>4RFp-Pvs z=^jPTuq&&CA-0W71QE?+d?`UdZN=OkU`#aNV|<6Y8@f1JcF(oW#b@1Q`9N*w0ux9e zQMi%Gd?6#ui0GodX*SK(uhP@O@-HQW0lc)pi03DTV#r%__ z;MR)ol^oubel&XjJUXNGuwyUf8jSq-bZj?N&%fTjqWUajycW0=0S-!k7saJV6@QHZ0PU^^dfn@lUul`+ zRc__3bAb_q7kUC#5k(2nS_*D~)hKHoVX3`H#r) zhn0K+P)Rw)DVOg~SKCu80fVU^r+Cd2t*ftn9GBR!^07|+@gVGG>4sqX(Y&08IKlPQ z>y52Yg?BL10EUKO)%$hg0IOCAgGHFDkzrp$jwpKGy?v5SvJVY^|EC zKlsZ?{KrET0bu(jov5ezQ{?|#@*ki4-)!oEA%@Gt2IvHObu7y+r^xn5lJcTVLmUP* zHAa~SvHyF7Ali#2JLgNvD(D?Fi#s3G{V=MI1w%~`_aj!?_Fb%%-q_BNcvQU5MzSKw z^UgK%WMsxbW%wVCCt~!Um)#9GwnFa#A106Rp*zjf311bO0l7(s^tD(71S21fq0!R( z=v!ujk<$|mtL!4w12D~68;iih-7q!^{&b0-X64U7^cMl!1Pgn$yIo@Sl;`Sg{jD>Me6vdI`x}ga!H+Jd{}=P(XM4Q2sjl0BQd{{CRlt6IcO{+2H)z*^%-vgl`nP$*#}2A z)UOFmLV&%8c3|B|Wr{n)ko2O$I2fq8{)NV0CU&%I4_?T-Q%xAsM;5WQ<}kmDNs?B1 zk)0(CVbBZDML3|zga_|rKKu`@{NC+@O)S<;edK5 zce_Wl+{27Pdm+wjXLOMB7nvU6R2fbyJJUqrk6?9NP`0_6&0n4I`S%R)?WuL^EtNZQ&eATI(QSrVqO z62(^uYT9H2D5DOAqVOLbfFh`S?prlf?Ko5}kLIl+9VhNBtM!4*o*cO3A#I>^Mg%(( zWSdPZ${h|RKoE1qd>vG!J-{Ki5JuE};!}nv1hsF11Qh#X+3JKICTEBI_y^gyaT-yt z_{&nHun&D5q@%RJpC0+(+(>A8C zp#V&X8KkJ|0x(y0G;Km}e=)^eJtx=-3U81gP+%W`ejLD*{Rx4B6PXeqm3DCTTHjo# zQ^u!#G`Md=&ZQWW5AX>vo%O&xlmHBS6>h18uUfmVsO1W>vfUe-JF{6y-xD(6{w}2* zP=~1LdNIGwHfSAGud<8bk(egFH~27TDyBTpU;1huFgM`5bJ;cRL-y#1Q%ja(wF&_N z+GUjUAZI4UQn965@s45C>E+Q1eO@V``j){?e^q81346p==k6I{E*mK^G2#KY$jxN9 z0s2ALxUvH%cL_TkjDH1#cHY&i^{uB-BflIJl9rw;=>*V_c7Uo;hRyWT68rkJS~242 zGp1*Y%ycA4+uA!8m*iX~lHy^iROJ_-w{_Anp~-e3v~k@GzrY;%MSMGV+3hE4@k8sU1cAS`u$nn| z_}pKE+D7Y8+p*4N&Q`x30I`-=T= zZ&j%kF;fP%37Y^=Te!z*=;zx2_j)c1ZWdV&otgy+==z!>=E}Vlo+~9IUSzUan&nnq zXi)PK65yBG26FiYPMY%VC|kXv76|nwh(Ts5lA(KbLI-cay?y>=rk%+$aF92 zNFr(y?2YAjwgz*jo!GjLd>%?b+=vi84h?!`X(GRXxg2j>GtS4_o5C&`mv0x8SQ^Gb z#6vXf`Ua)>osqgCLCCHE3h4p{^_`KsH*j{C%i%I0FwY%pk!5+GDk%Ej96cXy@ohX>1KTpT+HXfuT zTefJyjPn`PZa%~$XIDl*h4Wodf@s9)yfxz85$U|3ViSng5B+4W4ZO$3?&V=MF5wNG#6%b7?lE9{)KQshn2V(<>6dr?(Su-{hl~3XzX8RYI0~?vj`=S+fGrcC0?jM-s8x zeOk%@$z21EwLU-$v6{LRq&KTD;p|JJ-Z&4~0(r8?mPh@hGA~+x*^-JFB5V;t`!Wsm!xti zEvteTb+abWnAKi?PmpfSAG%3=0L!;>A8+`&^$;q`3a)k~gadfKHK|VE(_+#fD7MVv zFEsHCw;Y1pi;J*oxY1S2M#qiegIVLLH4TwW5?0p7R$3s09RZ-q%(;D}s>uUR-KIyX z-f{JBhtKAm2jm(8+(G6@*+_hlSMZ&<0@4o~64WZ#!Q*3kpF1#If<+edfbpqP2xsFi zEneyckS%JWBkr=1fhqD`F-XVMVHS_(OfgpHZgm!5O1H~_o!-7|tpzwG1(5fkR@hTT z3}IaNIwc=goz-t~9V;OitC5@Q*%0K(KtH%lG86AzC@_9*N5?eTvxcaBPMksgmR z#$4{E4&M$#?_CpH*WU8haiNX(g+|H{DsM3EAfj2H4JE(!gL>HUWHVJZLKdWWr{hOE zXY518g~p+q?Xub=b)Ob96VQrfSyte=*4!RcJZJuqlarATt*L=}=#zKI1R-Sn{uf(s z84zXLbqxz53?)N@NDN4~fPnN+(%oHBDh)#m3@s%kAqYr!cZ0Nmbcb|z$9r|^>I?pclI?W$54_Tsq*wb5A0IZZO4^Y8dR}X+0zv?!C z@aoN}>j!--2XNRQ2Wby#n4%A%yX+?xs?B3npx*R!j(`|ujWY2kMo4CMjN&{Bk zx}1%Az>+ScedHe`9jIY^u{&*weN7&u*dbC#?;1Cmfa4?taXrvj5Gva`L@=qPZL1i-OT^Sod~t9nKhnV2nRM6 ze?V0j+9F%(C|h-O{RdUGAN!Ok)fSbaID|8v|4o)18(Os51G3W==J-7C?{7Uq?oevO zoBfIwe(TsYM~Ag-){)p)o(8zgnxt;o*}XfQ^4z~2DGtB(bUWI-f5mqmoSo9s_1ta; zb)ZVYYBkqfBCGLSh_0~d?yY65+Ju};L5W1jc8cS)mgVR%tNfPgHK^kMCRegpi0ln! zNYTTT9-)`FlrYACgipHknT!Yk? zfFfxvq~3UPa1to|)liS1CoqPMePMmMmioz+GL+!>OfFB{R;?nZA^8_UdprU2TF0ka z?2Pw-*M%jZ!s+Y7egt@Zb%qlay+Rzw8D~UkP+BD(1Uq=M065MVn9aB3%Q3Jpk_{?A zJt0eCCU;#q&P>|uSgWJ_GQ%4EJ{R{>d{_r`c`O*@%2IDAkZ_=g^rMf`&S@y*LcSsT zQYfpcigox3f#?|II??DRA3e!Lqf~qSD?erk0>`nUL;W6kwy+;i-!JbRJ!gum{Bvol zzyGVwLPF%+?OO8V!xHcNy8zdGhsouXkKeKyBp@t|ejAvFL`=3q^g0z5QL|rp9kTF> zq5^Sbu?1nhG4y$ftyCk52ZksqvEbHv$4w|GK7UUqIfchA{iH(tsq{(2H8$%>F?Ude zVPG9-KOdu(#BFy$M-%{w;nV!4FK%HF0`A54^@Jx7|i@Q#f z+gDR0`SP`X?)u2W85iQMv?8~{g%j3+i&bu``%#CJ^E_{i-Th!X)|_&edl6ABH$DeK zrZi_g_hB^lR7QuIW`4LhXZvlXKi=}GE};@8f*)5Or0l-Ewu z0n%e<`KL`I0Y_pyD!aVzNmj5cN7=Go!VSj~or@SetklQTckAs}dzBx-xtWoEkn%Zq zm$Xb<-vBm?-8WfX;iMd#T=C^9{%WVEr?HgZ&gMBdUzI<$Q%Vw2VI+fgJK5X6tbh;Y zaTRK?G+3Y$vb^IdJbIT-(bIOI0L}=+BLDD;voOtp(YeC7X51Y&-SaKRgnU9FbTaP_ z_SfV@vir#CxI~_%)ikRYq#-+5o6VMd9J?@CYFDKO6H~DmKo))pzNLRYSrr#jQ`dcr z8Z70wv`n4I<$^GvQxPNK!4pMhGdycM|Bki}*BxHMyOj}Kc2WrOvpA5s?H(Rha_efl zK8jyh8}=27mn!b_)KL!|#Qj-E_ENCHmfctWD=(vAx2a zFhP%SOX6Azw8}B$D3Tqw#|LZ+oGAiYIoMvj=y>z|mlsG<&)obsmW{LX4+I6-(=g}O z*49a)gvQ3k;nGZ$1F$Nx9p;kml3I3Ut;6~=dwD35SdBU?fuWcmu77%R!pYfM`m%5_ zvzMQrf0V^?T`x zWnz6m?nD)oGacCoGRr&Vfuj`NO=m1vjpg+y)*j%gwJ{uN37(FsAVtZc{?5tgE({8D zIH>6V)OtMjYOJI6O|M0%UWY%j+{L#S86t?(euwD$(O*aD!=keJ!*xu%id)k#OcZuW9(QL1>f-J&2KN)b}mZeTP#mTRj9+` z!tm%R`Jl0CS;VL6`wK4f*wn#{NwJN zJNwbG503TlfxK$PO~V??7SvEJpp?xo-j1Fp{SRDs)4Wp+subTdgCC{%&J#^~6q*!H|lXf4=@HkS^B>L zEimY^H>jT?r*w;;>Qq|A;)Rp~AdB0($v+PF7NjGv4@nfa*&9uMHtxmC6eQL@D#?hE zAKukUfmJ_zHR_87du+c)3qBK1^*F0+<-=W)YQFV&9(G~KzdL(;aBI>$Klp3%d;xNL ziA*_mFq{f*`)*aZ`-uewgWrOeK}hhl8+ino z{NVY{TNn@avHY7icn+>f^Dvi+Du&yG-SyETC-jV$Dk$D8I@F=={9_3>9x^h+_Vdfl zpWvb@yC-IbO4D6BA#uaQE~nWe0`ukjLbc;K%V}yQTxJ(cN6poNLlO{8EAnx%Ie(eE ztbMOUIivNTB#FpRu12<5&$IW8_2Ma6R7R@wFxScUXZ`A&cEt>2FBIe6Pd0?j6A4al zO!LpD6MOR772uKG&y^~Ws?&Tr5_Zu+~v!Yp@VUG`b=k356wSIjp~ z0VjB*v`EL7q4C702F1>D&p4Ku>G$?tC1df0itTH?}Z~h*`H0Dz?fsz~dH=S`yRXk%uq7M`1@s zE(N(F(8?vgcp~>cTqLTOF{G$Ip}dH;n5Fa zVgL2-FAES-ydBp1qb>=ti`0Ux!Q6ZJNIN z0xEpqZM@V4uj??dHhwKByCD2k0U<>1u%jSTy;xtMZIaFQIhR$8&+m)F-&{h&ZdGX?F@_tV32HT zeuLOp1~qA~O}o`ze8BNEnW4%eG&{fY@+AOTslE2ND8cxR?n633c2*`HtEq3XPetOOgcnY!AV~3 z@SE6cm2&dKB^U!ao-phAK9zWfsARZu-_yZ2tw%lKyBMqG-Ubx*Tzg!O?DjiI^qK*x zm^7eJlLc=LHmmoTgTZibiKGr}Fw*_}#maJlAKen(rk~w?|``rUmZ4q+Qr<*wGW2{2V-m zB{n6VMB(Y$-R$K=WV$f%t4g9{=>(;hR4>5%Q+9^b_lj|$$1SZkQX+Pf+kO_aU+$Iq z*5oYO*9u2hr@Zh;2qiK7AEAm8^OGALB&sS!IPZATS?o`{3#ZsHBgs!DUxseR8{B_u zqon+m z02R=8Y8mBJyjwuNZOS6ynF=0O-OwyRaqt@oXoz9fsT$SD2BeNZz*QUrJt$8O&aMH4 zT&89qWxu2i7$;Wa+g)<53GCVv01pEMX=T3{f^71)eor?7Leirl0_#?7NSR2<6D*Kr z7DhT#%BukLwVkPQYN4PBH#@=!E5LIv0Bd3c{j|j(xGnDVD=uhV_IxbL-x zd6vXcA%|-NI+Xb0hn(mnO#H_b;I>7z^VyFRD|gGaLP~Smxv3)uP1o*AV<>jHmKj&d zM7rf4a4%zyvYH9|U%?Ds+5Gyb?cx>p^f^D}bxTB4n~I{f16e<|eq3+phpWNn-t%qG z)NT3HPxAPcL&!kmx{3%IPbtml^781k{JXa3lXh&IzS8TnEtbyW$0%5#in;5#(yM_= z78sahJMX6JRKlM`4B=qGg!@H5NW+8tLDsz#Z9wO?UKP8;L%2tbPWArhF7V1?sb!e| zSM>O?CCeNxSRccomQFah5U8Y8CMYC?Qeqgdjwm=sUSHU)ZaFRK)oTYen}aCVWp3R@ z%3|-|C@8W)>Gb(+uMay^2YaSCn$idcp7ou9g$zi~gj|aYW@^Mi$-6JS3l1j_Ra6_b zOTeU9#6Nrwf^#HchOn8R0%Z+m$D>Vgg%^@!_gpqKv4=9NhzOn&DYw7s3H9qhGPC76 zgjaiAS`}|^-?nqLd)ArF$kV^zwwY~VBio-0e^}Px)aM<4Eo&^0@JCgM>JsxrozpgF zzVEluwDxu^1BM$$q@SyO@hTY-VWuP?WiU6kvswb$W1S*hUMP>On#R{IXP|)Vm!FEx z+kv}|U|dZ7uxV$|f?ur{P_sB~+%DLby*F1s(XE1I@aWxCS?6~i6hZ6pf;0da{4o?b zt)w5jMKnVu;0A8EUF^x_2DxX!WjzIM*{^hK?K68$&vs`t&^|J#D!rXZC&l&UvQJL8 zQ9+KvqkIl6ZCkvoaXYnYYfP&KJ|@E|7gXGhT5G!m6_u9-#TkNHm0Y9urpf~kdb_nN zEFk-$Die?NJG3KI#^Q~?hgTYzS6**5G&M0yd{DDsc~5+{@BpGtJWJ^!_c-ur%EK#l z*`Jp?AW}QW^g5?#NYYNNdVE@+#XPcBIUIK^_*Y7IhV!v&A(z7(6#|VtKyC$*y)TKi z=1xo&wt=YAqkAkegRpq0HJ5egrgN89J7wa{)Ed@js?J!oOEnij5j@QKm`%f^8)QkolT`yeA%R!=Oe&6yM(9C?+x(HRWMmkutx+gSLyxDM=qGo<+ zhvK!7_RrlKkx4yI#hy?~tsrEm!cyh3l_q0Vm~P~m;3P$|d7*Zt#pbU})pRjbQT+%Yf9_Ho>+9<)i8x4` zskVvNj{?mE&jx@9L$ci}otHaMchijSfieS%hLbbE4NqoYl)A}H5LD~ucF}@?076^> zGBr~cadT_}6jwY*cLGi#Sl)4Ow)Or4|DzF3MyK7i(aqBggV4v_rBz9l)qh| zsGrF_Pok7G4p=$~pJqr##OYUFAfj0d&ugp(=ZWcY!q=Jgwc7>Et;ljrigagE&J+HJ zpR^#A$s6`8H&Rc?=f9>6Ej^3|xsK2!?j z1*%3-&Rn9$=Rj+P+jBot3t(PPitwmv8mC*f;;k1g1jVGlC-3jF4J^-Q{U^C9TCa`a zEuOM4*w*{;lj0un??_5(x|0dMh&Q>KN-k=qW3PsqKNXPMO9ICs6c|A^eUemQ*8qdc)D|)=sJ7%q#VP~E3HjmuaS75v%YIbkBK9wQ{ znk+`lZ%@dq$I>9D(kTny-laMU&$~t&%zu<7UVt6}8(b=#uN7{^Lmd~au$x=b`* zRKoIA`H7q!u3!ZM0s-^;M^W|v0i^zYEylCxeQt}SL{LIs(!*9)V@!v`iM)q8QD{Lf zu&G}MquJ{H!{?an81pGGEi3R^WIabd@^CJI*&M}=HL1FTjDWK^o>eVwew@JChb}Na z)5rso1;rceJ~@9QaXRodumvRzr+C%^+z08;G6&RtF_%0_C{U(wI~(gxIi1<)%>kdm zzw&$2FkyT9&kzMFx7Y6|xvA{cC{avmQCwe)1x*L;sV3wGJ==O-b$EEbc_=SnI;jNb z7N766_d%e9Ti|aEpPRi`t0gD(j7M&GB^zS>*Z>yp=Cp5>lW~gX=mqu~vsK;^hWppn z5(~k$C#X3pg^f7gHd00o=9Uq?6C^b?~!09$o@7b+O zcS*4`7#Vg#o7yY7l3swVtQO?$Q{i$Cf$j~T$gbYNxeI$;^NO)-^oYcaVWuDEO1mNl zah7~y7aLSy<50(dO1FaG+2yXGbHQ)1*5f^a{!um+_MP^sTs1hJ$G3x&jt54f0z3TI z{n4Cv$2J?Om`Ob|`QI@xL)op9z$IbNjzqFhODqyB9cLr|3~WB4^1oQz-)9v7sWrWc zl{_>G8!h=#ML=753tS4_Hb`J~m#dRe+S;hc>Z?9#+wefZ)37P`>EHNdBswYuCu743 zMFI28sS%M9*Vx2@YpQyIzNB}tzKl)N*=L5IC(cv7T*@V4MAK%+=ERg~V!G^S&pIA~ zs;}1W1;3RztY^m0U++KVdPa!VFY?`YePL!gVpVuQfxae4S znl-B9HzsT6qctLW8S0><=^|@Z05YNWd}#%H7B!kvhWQklrykX~Gh40@;9RJlS=OVk z=Jm!2hd=v{O@-`^3A$57pdZoeyeDOr87}w!@y)GiX-3B5u*(Iu3!8pQ`du&p`!e3&v22X-F(oUCx#M6`_%8<>l160QgDet@qu)d4CL5+d(xR{_06RIw5ro z?^xPO+bD3TUgVF{$lk?WHNXDkyz5ElRrPLMQ~lCp38xk_H<6*J>fO91+N$;V`}DWV zf^^CqYAG7qAJ4PZOJ?g#sxgl9NwKcM3`v{?tm3=D2C}i)_53vkr%soXuaG83e!Y5L zOZeZA{U799*kAlVkT6>0&OToK7Oh{&agh{ot5z zqrA8|S}hREeISxmsj||!-t>}T>brS03p_bF3u8%H=zfh)o2h|0)vL@em*Ew&yL&gq zmHVnT{iM^@e7x<~yr70~_R`V{he0o5X?)6zaXDc@?3 z2}!nYN-gLc4|K7P=+sr?1V%Fbx~c0Ve>OW0Y*|8yNS+;q%YnW$UBCau<D9P?rDQA1VUwz4= z?2DQB-U!AWDR(Z^Q3T*HX$3w|D0%q3?%LaAu3Kxj-(x;yH*-ql=-69vX(aGKf4VGB z$R~?;@18%2VHOQtyP<2OFdz7;P~0r=i$e3~#TrKjI49t#6W^KhS>5NrKu|8464rR8 zNqr!#qdTb>o$BQHZvDnp*r;w|9m6XDCWdt6_qw%;Zn~5WtS}FDv)7*YBkvMK%dSLUnsKHJiABX4oe`6dhtigK^0eYw5qg7Dkd|ys3A& z+EIE$WMn4k#Up|ix8HdB`u5O3%g z%4kY2E(YT|E>5y6D(E7L!-)N~_6auHlv(vdxnI zJDT-D6#X{1&q2q|D^+xU{8ON3Aw$eZi;p$tzvmI%7o;`vSvhaAc}Bv8bX_*g4@a3qzQuDdC%&QYFK;}yj&!*Ancf3BSJGob@Xy zOrr*uK86fltM#KUpRX01j?GM4M7q&eW7c^BdK70il9D68PPcQF1`_d}YlVEKtunV)Y>Ce)TGGv) z3a>%_IzG{8d^k|rH&0sG(9j#-oTnRqqTu2AGH7Y68ifUX@QvsrE&0!%KZo_o#IVS( zLVJ}vr$tm>(>W*%k~npv{NI0zr-?WrHm|sH$h5R61)$*cM|N)bngqPHwgk)$pI=3E z{_l7|po!c^eL+n#e1{ph**qXgzqO{v@FF=+I`%ZZg~6;R!&GUBSRJmbi>?CUJFDtF z=YGt}rh~fZ!hH7Ox%gZiBmbBEq0^u@)8%K89+z>9;+5K}84Q?c`Wr>>{C|Gs_#Ykw zTrNI{P(y&B+vsc>j4CfzLC~_>)1(QFtNs1@fa$N{I709dy(i1q?K?*+?$taxIr}yp z;jfs0*0i$Ntsi^2cyxqgPmJKcRQM_I(ZP}Dq84hz8hSP*-OYu#Hm}3u+iLolM1HLZ z#Jr2uvZ-Uozy3DxqI(xfv!wr$u_N8=C&xPWUun+~v5&=)Qmxm#`fG;^e*AL=X~=FK zkywmJm8$+z-0m@?5Im>M#X9_z?fxYPM~{0;?UP-!yooyG%8hwCjlr+9U4IVlKT$v3 z29jn4(}v5V;?)0cbs&hLkU{!M+W00PE~)Veq@q>nMV+Uc?GbnN`v)+elL5k)TRy5) z>8zylcFAW&LSh<&2HD|{$>UQJs)&95KM~Qx)vbe5Qf_8GOZaE@{0Nus4N~|%uUu$$ z0iFzt*uZOU!`3niJr8~B6pBUF$>6Q3JQ1!WHGCrz){z_5`>gID9arN$eyP8M0^oZH zi%EUx zbD0#zi(DK&{NgRNElJOuz-^h`BK0@fPBAfGlC#$aqs<=TMH_nQq(_g0A4!RcsQ!UV zeneey0Z8AqbH@H{lRx9i5_+Ew_zUghZ5n%f=kHhVt(1&3%#+RrzXIt}q$kqDj&P&Y z-5ERP-bm$5Dz~B7OZam-{|TO2kP2u3x^%3&2I0?H5Eh{+v0HMkb3J`J=Eek{4XwvF z#cOb;iKIo1DO{1nw?-Zw*2~`LKErjj$K<#CYkVJG2z$K%#@?$TuYu;z_k7+#z+O9) zq*v!ePt;Cno9zXsULv~ym%s2CF31v(>3I0A-jr}ILZSN(!4>Jx-~9Ks?2ZKZMqERIjBNP7k1nH;0{x@`5svKVsW3WfuJKzCU-XjVk z%&$<{>+b2feA(+-CAH}TM6pVME}^Hj)n~fGlIlqx0Mz^f8dHdLhTvxaEoFTG$GF65 z)~ACh|7R+E5T!nf_aVAYxeovJ-Ntcv8g_Z6W@eNwBOh5O*?R&{Xix7$ad@5lCGt%g zpS|~ozK#yDL^dNtF6;46ubsBUxIfH(1H=wPzE98}Fek^;EwzxKpl>1gjLMZ(V;g|9 zJ9}kiMOQi{`>%8LpRNB!rUmjIJN~cb2fX-34Q7PCN#O@e__UgJw+1gjt)~uvyIDix+Q~>ZGTqDzI6RuIbq458P0lHS7(+*U;UqKD$*Qu zP!H#K$c3f;5kF>@stu@?$YN}j-K&!AEp+S54G>e$y?GPY=v>MH# zAg74g13+dA;JuZttwd(Jr)2zZ47_i-Fo|Enx5f*Tm`yDB^eV0J3w3Kh1O9uoqDa<* zhl02z0m7P^M4o^#%V?v!%jWQVIUSj8vL@G9n&UOoSAAKM8v@k`7M1>$#fn|O4>pN zCUZYX8GUB24fu#D>4*W^6!JsBH4Jsr6?Gr_iY781RTy*!WX+pDzw)`d67#$KcQ79A zWekulY!c(q;a?|@kFOjBFR%Xx{aU%zyeD|?zzuFRkqW578;shsVU$$%+40Z#`)K5o zEyRR?a1v-i1QEEj8qEEfxifg}~c;AUh%4J}SGf-F|IrR!Py ztv|1SKtS*1Xr3BlSiFt2bVx%St$cE%g9x2*i7%u&Z~Q6g>lfGdE-vxbj9M?p0HFqp zUfl~(a?zMBr>@(Sm>AQJtI=E)$F+XqDe67BS(UB%py1%BbCRu(dO$~pky-Q&!gH*K zICk!X5q;|dga+GP`BXu<8dmUIEiJcW4JW24U*fCjs`p9BC_hGiik=$vz?77f%2M++ zihEswr=QwL^FJ9kMdj!h5A(SuoRGX!J9oQYZobM1QP!ePsx2;gDk zmitRhx^e)R=LFFC_ceS5v&RBO!U`vM*ZU)t)>s1irA0tP3#|}M3-g~87n{Th`T4zo zTaRe>lUxj@cqT2%Cx$U1!pWSLIAPDYq}8Hkyl$_0_|q7wHvk_H(cPJ9?z{}4JAh^d z8My&#(GvU(QpU@}^HANe0c;g+D=d*?K%*ZEWs#@g+yW|8B(?R;dSlXni$QTLPc3YE znd0Tk&(oFGw8eZx+Em&6jcz@ZE3BS?OUtLpw5J#8%sW6QvPcIpqPP7B=YnD~GHa}SC5tF~N;h5> zqR1v>;1cXJg14KnFZ;Bq9>$yuS8e+#I0FNPJveVSh&-3-EHKYmi=L4DaXptsf3(~A zvhzIn`t6_bks+dMr0`DTXoI;t%eedDO6%%TOoFh_i-#z3dGCkW?`f5nW84jW6o~W@ z{Emns$d~9wU?x9b-<76Xa^MV%$zGJo;)Z{HKUJ1_!sJKy1gjp9R&-kd4Q@$d>6D-g z9Xsr6NNK+TKd~5nzwY3PW^Zr$WuPP;{m_%}57zwvLTHK<4WFezQQ4BO!JyKKv^a;!ap=hfIvj$B9zC(4o=v#Dt znioJFKJH!;;Nd4u;0+X(*rWT%@BSA2hv(11SR(da1IUdeOK|H$H4xJTUM2bZgM@&D)6-}aPu>#hBJdw!B|U}$ffAl2czXb7F{O`b-3xj z8P2o8;Qz!wjMGivJG_(+0Q2@aB_6D22;NrZnFdz)=exUW8~OkdesM2(Gf*d56xbMo z!~u`19H)5Bu!Nf?vE+o7_S6^?&>m27T4SvwnOJNJ;42VP# zw36TQkkA2m^5YV(ARV@#e2_RsvGlBrKRRI?rBSAI?Dv`+etAM>?H5m^xRBcldFY?*i!AqrB65xXr&oeDIz;n`+ z4^A2|gms8}4!URodW>&|-I|K#ll!FJ^4 zWH~B_2O-l6En=xut^)CTHrHy0Hjc{Yo;e?ZSbJZth}Qq6dMloYnKJQlp&wE70X5b4 zz$9&aG13OZgDPJU)5%8n>i%<-Qp|uG*g+P2!?JfV6SRaw7AMW=Gh|dK!s7!*+Ghm` zf{hlHEPDa2-w0ev@maXPRXf%&_*FLts$XE*_-|ZZj;L&0WIE1^(UWHoSGq8B+dh6WcH^4y1CLY zwe=El7NtT6YyP;c9z>hcH18EkR^HMUwTlDs2st&Y7(>Pwkl}i^~p4XxH~nC{tmpa1@m8@87%p z0&nwwm@ibsKpVY`XMM`e6ur2(KgFXxsA>c~XG%O-60sHh{|KdP;~#ZA1(6&)^Rvk1 zdQT?w{<0^UHq#}U!LKAP$~;EB#jhrvpIVJP?%iqa<0xxv%EVXuX)kN9D1)6Vu#Q7di+ODThr)V5DS3B)O^|^CXe{AV#$0hwEeQBzY(*=WHU+9+t+83`to=WM`XMrM6+ zu{yh0%qsW{1WP7|zew!ASz(%3$}xVi>>F#s*;>-G{5g6$e7^^S!MbU{VwK3n%4Xv~ z4JtXRZ-ErY;%KGkS3BN(1q0G9;9xDZsbswg12NB}iK4Whwg~tyln)IlMvGcc05R2A zaD06HbgWKc$p=T@KeJv#6T~S>?X#`>X99TC%t`DW(1v%+1aDucCnkNxVRe|s9i8>4 zN&ffH3X4#)S^oMi>*=bk=$h;FTSn$Roq*a;oqrv)-8x9%J=`T>>@wVx$iu_KU+r>? zloU2|Kp!-^#@fhqVa{t|KI3GIF{|(socB1E z+uVH;g_9C+&?fptRVCd_!Z#-OkPR978w&6AkXv=&9Q&zD+(3k#r;>1#cyfLoKMyd@ zk|{_0DyQHgyk)Ys@6ZkS8w{H__<6Xek@f6Uekhc(`*uNPKx}SEfQ2tM#hhC14Drr#(N9#<4emEj{sjlf`d%dMq)i z%%(OUmq%v*3h19VZ=pHcK7CC{Ti8fD%G2#Ll_f_adcRmZ zfJ_2sUX$GWC>b0qip6sx{cnqKw$@2WLHU~xI>4S0f>4O=bjYC2Zh5`rYGNpdC*)c0 zo+|GCS)JfhfK!rdVHqq8!e$c?Q3>|1I;iXLa69$p(v#R6pmk!YB2*sv3QqlBy}&2X zVkohv#j6Z755vzbHolQyEHReg0PpLe-%$1H9IFnJRB+?%ifU#6Xp2uGa2 zaO$qP_d_+xa&azHg}TpCtS3v-@UB{LpfqhQ3?it$X12C$5HXq~KI2B~iK0v(?s#U0 zGWbc>6$DjO=am^ozBSf`4T;zmXcVV32!iRyD*Xc~iZn`%J26~&hpT|>=j9C@d<&>L zm4>*s`aat=%%g z*V_JU<5hhYV?|&p|dG9gRx6w5-c(xzeRu7<*S^_Z}VAlNv5YK{E=#&I{oBn zpCK<8hjEPQ*Ia-9z`O#~gs9Czap5RN!~xr(+gN_)%w&petF26o{1-;39Pd5n&Hopc zwq#Hksto^%5e6xN=cCy?_j-$)WiT=G;nGipNzKJ;IB7NCItyB4r#Anc;{1co{DVlD zq96o8yDNjhU%{jmuGnKRu#h1`F)92jP#+F;+sWm3&MR^bJxBlw`>tF`FAphVTFY^v zYdK087`zCT?`3zdFE6Ex@W=u-P<)?vyy!;=;1#avDS$L=1YuK|Is^#;<-%NBXenu2 zR-IkUX{sYhkNH;?=}{E;;0@viU~x4;rB4VpVk>C=lJgJxQvnNzDn#-{QKfM@ z`8rVN7w0Oyw2Ed&aI0fHSLYOMBm>lF%kUCFCw(6yu@iL&vne0{sQh?=jIFU6q56B# z%l`s1jX0$gsNRn;G9Ko}Xv^WsYCtM=E|HXfv*O(0_Z}!rFPfFylk3O6l#24h-g$s# z8ts=nKk-5oXukZ`?@I1ZU|z?H!kN!EguAwq5V%@p%O%kS25NA$)c_HPldaGvAYRv> z0JVjh~g>4gAq#&e{s;&p=^!^d(1mN}V_Z^DQBiFIg|ivqq1)yes2hu>t z8pu`2mfja~hkS17_n+=~P9X^07;`g+KJUC>(=}h=+?}q>kCKFl$@XJr?UO^#0pkNP zphEgf15J%h40q8x!!^l#^}>}JLySW}l}iK=m+nAJm@ZCgD|%s|s{;4#tPGe8F;TZC*;| zN4qF8_|Pt5=}G&kNR4sOdW{4q(7M+))wDzQP+iAUXs!43)PAPa@2kKQ_eV#MkQjOj5#6E+nLhDc$9RUGP zA+Y%Rz}5Vg*GVle$4tc7V+TJ(nZ<7xwsyk&{BTGqmhT{7I zDWfx#P(}pXn6!a)DGddSF-Yek303Jmprj=Zd6|$Xh8zY~CxFs7Fvor&AS06}BO{aW zRY#s%>9$-k68k9%Yfk7x4N@!sP>ZNGx{LTB6y+!`qyjxmY}(iGkc^MxEv|x0qai9*Uq4*gKl}I3x$7_i z7JrJo(~tcYQEsg`ryNw8dCop#&xiuxv~+Dh-@bhxAP)=tBNqP8>ii;11ENS>>`&k@ zt5JqoK+Yl1E@*|tXiikW1CLP~lBi&tEQZl25+XhS*~^zOR0ajfSQn)kiB-wbxG8(` z<*?p~c>U%FcuE_qL&R2QaFI7CGI5i*<29%|feXfvcaXhy^|3p^wqRWQ?W8bxY~3A+ z@e|fz1t70hHWY9@`i{rM{!k(yA&=?Gb&3obj`@;P96!v?9}HArji~xPvXFO_FuU;* zmi<|fkR)VZm3Pp)c`Bd|=Lk6Gz|c=wYpc!^><`QSX%f0Xf4d($eyy!DG3q7JFI(B8 zViqp1st^<_*U+adTKsaf29D+WnjlM{x=0ZY;~D9j^54Qc&ywEj>{*!ed+K&Myhjpm zsjy=9+MD*J0&^_wf9gOfnz6gfEKs8qt%?2NW8++v5oe88`y9!>6jostmKGO=D7R*v zi|l!rT%nhy>PQ2<{58yfKve&ulfWMdGtiV0zlwpkDiu-%iJztsD!hDW_#5ruWTwW> zB%qO@xD|tOEd#CU?c+l{-WS-@1j{sluF3&SmOoj|>HXGAc(%dSA#{(=x})Q!Wi_f$ zWHyQAsXX2bv2L`K`4h2C3ai~+1wntkO*japeo30AFq}tb9UhojEQ4*O!tgN zqe@Txi7FR4++XeT)zpl#d!_4NG;zyYB<0U^oAd0_;piDF7kx3GVM%nCO#^j<)ui}k z8PKy2s@*+FJ80L@jn>o*E1EACSmFtPAlZKw?)glOT0HxqyBq})&&=$zW5@vzMw^}? z4e{N@p#5GTDHv|?d7O#zc3Nv?5C@*Kdd=dZJJUwFV$HrJpq2K8&c74+L3=j*>)j4W&spaKt@FlqCbvHxiY*d6&q8&A$dAtd>=`DSNNOMgOdl`X8q2xz zeM6^QnJ-FjsJ%RwbAH9?7}-f$AhUg&(79K``}Znf1HX}~5ecrYRx*z1UQJ29Qf*ej zRM4OLUi?@6<Z6FstJP%idj(Q=A@J)oQC>F3!)td=EHzez78~G=o zG$la@;slWWxZ-ytaK5*QaX8>}6E*C+N|#!2RQ*fM0Iy11bL1s$Sv*2Cr$a-h--&vFGoF`1Ydy zJZmV5!Eb76<8Y`ZdN=FbDb{&kYLqc5 zCp?#NrsM;xlDQO89t3^=8hS$##P&R{^q+0zW!l4^SKiqu%-HCs5t~O0$@r+{#&(-0 zP~L4mk>Rp0JE)VDmrjq4(g0c#ZI@|w*eGVpeXx#Yk{d+zIl26pr`lFqQZP4G{Tv_H zm6H_ytyUZ6SoS}qaRMzc!^-}$YV9-1FGd4Q)oyj_D`QJ?_RZHnEE&C?oSx!Oyt1>i zb3EBlLc_$2y`*7KFL)y&BC@j6ZvYoaWNgd$7xpMDLL0ebA=yK=ynk@}!afKG@?Duq z72lER$%k9|U?s={6Ub#K})t-D^Dtr1Zy+xi$x7MOgxCn>Y071AI5#+T2;u zqhHFNVJv!QZhO*=N7VcND?tiAAVPLZe46~4+Pm8R1vZwEQCbfEBd(!k3 zlC;lBk_rl)5-3&S0!i_)0(sq{L!h{f423=Ee^$&Uxcjq3f_(EUflNZl0VkGDF$Y?7 z%}c;4dI@L`SCzJ5bD`swXTUD1M^8L z!Q=tcv1s3ru+eOSL1qpIKd9?S38S+oI-|`IpZExeh(bb&7e(kR8|ZS~q@K6dydBRQ<8&Cy3w&Z+Y^?avBlhr|gJE!Eada+;SdIPl{^q(k zDY2_TN0VPkIJZYdLV}v8HBw@dzc3?tpw%mX%;ytFPdhFSD#uGr7K@z=RWYkx zR&5PyzcNR6-21mG4-LEgT5Z%-{}AnRH4GQS8MI(TZwD5!YAbYp$H|!ndRYCjG_paVGj3ty-=HVvk7Do~@ z&All5;CR1@7V|5QiCQ#=iH4s^k5tNIn}zn7#!M*MTnKHpT)h2o2e~}86uj>>Gk^C3 z;fT%%V$FVFUWe1g$<>zywm~ntR2o1!Xsu=M3RzTDVOnKoZ%AyLh~=K{2G{HZ1XP_K5kIcp*%mzz>PR&I&GwVm3#(|&6NdR_~v z?qP2Bq4C8b{p+=x@J`1~wv~`p@*EkHF~$5unam?bUuaQ2vuMgde2Sitm{RO{$Y z$T_M`Lb1trb{PphMhS~`LCQA~x$L}UG;&Ph9RL_GOE@Q>RxJ3$YO|bty4EiH!{s;& zKx^ayL%*Ge66I3NKa%M)AJ*C%LKHSD<$fRHt5*$ENg*sPE&Pb~8L2>TaOLUI*8V#A z?LHJQDWAs5x;f>t!c)m0|3H1`Od%u%%nwPUg=n@kdU5&CC#(JsDQ!WZL*8>%kuiv`J*vCCdqsx!Eu4f^xq}$U6x#5O(qW&`i9JQ z1c#2@5+KH5S;Yv&{qOTEZ1jY{ElPYFW<#m>*5>X5B)ot@pJe60S-UJo`llr8?(55Z-rknyD?9~Z zv!%XFq-eDiK^)vR0`#V%xSy%M1poY87=e>6Sek6+il}5$eA{dT_e=-{lZ}pYZe2`D zDsPPE=@*%FiVzyPOx{1SI511;v$PK-(N6)B^*6$J98QOC0EQNM+?~X0g^eZ_wkj$< z0b6V&_nvB3k#p;#R+kCPSFJ~7Ye`d=(fj~h>iz{+y@DqdKhn-_1uH&6v3$)i%2I3yl!llcJwN;&Uc z^Ty+c6z5%JS|ncm1C~Doz|;K@`18C$rn$fUX;x=c08}{QmtQ{*FZBX^kt~=q5sX+2 zP0awKnY{u$wtM4nI*<@xa*ot3TW*}BR*K)X_#zyRIRPjp4*)agpaLi;D1dH2et=!= za-yMry1Q$_5tSZO~07f`s<=JoVz^y1&!#VPep)&%bFrTtsjwWl1b#fEYa7k)b2 znB~ZKpLlOxT#4)$)&1A^j1EVEQ%Ze>G~8HU1hhSe&`3cQ41ld`2PGUT2K1An*_O(z5w{>>IRqw=3iP`kOa|uU z= z`Ig<@e``>*4=5g;lae~Ts-kKb;t_$8*@qN9wi7-|kiBY59p~SQZ{30_gG-l@U35SH z{60+ef7$u#C@B5ijh4O4__vQkfObx>x>WT|^+0RiW=lc^@9&U8XqH*fYdPbKXU(B? z@!eyc3(xE@H?=eG!#3I@u=^cYSy>sE&gAg|0UG7c(Wta2R4TUpws5jx05&HF;KnzF z56QsI#QVCwvnev->0hME$Dr(Tm&a6bx{1JH8gol`V>o^2*mux>0}2y|&>P4V1Vu}U z$>h9OrB@J|wtoEa{Dl?kWUWjtb+8G8MAr26k`9&*7fD_Y1R@vMKoXb7hYrms7y5Ro z7#n`?5eA9>je6L9R@HAP&WxlOqZ=7(*xjndk9tPZ+0~V=cW`_x^{?BQ$Q8vMk#5cW z!ej*cmoxMKr+z7^dQ@p_XA7FSUk9fpbLxk1R6FcQ9KdOmsa2=^VP*eR!j|xOFKNmfWsNqdcX)tr}jnG_pR!+REt{=DOY)mbK6fk39Mh z@9>h<*LY7>;eO?ZlZFscO}+3+RBo-S%^-5^{*#3ii#7`VKeE?_=&Z6$mg@2eky%;FVePd#hXF`UTP_mVW7QYMg#=yV;)KsVe>@4(M z3m{+NvFN0LO-CL8<)m68(Zho^g+y`N;SdlJrMI$wM@RD=%~g^I6t7h2zwhZS@S6Pe zz&f!8*b12qCDI14nwHn-SoHRW7SxiUqP5`^d7)Jz7Zh*QX0Ejwc>3?BkV^b~} zo&tTF)D+gCo>Z&v1)=w~=OW1a`7l0CdkNavWgKCCFrOQ4XDds9FWm2yN9e zU-1aBxS&TQ9;QMLrOnKCny0g!I32JIcD|#0bm>W8c>l|?+Le-wKLig6M$(!1(VHv1 z4NW|iE)%VD$G(|9K+4m6kHWzTDD`B~oY0gqF7ax!5vWIQQ>{?S2e*~7a?=Y~p}S6B z4;6g-SAO^NKS!!lsjW(W{q7uo>?#!9mAA6J?81mq&X&s6dqUJFDNrTl^_j>qu3o+r zASzA*a;%*-O;F~(!!E-}DZNI^<;!=0UPS*k%7 zQV@yTL!?jpAyM$=e0JaBc?AzABKW^FNMDG6-fL6t_3LZI_0WO8oADr*?Ef7SKIE!_ z#zptDu3=ChG6*;ytkSA##DF8schC}K&ZyQ?gU%nbTVJE1fD;-+jC+K zNQEc@YV-EimSBHl@u$^H5I>MIaV8ItT@Qd{4f#Tv#aAE=RjEXY@}I@9QHCt=bGX!4 ziRXAUTV4~DEf_qA?7H|_vAdBVc@AI|SyHq)?tT8}wPP_`CJr2xf`1GN2n4bZ{E>Ou zmm|#OT!R0!@*gouu9dYjeD7o89Dafh7CWYb*>u~* z+5_rs$|hkosU6&exVu>W0##>#Bb_pkK^i+vRa%k(zTNirJZOs*W1ADk{n z#-9sI=E&@Xgu!J`ZoX64#J#4ti2l2aV0564N#{`+qg~c- z_#2^HqW%$}f1yCgmA#Ju21SWtoyiCewhcj3mGY`pnvzfBKZ@f&rl~m~)(&EW+{V0@&<UX5c(OR z0ZDldU^`6F*E@w25PX>~JZcdD7p!lY$)Dkz);P2drR%guE71QK2Bbl@LbSuwo)7qV zU3vZ=MQRw$j==}W_d2%;=qS38=;qPEhKm|(QNTsTBq3opHh~$MZfMAq6*;O(OFXe8 z;B@FdCp9$js8lJ(k4_hRV^Um35ZfO_Lc@acjg*) zhoC6Hbo0RU082WQZfRXz-L7$9)@ypsna1%wdj!l?+Ppllsj!GGnjCiiao7sQrG(&C?S&WH;2Oi_)1n`w6kGNfLj!s{iaZfd~Ogr6@Y4s0ROEV<93Q6*^v-u}Snbz`K6-lss0$qC{fbBqmTCpeWC4e|t>@ za$xAgiGFteBP2EL5K8f(h@od|(}y9cA=5a9^H*stHYcTxhO{1;g@X8pdSNaxpt7ZV zRp?|cm^IL5&Ko?3Z%*xz`vhsz72iq68PT!wPKwcRF#0KAY zTn3l`VyU~d0G?9NJ~Y_e8sOmnhY(6^YirxZ0}q(rkvi&I9HlO1{v%DURB#6ey8*o5 zZ{f%$UI3T#QlG>c{}~9mbta9k&}@_uQ5X*@7uB(yp5T$nJ9&?Uyx~h65b?4ROz5Ce zCnbhReRiDq_%6;x2l8eH;=sB z4|vew=(jogTzL-@RyOl>CpM>*^3)ELzK4{!gXOa<5B;5JA%o*}&DLfo363nPo;DE> zS5XKG)>(HT=m1E3e1d^d^q45n51n+9d>#Qsv{7Eo9xZH62+Nvcc~viLy);&a!bWrA z_^b1I`uz_K_%i{1*r|D=QG7ikA0J#Cic zFEnqrJ^uO%RDfg;%zktU(vm_$Q;zfU@i<4qhK&NGZGf~K`K+IHZd#=+{}>JFMy|h$zKkPtAiIP&6q^icPC5agdhEi za4<1Ne0_aI6#39U22_Z8AO2=G#Uc2{R1%*KD_;C?i^55g~KKS=gl<08!#`97oSMOiU{2OgLl|PU8m|fh64H>4SxBvkLTAFr?c)Tx{vGhDn7}E~Y50lEno*_7ayA6t3hRtosU9>?lXUK~U=bdt^~4od zVXhh<%$Fb4?XHG8EnSF{0qT~JzM`#16%=+?^>2R}xvjx_!98*(nmt8Yd1!7cc3goVZVk)X*S_b8va;bgt{pdFo^NuOK&`S#cK zo@bE_2s5&H-LH$%%z-xD=-YdYOD$FpGe=^*u56O=Xz#gDrH_asN_ zeP{0xA%ntAjbmy~XSi{+Amxv}4li6Zihw8Tp3Akr_ga@v77%!zNwg5zQQVw-?^VzL zNay-XVa|C_=*#Uw>(^V8-2ty58ajdk>#saXY!`4@w*sD@P&-Ko$QE6XsPIuLXH+{W z$ryc0uxxh~ba;)$u#HMOe=nwD{HTmbA5CTF;O&hJ8n@%*xtsMsV2y_9G z)uSUCO3pxUo*P2^mN*b7uLhkk9TZ(W-JT_uB5`8N6#iUlo@ULDrXPv;t`h3`;V*46}pz|tFV;FnD zea@(ceXR#){w&=~n?~a(p-8Mv8!U45!zd0e^X~i)w1LsHI(Ww6#T&+OcH#e19P_vbD8^ zs9S`SK92vD<`}sDd3wbHT`36Kg_eWCQovG1fw)c@NHO1T0>(=XT8%{HCZ3dVBu`=C z&(YGVV^a?;ol`vvCSbIaEg-T10A1BZ@1+!}&H_d0my>}=Txa{4(Ke7q;^vKl| zq?hz*l0~b6zG2vBbCBRs10sAw$1(klgIUrlg zavgq3)Y@$MPc=;?Fw5K=Oo^UeA(83P`u@7qP+uaP8zX=ge{TEc(cw_}uZZ|Mf%aQz z>y;DiLaj}DhTw_uj{E90<;o3ooJ8{!I6dyztlPX}Fw=NtU@LSOAepQ7C(z}8@MJ1O z#|H6*zRZhcHwI-d!7 zw7gN^kwSo3Nr6_?ralAj+-Mw!i+BA7wzBWKv&qfPZNTI_KEM>l3K+X4yA%mR2>Vsx zkYK^mUtw@N>o)@Js?Qa=v0|xoF}aZHb9R=S9F3O%TF)fFb+2e1o@V>@O$a_9xewUB z8WXCqcaEDQZk*_WRtg2`i7OLlqf^m>&MPz~P@yT>%!I!*18Nn;rEeoVhwG9$0d-wi zwIJ+L2hW1y;SW-Lg6k5;+KUt!(1F5B6l2Evu%hr6G{PhnEV&Asp)(V(Z~5(GnbFXB zwa6a@!jf|MkmxORN@(gaGZGR4z}RG+E9;Vcwx+5UXPS|C!h~;ONTC~zwsQOBmQIpf z)j;2SN$lG~uZl(0da93oVN}0TfUy1DiiA@Oy#GQd3g`5^&-mbAZ%@R_%lrH6iyt8u z#)f`!j2^7WAOafa*MF4rHRJ$YulOK0l4UA^G7I2Q2~Z}QYqVMr0D5%xAJWfWmXmp* z3s;9iz_xm(Vp$+s>EoH=mu}(K;`UU%d;*5aQH-0Jr!SDTaZP|dzpY_1mLUz`_;Z=N zhGfyDpk*wi-W4oeljbI@5#_7FlQ>QcOqn&?MHzpg`vVOODkF<#FdrHvX-!w8c0?BX z1=6fz?^(c|(Lcc{gRh?eAGOY8T$FnKvBBq2rLo+2&@Y9MwjYksuN)o$qah%^`(I(O z>xg6oc~QuI+oFx^<7u(^2Sbar#r|N>-TKMV_2p{4({8VE7?xi)<(Y&aA=?_IkoPIU zbftpzp?U)o=fc9oXB5hVC`VEA!2T9Wbk)?Tetah1W8$B|N<~o~{aH&gZ^OxRYyK8HGRWKFz06OLbzup{(8D!(2b>=ah zV7Y-#!XEEqQ8J5P{)`` zUU0)YREvlnHc$>M#e%rlmNvTpj&v}1_J=cKhvM2@rjvW_i0mC*A=(@8h=7qCQEv)o zC;)>$&;z2N>8}QxP=pXtvBhg|(f&k2g9YC~8yCO_y^ z-j3QU0PxErpzGwE`Mg@Dq1o(2sa&a(K8S#{0N^$XfG|~t(7A%+VNOLvCkWez zv&Q|O4wipUud!lXyh+-LNxQ%1|5a+c6r|r57~wS&5<#s zS-s^x+RBtC8HpGef7c#pDtg#&<)2x0NVS8b7Y$Gg42I6|#O8F!`_u?OCbpB1)tPUz z3Q>m-L8ggery>E4aKO4`@KvbqaqD1(g27d;NqJwJWz~}b>&BNOsm5%Ev!qcQ5W4;BI~BN30r|EBnfuy zObv{H0_yx)~C zp_<#^Z%oonP0-r{z0}0S0hN)9mZxVzA2tx3w);^8&6FIs zj9fVhD)=MCKuKTXlKyc-J@i;Uq65P6z{Wku?j-ME4DjH91cHX@I5B8+ugQL zIe%S3che3iMZ;8U%keyi+JSXBi>{nob<13Ibt79UxO|w8apF1tB%N~0jA8FM)?n}8 z(Fc-_c+;%udVJ>5_8iDKl)rM@)7d`T_>JFXm-(NHD=N^j?4iwyZ4I86e`0=nJLX35 z?H3I3sqgmxbFZbTErfE3!bu$$*tesOLbP8YrVkyCwV@kFuw4-(YLbQ#6OPxt<`-qb zEz%e;|6BRW#W|O%M`CciYAusf=&l>05$Z;AW@5T_S@*bX&p1{V*?IDtZxrQcBdN|| zEmn9EXCF^3Nl$H~vB`vei>TOF#hiS~zmqEZif?19ZXKfX);`ao?(}@9lW1ZB`>iBj zq5XcUq5NG9H0=s|r5)Q-%zSw$8w*P>B!*^_LxIV=!-NKyyae}|j$Je@sVhe83+U^3 zJaNC99M95!=+=mzKA&=m>Vk~yqXEJskM|O>(6S2J7whi?mmMtLCL`~sC#%)3-?0^S z>x%(|IT?0&?w=pb#8y@}HN9#)syRe81t;aK=)6k&df)L2oGNl=4P!P04b;JGa69su7B_ zIfL=S)k5sgMX@rF$}~KJ#)$s8p|k}H-puc=VrbX z5DUC|%kR#G=S0CK2Fh<^tA1sP5yBd9zMskLI#*Ea8Z24hTP8K;6XfS9s3TOZ=ccEN zB1rJg&$6tS8ho*is`dy)G4*Vvc;?t&@w?ioo4h?LpJxeS-(#a!duX-YZ-x&dOmal!HMvM9)bsTq0h-1`*8+uDYKA**(5B^%6Hiu(`Abb} zD?V|jAA_eIq10~{Xl@ce#0pJE1x{t{jU;BQ^!Ev(-o5ed{58_y|6sjVhp%TRkB$p#?bsG4*n ze?(p6C%H5Vx}USw-Pcctm?72Pi1cyX<_gZL$984nypf3U%uJ!34Y4iqT~l%E{iTe3 zQzgTpEb?1$>L_rjte8^GES~R+UhGgw@DHx;RQbs)4v!d9Mqz8j#L?R82kg}{6Mn7q zxXnv;8ZoWBQKghca9~K>g54OsbE;_z@sd5l9OSg*WvDesVS{^BZ%r|wS|lE=X4qa| zxi>aK++AVklV%pTeL3Qf0Yko--ql;Y*tAwg)~zI}=az)7ff`qRQ5^bn{fj6PXsJvd zZCV8;EGSqaR^$&QY@ZEbjWp8~pSM~@O)o?J#5a*-08-U3>mD089&=i1-g{z0+aK(tfPXN2I_o7hca zjPk&vL^*g6?BfsNcHPg~LpsScRUZR`h!`lk_0n#xYfIBkkB*O_O&=drl4UntOg`pN zLM!Hr*68uzfNgrk~igKEF zf3>pRVVdMle)LK8m^^EJ_qcLowfD14XGKoR5$q!|q#&Mc;aEz&#NJxa1e@qUq(|y2 zem$Dh$V*|7UXA_671C-HWRJml_6~Z}dI4&DC$XGZZ^o*+6-M%f+#>XTK9=qAj>UDj zlhng|v}NNOfeDlDcQ~0viscf@&a*;fW9?rxz4~uTXdbdIWFGt3!BtKN3OfS>I*BxR z;pr_fkbX&~cok0vaPkIJ_Md>xzxQ!>21Mlz%OV8f1lN}rHR}{Y3h8?Ey3gWhmBXm0 zbOXd7&nK@!%>X*V5Y_#DL2{6?a&X? z^^_OyLZe9&cIE{&vE3rv#*FqzL ztu0~Qcr|x=do+ynWh5n;DkQUD&RKaIQh)y!-!O-9m=S=9~)Try)VGaf1`~q?vDp{_zuLBw-iap~I( z&Xh(i2M|){QxjTgZq#`m_jA{!*EsOJMBVESqk3lhQoY@fx<2-l*bGsZ*nQ)HBlK>7 zC-iJaP13nSR-yO!z+4zn``F-tG=BE6rk%bCQI^Z8cIHHLF{=y$9lVJeldd^FNoO^C zN}%!FNdEd|6}-!sdQcSYEqjk_~!EGaA9yH?SH z+ST`~?=5D$*w$5>S2ggrcC~jfRYk!x%A5Np(ea|i7trhF2X?*J5!alr^stSi>E%ZT z6ZJ`km)GilJO&i6$^XL9yj`MsKCZDl%{^qjt?S<}bw%aEh9+6z`sn(rzCFPF8T;e^ zl+ZD_L*u#n=UiveV|}Mk)(5lcS=ZJppsHU-OIiv9`gbQr^`Luo60J#9EJB(&o8+dm zn4UA@!jMpJ4JF-kE;F{*k~ZoXz$Rc@8e)Cs$&v7G=^si{#JU-}?ObNWv4#@o`kVZo zNlstvKX6{N>u-Jd<(aAR_|mA?1y34cac6zt4Kwm&8;R|gO8mH$z`s{)qlUhs93M_t zi>j=qY5DjsZs1~`{;15H*g23d7B z)GBQQ1djRMZ@qoT`~wA}T8+^BQF- zH`d5F_N~KVAySRoyo4--eRq0SLN0XXPqpt!s)M))7b-pN5~p-_2K{m0rjmQvoj2yoIbi( z=!r%4Zth>;$9hPOzXi-?d$0>e@tww<#p&{e!`~;v%tM}u?;pp9MBzO=*swhq9Mser zy#0exyy~K>K*OyvvJ7mO#B58^bkC4WO;WW;9A1l=QKsNH5@QfLr2mLQa++;S5EN_rM z`gb-zp&>UX+WDUMcf#z?ZC_4Qx_1vp z+azlIj_#w4ltA}o0*#m5*VVYT@El?~TpQZBgMFn-`koXTGd%HFy2$o~Qq2LR!e&@~ zc@4(t?KQMOf{N=AZ%gtnQsX(b(l?O3iXkCmaM%{BuP2YNtZ4cO33Zl%UMv}J0BIUN zcUS&O3%YvWIS~=?*Ih#$TNW*EF6yM$7JZN`w+`g2)Vuji{*7#PwwAYo^dgoU57vVn ztZY{iiI1MM>!0U;AFC2H8y##+8|Yd;{c#F@tPTC6%1SDk&CK@Sfz9o|v0R@>k%lwA z#b*$r^Aj0F6;)L)+j6>@+N};6!MAiJD#S;1It+3DP$qdGhWXA2b$550dwlEr(^aqy zmsJV%y-WsSP%LjnzzS~5Mg!Z2+k>5b1LAlq$Ee-o<4>dNoL2J;*8mwyo=4JVY{Rg& zw)RJYSur;j{iVLGL^X42$9q@pTp`KNL_^s+>cd8c6X%gn8GWilbB6@cfjvOFsr+z@ zRS6ZY+uw-h@mhp4A1H{jk&PTDE$^czYle?CWh8;*M152!d{^vKMIYjoX>kol^Q`N% z@?cjSH}_5XTXJbkBEQb(K+T)*nTu9Qoj}aR+w`bJ|Hk|DHR2Lee^cGukEUk)E+Wl1 zoO@NaQQ9(VXzT&%9PPY7FN(tp#M5UMU3b>q!AwwvZ4HjU%2?ot-w?7?q3H;%Fhh~-DyX`ES&a|Qx@AdqkEPd<;(1e}r+ z^awLi864pcIX!{yD*VJoRJ5Sg=%mP8mbn#i=g4utB?0)pLw*Es`KBba{W@_;hoAcO5 zlo)~f*sS^44UMKZ+9j8R03}SNCE_F##WCu+fS<@__)xF!!@ByivoaCw?GNS60dEJAg&sUD0Zv%%#s!(d@e`6yVQ?$ZzD4Vez=7dmzumM_A4@My!2CShia3R9cPeq4<#{)=XHV*8 zDROff9(^umwqZDe68fV`R{M}yFIuK*8yXY4Pd1}OXvok$_)|*M51$g?mk9bX+zDm_ z0)?2(3tdA9D6Kg$PFw^%!hs)e5WmNv{cFsBBJW?rEE--^G6o2QVH(k>Ii%yD!rpD0 zWTg?Kd=< z<)vMi8m#cB49xo)*wQ)fKtC6nh|3&Tm97$gX*5in>(LagY6=SM&fuM^=%^XSGw~CxXN{hnchomH5Ndyz6D1a|8>0mb>&2mT;#@-cDU$aZ`XUv-jb-B)~>qLd;DOC^GH77Fn#W=2HCG;&rHyEK0MTr>Hjs2 zp1`Ys#ux!R!b?3Fiza?wt0<6laZ@mA+w=s*)dVJTd9IhY)=KK>VMH74OAf`7>(};( z8>wZQRC?&hCc)K43jd{Gd_XDwaqigz20QIXv?)UBuA8}Cj4Z{bfAT+s1SoV+nkZtF z)X=sCWf=mqggM&(Qpq7h-yyDs z2+eGTW+WqSJO~KyOCFo)x zcsd~KRGx?=#o{;>|A2z^fQ5m9SqHLqVE5dt=$4809;j()dw{?iB{Mb4UTSArlJ%`E ze_$=86i74a-P_nR<}@TSbx~jE8N1kc7e+W+%1;Z~f%iX~LlJz+u{{&R3zHODt~Tlx z5lzN>)@~e8_A}Ar6Z|qhSp$zJDJINiZRPBo7);OZ=I@v{KC*(3hm*)nq!<@B3?GZv zd-GfLRG5B{|KZXj^LKvNrir&r_ zNB_32tlsXkr6_`bUHb-p@yicGx|&BuCbPB?`G~~hm7fABj&nzD?iwqaoz8eG3F5f8yL=VQI3kN2J-~t`_RQ_34*MF?U>)<8&=^I zb(~qSKays3w2`zXVwp#is_mcJ*1H;M)db^&QXy1b)_nKzClDed5p{%jZAK7KIed%-1fX3`my54pSX6Zf zHAVTJbf(_iM!8+K?=+4wigwE56l;~XI^Ln~4tyq*R(Lu(1;d9Efcfz)b=7_v4Gob; zZJL!&+l>*KzCBwa1%_w`owR)Bw=`6f9Hk)|_ZgS7dfZYiQ@f=+_1+eblb)@3l}SJ% zU|d{xmv*lI>t^Fp@YhY)4LedUS#?x&0Gia+xxRkB*_>O)^Ae>j+uOG7$UE+Vg_VB% z-<%NZHivVK>vNy={R{=qLXM97KQ#t;xfWCSkTjl;6xV(IE*0V$9SS&|gy!B!T_XSp z$QD&*-awd0;3_)X?9zxGW2Ah7iL3`l%SLRL4*EtvzN?jPxKdb+k+nvWi7hck03R&c zID|&H%xdWkO_RqP8*LWc1swS@i`SOKRX{NQ6njpyjm9Z8Im% z_WoTSsqQol!K~*gzlh(iuI?DnNqT&#GoDvbTutP1ISDMg#jC-Bmf}+(a2`E-@9k^- ztdJ^dp=A<|wZbfz%Sglkon1jd)%>lpS1SWED!=cJ$UlIe?e^;MU<#jc1$vs(2U}{x zJXVZFisVdG(yzE_p7U#E^CzF}f(vAOQW!IehbNh210WD&Ns8N;DM*hZ*JjXaK4Gi3^$I^yq1e5;bZEOy*sfAL2-BG zq9e(C)59TrFI<;J*I#`n^Ck-Gf`1&I62M1ZL&&GFSw&^^_!h-EX0OQS$JNTVmlFVD zWCsxJ6_c4M+uRPO!&Rtx!S<=VD0q=Bcw=tOZ5y+CiyygD(~&P0XmA+$``$pwKW8%t z%PXyrsiVDgK*sO=i&UcH#t%YTAZi-7dlXx-*PwfxXJ3#YCAHzW2lvd&8ZdG4 za;TefboY5lccq@VyvtM}ig%E%;VU9kd2FHQ{W}|RgpqsTNT=$7NwlyTg(oB}jhL2( z?zw)Nkbemt311-zz)Hq1=uo$dcVgRe5#b|ZSQ3Xq?%l@jaB)W09z;Q>qR8gKP zuc7csmfqG`XKdq`y-cx>q&HI41DKcoTdcBZ;o{{E&#ASHHTp$ z{T^o4Kk&-SK;GAb=4#XcHtQ*-&gXKYgG;qo-w;9*lgX@V?274yG$tF|KdkJqMh0`D@KnNHoZ_jl7=%#@i~|M5rW&liJtwQP0NbL_X?R!3Fk5oe8(fgw{FlD7gK z8qn+-OIZmw5BWBxb)!slFIX$X2Q@8cR*ZDAu;lZOHj?nLE%8Dfh%6HlUx{jdl4*{j z8#sWZoiL+ecZ+O%L%vSP)dqVYbtU|5J;!z9rjz9kdto^+ALNnxUKB9Co#VMnE8U&fLtP+ZsxaLeii+N2R3y)UMRD;^|<` zkB`uEli?nsQ=Du2UB4tIJzSn**&UT=y`@XLA-pRn8tX-vgjH(llJsP=W$OO-_h6-D zWY90{{w8a<^Sc-_4GV79>$~4!>N-C@WIaok}nZbo~! z_k1CS7pRb{K}WU6(Id6?Tk`H(LKt#Wa=hhQguW;Od9_oIWApnDFeQdqSgNE#WcX2i zig)x1#M={sPtKbKz169iyg7zXv47k^^t>+=y|Qh{tk-wFLApNjemmpxh6&(-oA-j9 zO0BNCZUl-ZqkqpCw-~K&jn!%`I0u5xZS+&w>ohMdY1a5XO!=Q|VCpq%y?vA&_BtAhx=#1xbNV~P;*-dBm3P1%fCD{?@^Ym#D1 zxVs#mh!dDAT|ptgVRlBU@5}_3RTk=!?SuC~5p>$)=s&`;3N7(M48Sx>05lX?fCY1 zRZ4w+W4;U03wJv*8XEL0YX9KSd)t@Xob~nb?Y`QDiU{3Fwxz6eDm7+0%fp7g^ZGI8 z`FRxDHvCFtZGLK+yX^wS&qJO@lZPXN%^Dn2gX0t$*pFgs@?Umw-(jILY)K+qm|yDe zxO3);!aPn06~an)JZmkVv$&b~ z_fXxTRZy7JU3?=o>i za_S!=Yo`NnC5^82Jt_&ypT8v0g8eGWa~**J91ZMiZz6LQP5z6E&ATOO7jp~#v9085 z9xuXG$OEY+z_e0FL^1qw(dtvRN=>T7NPRP2DJw4>kucREIfRzdDBs3U#Lbt2 z=rVkqTn1Pp5v{GwIj7rjzphVw^-`z{$GPrn<4Nv)cb8iz4&^xMS7+E!vDDA;WWoc; zKZ?YflJ#|-`3dHKKwl{OWN0R{N}Ss?HPMMzR^axG81o{=anqfRnsPP;Ss7bM5E_<4 zkLxo-O$aqxAnu$L1I8*6=<*TiVmHm2zWk6x5xbqbE;zOPFuVPkh8hu_MD)XYRud|# zqr{VzvfJf4qffo7S;q?tCmwES=l`-)A08Ya$f6?2j(3+)ZebM63Ezss-_&M4ZZXucqG$A4(Mh;( z?2Dn5i5>DTuOOhTu1s5uL)G0CFd9!-jME%$A>Z_6B+}LcW{I0_>c8g{Jeyqrq;U?v z@wy{E#L!%~U@?WNwvN~F=AOClB`C;6YL!Koo>1t=ozO7-YSHLj$LcC82da8Fs_oCg<;6|dnqB&pVltyM z4HJ{&#hp)UCzCC96Lr<`og44k-t7HtXm&q$3cjDok7`$oI9>6yJ4?S> z-#M_Uc*NEnCEj4T^c)@<5fRZS(Xc}n*f6NCD(gk+pW}Hw#o9J9RxpB41qk0NDen$z z>&dZ3U&`=Y4u|)T4)Z~~S$}W&)6p*a^-pV?ecNzUQTt}6!2La2z^C2j#JT9{Uh zfzZB$eSFLbwAI22%M)^$bg0!|Ck4Xce^aZ?vn?WqcC`MB%Ln}Aga$sUC-aCP`<}6Z zgj_(FO!w-qImLM5Ta)2@m)IRM7 z_fT!bEm;#)4zr`(QbX1C9s!DI92@Q&!P2pJ?poLAtPmrfwjLVw_16c<)6$^-kF2kZsw3OBZU_Vj z2^u6pg1ZKHcX#&?-1Xq@?m>eGcR#qhySqbhhp$L?zx&?(#;6~_IMk`yvi6d><^)qK zkjEY7SNp^@6CrLH;Y*B4oYP5-xZFF6@SJzp!YAHu2<{};vj$dW6vbHl1UVY00(%F==lU}; zA4C$H8_IQDU@cpT+Dq>*2V+Up7q=z=TGg}jLE6Qfvf7gzYJX=5Xou2Xc!gWn6G=vN zf{!RakJZR2MQohPq>Og6_UUSf5Rt92fFlNp@bi!v_L!%xUz zi5U$rlpd;OBMxcl?BzV$X&6{I2qTGilVvGqTXrwdyQ`cjLJ}Ts*P2^Mh{4%`OC-EVoX5E{2g*qPp^rKoEA7)6?Q(u#>h ziL9hHXUm908BclpLu0$OdAI%zVp#CzHEX738jrouuYLJ?==c{=9jgHxBe^=onbhg{ z&hpqfr=>bewut|@H^nk7_pF=n;IqoSbKFDd)QP1HVd3(Cx3RSX5 zYWMmmGVtMjU`pDX1CtmPNrU;+j9|3=-rpM16cg`kWu-a_laR`xA?z~PPF!QsRZT)& z)fQk{AB}8m`=anM8NR>g~_$wWMuJZcLdCU+mkZrnkc9kt1#B{F53Q<0a?@wv@fHQ>^?5aYBC_ZA- zziV0gA^lCS6K(#p{1|1WUWGtL6709`C%>FHNkv8FOdWHX>FEuji>szC$``PTg*yIA z>(lL3k*L(DvUY|y#Bzb zpP%zaGP@hqs!etqdvkvARV=&c+D!3=UBUBV=CX?wP<0Q4LAfQ^!~P($MW5u?dUBL@ zbi7^d8k7|yR~S-rbb$646>GoLNSQIOgB{cH{H;-WDUJ($uWD zy5taYSl6F`v6i51B_X+HYH{LN(NYqf=IUYcKE4@g4;B*eOncr`{-B!;!^kS>7*jv^ zWLmY$4m~vU^y!7CuK~!6)e`DbP?@`i#@D(gPjb0i+p{Q7=InvjM2FrT<$`HQAN)69 zrM70GX1~>>W9$;WAZdOX$B@=KI#c0eX5Px%xEcC}O*146E!9XN_E=nI#dBIdDT6O! zLFJd9f3Jkm&nHk9~Hfx0D}6=n1f19n`Z-ZO<$!mVBZ)xm$RdowQ^n2I3fNg z$mWG*B$1WLR4qC*wZ||O`?w`#bXh0DBmUznkS>We*(@&`*|KN|=^9x5idx~%$;5UA zce$8qI^&mr@Y!s^RXfO={=z_yD)OP>9s2Yhp^Fr*C!$7Hg!NJnF)~_Kwov{VkAK9<> zmp$$9n+JMoPz_?#^$CC z0B<;h^Y z8#%$uED}y!w)3&d9AB~=XmFnS`|UhzaS(-o78ki|Zl==Ij4mQIingg$+0N$kh$Y#t zk`C$Ql+!*b1rxpZ8htP^nrKpgx;tVzdLudv2*PH_97dWh4$l(`=iZIG7MK^ z%2G0hm!DHK9g`Ra1CbelA+geaBJ&cj1!AdPX`EvXnGM>u1rHpOs_Bv%3&P39}$Pi$;# zzY5rz3sV;DGk+;c+xS~^Du=}mZpeORdaug)J_vtQLSaX;W7|-UjJnK!*WGh$5Mgr` zC-1Y^j~b}3W-5-so79?7{HwnqsjmeWV8V*=$Dp+t>rOVO!@-m|WHGXzTO_zbtYQ$d zQ~mp5^SKD6uGB>?E{v`%yYv{~!!vMPT2QE^YPKs;8Bbp*8N@8yXh>dnpRpN$J|zxK zoC!$)z;QD#921@ru&EwR7I`%TJoA#QDhyK8G?qwrD^>L^xoB#SQ!ENTCS^lXzp#&kF3IZ>0of(s1tiGHMZvKDJ?U{KCMLH)-%eCa zjDBGVpBl!y?4{hFpScG&W`)D=D@W`Vauo}cWX$c)(SSQnN zX0;&PW7Fip@+aH)m-bvMoXqiJkd?8&5qEH0s?PZ^e(Z1F*717g@31GQEbpww zLxydL$adsLLain@D>(09ZTlrA+Kj(<@qdqMBJF{`>e}?}S%02G-jgX3RUqT(fn!+L zy!e&j7X$fGes!7rqj{JP9w%2beGCBGNQ@Q5*PGqW$*TRKl>gQs8Wmp`+MDP3_6@-3 z)dlFxwRIR8l>>?@Ngt^~LqjW^PPGBPAxMBjr%tq-(F3

$7u<>t836iJfPsDc-&x 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

G^m(YTBc;2THvqK$KWACKp0>> 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 literal 0 HcmV?d00001 diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py new file mode 100644 index 00000000..9c849c0e --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py @@ -0,0 +1,6 @@ +"""Intent provider interfaces and implementations.""" + +from .default_intent_provider import DefaultIntentProvider +from .intent_provider import IntentProvider + +__all__ = ["DefaultIntentProvider", "IntentProvider"] diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py new file mode 100644 index 00000000..195dad6d --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py @@ -0,0 +1,58 @@ +import logging + +from strands import Agent + +from .intent_provider import IntentProvider + +logger = logging.getLogger(__name__) + +INTENT_SYSTEM_PROMPT = ( + "You are an intent classifier. Given the recent conversation messages, " + "produce a concise one-sentence description of what the user is trying to accomplish. " + "Focus on the type of task, not the specific details. " + "Reply with ONLY the intent description, nothing else." +) + + +class DefaultIntentProvider(IntentProvider): + """LLM-based intent provider that classifies the last N messages.""" + + def __init__(self, message_window: int = 5, model=None): + self._message_window = message_window + self._explicit_model = model + + def derive_intent(self, messages: list[dict], model=None) -> str: + """Derive intent using an LLM. Falls back to agent's model if no explicit model set.""" + try: + recent_messages = messages[-self._message_window:] if messages else [] + if not recent_messages: + return "" + + kwargs = {"system_prompt": INTENT_SYSTEM_PROMPT, "tools": []} + # Priority: explicit model > agent's model > Strands default + resolved_model = self._explicit_model or model + if resolved_model: + kwargs["model"] = resolved_model + + intent_agent = Agent(**kwargs) + response = intent_agent(self._format_messages_for_prompt(recent_messages)) + return str(response).strip() + except Exception as e: + logger.error("Failed to derive intent: %s", e) + return "" + + def _format_messages_for_prompt(self, messages: list[dict]) -> str: + """Format messages into a text prompt for the intent LLM.""" + parts = [] + for msg in messages: + role = msg.get("role", "unknown") + content = msg.get("content", []) + text = "" + if isinstance(content, list): + text = " ".join( + block.get("text", "") + for block in content + if isinstance(block, dict) and "text" in block + ) + parts.append(f"{role}: {text}") + return "\n".join(parts) diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py new file mode 100644 index 00000000..411e507c --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py @@ -0,0 +1,24 @@ +from abc import ABC, abstractmethod + + +class IntentProvider(ABC): + """Abstract interface for deriving user intent from conversation messages. + + Subclasses must implement the `derive_intent` method to analyze conversation + messages and return a concise intent string. + """ + + @abstractmethod + def derive_intent(self, messages: list[dict], model=None) -> str: + """Analyze conversation messages and return a concise intent string. + + Args: + messages: List of conversation message dicts in Strands format. + model: Optional model instance from the parent agent. Implementations + can use this for LLM-based intent derivation. + + Returns: + A plain text string describing the user's intent. + Returns empty string if intent cannot be determined. + """ + ... diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py new file mode 100644 index 00000000..57fff548 --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py @@ -0,0 +1,109 @@ +"""AgentCore tool search plugin for Strands Agents.""" + +import json +import logging + +from mcp.types import Tool as MCPTool +from strands.hooks import BeforeInvocationEvent +from strands.plugins import Plugin, hook +from strands.tools.mcp import MCPClient +from strands.tools.mcp.mcp_agent_tool import MCPAgentTool + +from .intent_providers import DefaultIntentProvider, IntentProvider + +logger = logging.getLogger(__name__) + + +class AgentCoreToolSearchPlugin(Plugin): + """Plugin that dynamically loads tools from AgentCore Gateway based on semantic intent. + + Args: + mcp_client: MCPClient connected to an AgentCore Gateway. + intent_provider: Strategy for deriving intent. Defaults to DefaultIntentProvider. + """ + + name = "agentcore-tool-search-plugin" + + def __init__( + self, + mcp_client: MCPClient, + intent_provider: IntentProvider | None = None, + ): + super().__init__() + self._intent_provider = intent_provider or DefaultIntentProvider() + self._mcp_client = mcp_client + self._loaded_tool_names: set[str] = set() + + @property + def tools(self): + return [] + + @hook + def on_before_invocation(self, event: BeforeInvocationEvent) -> None: + """Derive intent, search gateway, and load matching tools.""" + messages = event.messages or [] + + # Pass the agent's model to the intent provider + intent = self._intent_provider.derive_intent(messages, model=event.agent.model) + logger.info("Derived intent: %s", intent) + + # Clear all previously loaded conditional tools + for name in list(self._loaded_tool_names): + event.agent.tool_registry.registry.pop(name, None) + self._loaded_tool_names.clear() + + if not intent: + return + + try: + result = self._mcp_client.call_tool_sync( + tool_use_id="intent-search", + name="x_amz_bedrock_agentcore_search", + arguments={"query": intent}, + ) + agent_tools = self._build_tools_from_search_result(result) + except Exception as e: + logger.error("AgentCore Gateway search failed: %s", e) + return + + for agent_tool in agent_tools: + try: + event.agent.tool_registry.register_tool(agent_tool) + self._loaded_tool_names.add(agent_tool.tool_name) + except Exception as e: + logger.error("Failed to register tool %s: %s", agent_tool.tool_name, e) + + logger.info("Loaded tools: %s", self._loaded_tool_names) + + def _build_tools_from_search_result(self, result) -> list[MCPAgentTool]: + """Build MCPAgentTool objects from the gateway search response.""" + tools = [] + if not result or not isinstance(result, dict): + return tools + + tool_defs = [] + structured = result.get("structuredContent") + if isinstance(structured, dict) and "tools" in structured: + tool_defs = structured["tools"] + else: + for block in result.get("content", []): + if isinstance(block, dict) and "text" in block: + try: + data = json.loads(block["text"]) + if isinstance(data, dict) and "tools" in data: + tool_defs = data["tools"] + break + except (json.JSONDecodeError, TypeError): + continue + + for tool_def in tool_defs: + if not isinstance(tool_def, dict) or "name" not in tool_def: + continue + mcp_tool = MCPTool( + name=tool_def["name"], + description=tool_def.get("description", ""), + inputSchema=tool_def.get("inputSchema", {"type": "object", "properties": {}}), + ) + tools.append(MCPAgentTool(mcp_tool=mcp_tool, mcp_client=self._mcp_client)) + + return tools From aceabba0b63126b9192df33597cfc5012193d2bf Mon Sep 17 00:00:00 2001 From: Senthil Mohan Date: Thu, 28 May 2026 22:17:28 +0100 Subject: [PATCH 2/6] refactor: address review feedback for AgentCoreToolSearchPlugin - Rename DefaultIntentProvider to StrandsIntentProvider - Filter to user-only messages to prevent PII leakage - Add configurable system_prompt parameter - Add existing tool check to prevent overwriting static tools - Add missing __init__.py at gateway/integrations/ and gateway/integrations/strands/ - Add module docstrings for ruff compliance --- pyproject.toml | 3 +- .../gateway/integrations/__init__.py | 1 + .../gateway/integrations/strands/__init__.py | 1 + .../plugins/agentcore_tool_search/README.md | 28 +- .../plugins/agentcore_tool_search/__init__.py | 7 +- .../intent_providers/__init__.py | 4 +- .../intent_providers/intent_provider.py | 2 + ...provider.py => strands_intent_provider.py} | 37 +- .../plugins/agentcore_tool_search/plugin.py | 20 +- tests/bedrock_agentcore/gateway/__init__.py | 0 .../gateway/integrations/__init__.py | 0 .../gateway/integrations/strands/__init__.py | 0 .../test_agentcore_tool_search_plugin.py | 240 ++++++++++++ .../strands/test_intent_providers.py | 232 +++++++++++ .../lambda_function/lambda_function.py | 117 ++++++ .../test_agentcore_tool_search_plugin.py | 363 ++++++++++++++++++ uv.lock | 2 + 17 files changed, 1032 insertions(+), 25 deletions(-) create mode 100644 src/bedrock_agentcore/gateway/integrations/__init__.py create mode 100644 src/bedrock_agentcore/gateway/integrations/strands/__init__.py rename src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/{default_intent_provider.py => strands_intent_provider.py} (54%) create mode 100644 tests/bedrock_agentcore/gateway/__init__.py create mode 100644 tests/bedrock_agentcore/gateway/integrations/__init__.py create mode 100644 tests/bedrock_agentcore/gateway/integrations/strands/__init__.py create mode 100644 tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py create mode 100644 tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py create mode 100644 tests_integ/gateway/integrations/lambda_function/lambda_function.py create mode 100644 tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py diff --git a/pyproject.toml b/pyproject.toml index c7107102..9d46e015 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -162,7 +162,8 @@ dev = [ a2a = ["a2a-sdk[http-server]>=0.3"] ag-ui = ["ag-ui-protocol>=0.1.10"] strands-agents = [ - "strands-agents>=1.20.0" + "strands-agents>=1.20.0", + "mcp>=1.23.0,<2.0.0", ] strands-agents-evals = [ "strands-agents-evals>=0.1.0" diff --git a/src/bedrock_agentcore/gateway/integrations/__init__.py b/src/bedrock_agentcore/gateway/integrations/__init__.py new file mode 100644 index 00000000..2da3d743 --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/__init__.py @@ -0,0 +1 @@ +"""Gateway integrations.""" diff --git a/src/bedrock_agentcore/gateway/integrations/strands/__init__.py b/src/bedrock_agentcore/gateway/integrations/strands/__init__.py new file mode 100644 index 00000000..d00cd5fc --- /dev/null +++ b/src/bedrock_agentcore/gateway/integrations/strands/__init__.py @@ -0,0 +1 @@ +"""Strands Agents integrations for AgentCore Gateway.""" diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md index e7f83b66..162a7e83 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md @@ -13,7 +13,7 @@ A semantic tool discovery plugin for [Strands Agents](https://github.com/strands ## Installation ```bash -pip install agentcore-tool-search-plugin +pip install 'bedrock-agentcore[strands-agents]' ``` ## Usage @@ -22,7 +22,7 @@ pip install agentcore-tool-search-plugin from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client from strands import Agent from strands.tools.mcp import MCPClient -from agentcore_tool_search_plugin import AgentCoreToolSearchPlugin +from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin mcp_client = MCPClient(lambda: aws_iam_streamablehttp_client( endpoint="https://.gateway.bedrock-agentcore..amazonaws.com/mcp", @@ -63,9 +63,9 @@ Previously loaded tools are cleared before each search, so the agent always has An `IntentProvider` is responsible for analyzing conversation messages and producing a concise intent string that drives tool search. The plugin calls `derive_intent(messages, model)` before each invocation to determine what tools to load. -### Default Intent Provider +### StrandsIntentProvider -`DefaultIntentProvider` uses an LLM to classify the last few conversation messages into a concise intent string. By default it uses the agent's model. +`StrandsIntentProvider` uses a Strands Agent to classify the last few conversation messages into a concise intent string. By default it uses the parent agent's model. **Basic usage (uses the agent's model automatically):** @@ -82,13 +82,29 @@ agent = Agent(plugins=[ ```python from strands.models.bedrock import BedrockModel from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin -from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import DefaultIntentProvider +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import StrandsIntentProvider intent_model = BedrockModel(model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0") agent = Agent(plugins=[ AgentCoreToolSearchPlugin( mcp_client=mcp_client, - intent_provider=DefaultIntentProvider(model=intent_model), + intent_provider=StrandsIntentProvider(model=intent_model), + ) +]) +``` + +**With a custom system prompt:** + +```python +from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import StrandsIntentProvider + +agent = Agent(plugins=[ + AgentCoreToolSearchPlugin( + mcp_client=mcp_client, + intent_provider=StrandsIntentProvider( + system_prompt="Classify the user's intent in one sentence. Focus on the action, not details." + ), ) ]) ``` diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py index 37d63a69..a59f7b27 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/__init__.py @@ -1,3 +1,6 @@ -from .intent_providers import IntentProvider, DefaultIntentProvider +"""AgentCore Tool Search plugin for Strands Agents.""" -__all__ = ["IntentProvider", "DefaultIntentProvider"] +from .intent_providers import IntentProvider, StrandsIntentProvider +from .plugin import AgentCoreToolSearchPlugin + +__all__ = ["AgentCoreToolSearchPlugin", "IntentProvider", "StrandsIntentProvider"] diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py index 9c849c0e..d30209ac 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/__init__.py @@ -1,6 +1,6 @@ """Intent provider interfaces and implementations.""" -from .default_intent_provider import DefaultIntentProvider from .intent_provider import IntentProvider +from .strands_intent_provider import StrandsIntentProvider -__all__ = ["DefaultIntentProvider", "IntentProvider"] +__all__ = ["StrandsIntentProvider", "IntentProvider"] diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py index 411e507c..864ece94 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/intent_provider.py @@ -1,3 +1,5 @@ +"""Intent provider abstract interface.""" + from abc import ABC, abstractmethod diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/strands_intent_provider.py similarity index 54% rename from src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py rename to src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/strands_intent_provider.py index 195dad6d..1833257f 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/default_intent_provider.py +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/intent_providers/strands_intent_provider.py @@ -1,3 +1,5 @@ +"""Strands Agent-based intent provider implementation.""" + import logging from strands import Agent @@ -14,21 +16,29 @@ ) -class DefaultIntentProvider(IntentProvider): - """LLM-based intent provider that classifies the last N messages.""" +class StrandsIntentProvider(IntentProvider): + """LLM-based intent provider that uses a Strands Agent to classify the last N messages.""" + + def __init__(self, message_window: int = 5, model=None, system_prompt: str = INTENT_SYSTEM_PROMPT): + """Initialize StrandsIntentProvider. - def __init__(self, message_window: int = 5, model=None): + Args: + message_window: Number of recent messages to consider. + model: Optional explicit model for intent classification. + system_prompt: System prompt for the intent classifier. Defaults to INTENT_SYSTEM_PROMPT. + """ self._message_window = message_window self._explicit_model = model + self._system_prompt = system_prompt def derive_intent(self, messages: list[dict], model=None) -> str: """Derive intent using an LLM. Falls back to agent's model if no explicit model set.""" try: - recent_messages = messages[-self._message_window:] if messages else [] + recent_messages = messages[-self._message_window :] if messages else [] if not recent_messages: return "" - kwargs = {"system_prompt": INTENT_SYSTEM_PROMPT, "tools": []} + kwargs = {"system_prompt": self._system_prompt, "tools": []} # Priority: explicit model > agent's model > Strands default resolved_model = self._explicit_model or model if resolved_model: @@ -42,17 +52,22 @@ def derive_intent(self, messages: list[dict], model=None) -> str: return "" def _format_messages_for_prompt(self, messages: list[dict]) -> str: - """Format messages into a text prompt for the intent LLM.""" + """Format user messages into a text prompt for the intent LLM. + + Only includes user-role messages to avoid leaking PII or sensitive data + from tool results or assistant responses. + """ parts = [] for msg in messages: - role = msg.get("role", "unknown") + role = msg.get("role", "") + if role != "user": + continue content = msg.get("content", []) text = "" if isinstance(content, list): text = " ".join( - block.get("text", "") - for block in content - if isinstance(block, dict) and "text" in block + block.get("text", "") for block in content if isinstance(block, dict) and "text" in block ) - parts.append(f"{role}: {text}") + if text.strip(): + parts.append(text.strip()) return "\n".join(parts) diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py index 57fff548..28465a0f 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/plugin.py @@ -9,7 +9,7 @@ from strands.tools.mcp import MCPClient from strands.tools.mcp.mcp_agent_tool import MCPAgentTool -from .intent_providers import DefaultIntentProvider, IntentProvider +from .intent_providers import IntentProvider, StrandsIntentProvider logger = logging.getLogger(__name__) @@ -19,7 +19,7 @@ class AgentCoreToolSearchPlugin(Plugin): Args: mcp_client: MCPClient connected to an AgentCore Gateway. - intent_provider: Strategy for deriving intent. Defaults to DefaultIntentProvider. + intent_provider: Strategy for deriving intent. Defaults to StrandsIntentProvider. """ name = "agentcore-tool-search-plugin" @@ -29,13 +29,20 @@ def __init__( mcp_client: MCPClient, intent_provider: IntentProvider | None = None, ): + """Initialize the plugin. + + Args: + mcp_client: MCPClient connected to an AgentCore Gateway. + intent_provider: Strategy for deriving intent. Defaults to StrandsIntentProvider. + """ super().__init__() - self._intent_provider = intent_provider or DefaultIntentProvider() + self._intent_provider = intent_provider or StrandsIntentProvider() self._mcp_client = mcp_client self._loaded_tool_names: set[str] = set() @property def tools(self): + """Return empty list; tools are loaded dynamically via the hook.""" return [] @hook @@ -68,6 +75,13 @@ def on_before_invocation(self, event: BeforeInvocationEvent) -> None: for agent_tool in agent_tools: try: + # Skip if a non-dynamic tool with this name already exists + if ( + agent_tool.tool_name in event.agent.tool_registry.registry + and agent_tool.tool_name not in self._loaded_tool_names + ): + logger.debug("Skipping tool %s: already registered as a static tool", agent_tool.tool_name) + continue event.agent.tool_registry.register_tool(agent_tool) self._loaded_tool_names.add(agent_tool.tool_name) except Exception as e: diff --git a/tests/bedrock_agentcore/gateway/__init__.py b/tests/bedrock_agentcore/gateway/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/gateway/integrations/__init__.py b/tests/bedrock_agentcore/gateway/integrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/gateway/integrations/strands/__init__.py b/tests/bedrock_agentcore/gateway/integrations/strands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py b/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py new file mode 100644 index 00000000..9623df25 --- /dev/null +++ b/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py @@ -0,0 +1,240 @@ +"""Tests for AgentCoreToolSearchPlugin.""" + +import json +from unittest.mock import Mock + +import pytest + +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import ( + StrandsIntentProvider, + IntentProvider, +) +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.plugin import ( + AgentCoreToolSearchPlugin, +) + + +class FakeIntentProvider(IntentProvider): + """Test intent provider that returns a fixed intent string.""" + + def __init__(self, intent: str = "test intent"): + self._intent = intent + + def derive_intent(self, messages: list[dict], model=None) -> str: + return self._intent + + +@pytest.fixture +def mock_mcp_client(): + """Create a mock MCPClient.""" + client = Mock() + client.call_tool_sync.return_value = {"content": []} + return client + + +@pytest.fixture +def fixed_intent_provider(): + """Create a fixed intent provider.""" + return FakeIntentProvider("get weather") + + +@pytest.fixture +def plugin(mock_mcp_client, fixed_intent_provider): + """Create an AgentCoreToolSearchPlugin with mocked dependencies.""" + return AgentCoreToolSearchPlugin(mcp_client=mock_mcp_client, intent_provider=fixed_intent_provider) + + +@pytest.fixture +def mock_event(): + """Create a mock BeforeInvocationEvent.""" + event = Mock() + event.messages = [{"role": "user", "content": [{"text": "hello"}]}] + event.agent = Mock() + event.agent.model = None + event.agent.tool_registry = Mock() + event.agent.tool_registry.registry = {} + return event + + +class TestAgentCoreToolSearchPluginInit: + """Test AgentCoreToolSearchPlugin initialization.""" + + def test_init_with_custom_intent_provider(self, mock_mcp_client): + """Test initialization with a custom intent provider.""" + provider = FakeIntentProvider("custom") + plugin = AgentCoreToolSearchPlugin(mcp_client=mock_mcp_client, intent_provider=provider) + assert plugin._intent_provider is provider + assert plugin._mcp_client is mock_mcp_client + + def test_init_default_intent_provider(self, mock_mcp_client): + """Test initialization uses StrandsIntentProvider when none provided.""" + plugin = AgentCoreToolSearchPlugin(mcp_client=mock_mcp_client) + assert isinstance(plugin._intent_provider, StrandsIntentProvider) + + def test_plugin_name(self, plugin): + """Test plugin has correct name.""" + assert plugin.name == "agentcore-tool-search-plugin" + + def test_tools_property_returns_empty(self, plugin): + """Test tools property returns empty list.""" + assert plugin.tools == [] + + +class TestOnBeforeInvocation: + """Test on_before_invocation hook behavior.""" + + def test_empty_intent_skips_search(self, mock_mcp_client, mock_event): + """Test that empty intent does not call gateway search.""" + provider = FakeIntentProvider("") + plugin = AgentCoreToolSearchPlugin(mcp_client=mock_mcp_client, intent_provider=provider) + + plugin.on_before_invocation(mock_event) + + mock_mcp_client.call_tool_sync.assert_not_called() + + def test_calls_gateway_search_with_intent(self, plugin, mock_mcp_client, mock_event): + """Test that derived intent is passed to gateway search.""" + plugin.on_before_invocation(mock_event) + + mock_mcp_client.call_tool_sync.assert_called_once_with( + tool_use_id="intent-search", + name="x_amz_bedrock_agentcore_search", + arguments={"query": "get weather"}, + ) + + def test_passes_agent_model_to_intent_provider(self, mock_mcp_client, mock_event): + """Test that the agent's model is passed to derive_intent.""" + provider = Mock(spec=IntentProvider) + provider.derive_intent.return_value = "" + plugin = AgentCoreToolSearchPlugin(mcp_client=mock_mcp_client, intent_provider=provider) + mock_event.agent.model = Mock(name="test-model") + + plugin.on_before_invocation(mock_event) + + provider.derive_intent.assert_called_once_with(mock_event.messages, model=mock_event.agent.model) + + def test_registers_tools_from_structured_content(self, plugin, mock_mcp_client, mock_event): + """Test tools are registered from structuredContent response.""" + mock_mcp_client.call_tool_sync.return_value = { + "structuredContent": { + "tools": [ + { + "name": "weather_tool", + "description": "Get weather", + "inputSchema": {"type": "object", "properties": {"city": {"type": "string"}}}, + } + ] + } + } + + plugin.on_before_invocation(mock_event) + + mock_event.agent.tool_registry.register_tool.assert_called_once() + registered_tool = mock_event.agent.tool_registry.register_tool.call_args[0][0] + assert registered_tool.tool_name == "weather_tool" + assert "weather_tool" in plugin._loaded_tool_names + + def test_registers_tools_from_text_content(self, plugin, mock_mcp_client, mock_event): + """Test tools are registered from JSON text content response.""" + tools_json = json.dumps( + { + "tools": [ + { + "name": "calc_tool", + "description": "Calculator", + "inputSchema": {"type": "object", "properties": {}}, + }, + ] + } + ) + mock_mcp_client.call_tool_sync.return_value = {"content": [{"text": tools_json}]} + + plugin.on_before_invocation(mock_event) + + mock_event.agent.tool_registry.register_tool.assert_called_once() + registered_tool = mock_event.agent.tool_registry.register_tool.call_args[0][0] + assert registered_tool.tool_name == "calc_tool" + + def test_clears_previously_loaded_tools(self, plugin, mock_mcp_client, mock_event): + """Test previously loaded tools are removed from registry.""" + mock_mcp_client.call_tool_sync.return_value = {"content": []} + plugin._loaded_tool_names = {"old_tool_1", "old_tool_2"} + mock_event.agent.tool_registry.registry = { + "old_tool_1": Mock(), + "old_tool_2": Mock(), + "permanent_tool": Mock(), + } + + plugin.on_before_invocation(mock_event) + + assert "old_tool_1" not in mock_event.agent.tool_registry.registry + assert "old_tool_2" not in mock_event.agent.tool_registry.registry + assert "permanent_tool" in mock_event.agent.tool_registry.registry + assert len(plugin._loaded_tool_names) == 0 + + def test_gateway_search_failure_logs_and_returns(self, plugin, mock_mcp_client, mock_event): + """Test gateway search failure is handled gracefully.""" + mock_mcp_client.call_tool_sync.side_effect = RuntimeError("connection failed") + + plugin.on_before_invocation(mock_event) + + mock_event.agent.tool_registry.register_tool.assert_not_called() + + def test_skips_invalid_tool_defs(self, plugin, mock_mcp_client, mock_event): + """Test malformed tool definitions are skipped.""" + mock_mcp_client.call_tool_sync.return_value = { + "structuredContent": { + "tools": [ + {"description": "no name field"}, + "not a dict", + {"name": "valid_tool", "description": "ok", "inputSchema": {"type": "object", "properties": {}}}, + ] + } + } + + plugin.on_before_invocation(mock_event) + + mock_event.agent.tool_registry.register_tool.assert_called_once() + registered_tool = mock_event.agent.tool_registry.register_tool.call_args[0][0] + assert registered_tool.tool_name == "valid_tool" + + def test_register_tool_failure_continues(self, plugin, mock_mcp_client, mock_event): + """Test that failure to register one tool doesn't block others.""" + mock_mcp_client.call_tool_sync.return_value = { + "structuredContent": { + "tools": [ + {"name": "tool_a", "description": "A", "inputSchema": {"type": "object", "properties": {}}}, + {"name": "tool_b", "description": "B", "inputSchema": {"type": "object", "properties": {}}}, + ] + } + } + mock_event.agent.tool_registry.register_tool.side_effect = [RuntimeError("fail"), None] + + plugin.on_before_invocation(mock_event) + + assert mock_event.agent.tool_registry.register_tool.call_count == 2 + assert "tool_a" not in plugin._loaded_tool_names + assert "tool_b" in plugin._loaded_tool_names + + def test_none_result_loads_no_tools(self, plugin, mock_mcp_client, mock_event): + """Test None result from gateway loads no tools.""" + mock_mcp_client.call_tool_sync.return_value = None + + plugin.on_before_invocation(mock_event) + + mock_event.agent.tool_registry.register_tool.assert_not_called() + + def test_empty_messages_with_intent(self, mock_mcp_client): + """Test plugin works with empty messages list.""" + provider = FakeIntentProvider("") + plugin = AgentCoreToolSearchPlugin(mcp_client=mock_mcp_client, intent_provider=provider) + event = Mock() + event.messages = [] + event.agent = Mock() + event.agent.model = None + event.agent.tool_registry = Mock() + event.agent.tool_registry.registry = {} + + plugin.on_before_invocation(event) + + mock_mcp_client.call_tool_sync.assert_not_called() diff --git a/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py b/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py new file mode 100644 index 00000000..d352d54a --- /dev/null +++ b/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py @@ -0,0 +1,232 @@ +"""Tests for IntentProvider and StrandsIntentProvider.""" + +from unittest.mock import Mock, patch + +import pytest + +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import ( + StrandsIntentProvider, + IntentProvider, +) + + +class TestIntentProviderInterface: + """Test IntentProvider abstract interface.""" + + def test_cannot_instantiate_abstract_class(self): + """Test that IntentProvider cannot be instantiated directly.""" + with pytest.raises(TypeError): + IntentProvider() + + def test_subclass_must_implement_derive_intent(self): + """Test that subclass without derive_intent raises TypeError.""" + + class IncompleteProvider(IntentProvider): + pass + + with pytest.raises(TypeError): + IncompleteProvider() + + def test_subclass_with_derive_intent_works(self): + """Test that a proper subclass can be instantiated.""" + + class ValidProvider(IntentProvider): + def derive_intent(self, messages: list[dict], model=None) -> str: + return "test" + + provider = ValidProvider() + assert provider.derive_intent([]) == "test" + + +class TestStrandsIntentProvider: + """Test StrandsIntentProvider class.""" + + def test_init_default_message_window(self): + """Test default message window is 5.""" + provider = StrandsIntentProvider() + assert provider._message_window == 5 + + def test_init_custom_message_window(self): + """Test custom message window.""" + provider = StrandsIntentProvider(message_window=3) + assert provider._message_window == 3 + + def test_init_with_explicit_model(self): + """Test initialization with explicit model.""" + model = Mock() + provider = StrandsIntentProvider(model=model) + assert provider._explicit_model is model + + def test_empty_messages_returns_empty_string(self): + """Test empty messages returns empty string without calling LLM.""" + provider = StrandsIntentProvider() + result = provider.derive_intent([]) + assert result == "" + + @patch( + "bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers.strands_intent_provider.Agent" + ) + def test_derive_intent_calls_agent(self, mock_agent_class): + """Test derive_intent creates an Agent and calls it.""" + mock_agent = Mock() + mock_agent.return_value = "user wants weather info" + mock_agent_class.return_value = mock_agent + + provider = StrandsIntentProvider(message_window=2) + messages = [ + {"role": "user", "content": [{"text": "What is the weather?"}]}, + ] + + result = provider.derive_intent(messages) + + assert result == "user wants weather info" + mock_agent_class.assert_called_once() + + @patch( + "bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers.strands_intent_provider.Agent" + ) + def test_derive_intent_uses_explicit_model(self, mock_agent_class): + """Test derive_intent uses explicit model over agent model.""" + mock_agent = Mock() + mock_agent.return_value = "intent" + mock_agent_class.return_value = mock_agent + + explicit_model = Mock(name="explicit-model") + provider = StrandsIntentProvider(model=explicit_model) + messages = [{"role": "user", "content": [{"text": "hello"}]}] + + provider.derive_intent(messages, model=Mock(name="agent-model")) + + # Explicit model takes priority + call_kwargs = mock_agent_class.call_args[1] + assert call_kwargs["model"] is explicit_model + + @patch( + "bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers.strands_intent_provider.Agent" + ) + def test_derive_intent_uses_agent_model_when_no_explicit(self, mock_agent_class): + """Test derive_intent falls back to agent model when no explicit model.""" + mock_agent = Mock() + mock_agent.return_value = "intent" + mock_agent_class.return_value = mock_agent + + agent_model = Mock(name="agent-model") + provider = StrandsIntentProvider() + messages = [{"role": "user", "content": [{"text": "hello"}]}] + + provider.derive_intent(messages, model=agent_model) + + call_kwargs = mock_agent_class.call_args[1] + assert call_kwargs["model"] is agent_model + + @patch( + "bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers.strands_intent_provider.Agent" + ) + def test_derive_intent_no_model_kwarg_when_none(self, mock_agent_class): + """Test derive_intent omits model kwarg when no model available.""" + mock_agent = Mock() + mock_agent.return_value = "intent" + mock_agent_class.return_value = mock_agent + + provider = StrandsIntentProvider() + messages = [{"role": "user", "content": [{"text": "hello"}]}] + + provider.derive_intent(messages, model=None) + + call_kwargs = mock_agent_class.call_args[1] + assert "model" not in call_kwargs + + @patch( + "bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers.strands_intent_provider.Agent" + ) + def test_derive_intent_respects_message_window(self, mock_agent_class): + """Test only last N messages are used.""" + mock_agent = Mock() + mock_agent.return_value = "intent" + mock_agent_class.return_value = mock_agent + + provider = StrandsIntentProvider(message_window=3) + messages = [ + {"role": "user", "content": [{"text": "first"}]}, + {"role": "user", "content": [{"text": "second"}]}, + {"role": "user", "content": [{"text": "third"}]}, + {"role": "user", "content": [{"text": "fourth"}]}, + {"role": "user", "content": [{"text": "fifth"}]}, + ] + + provider.derive_intent(messages) + + # Window=3 takes last 3 messages; only user messages are formatted + call_args = mock_agent.call_args[0][0] + assert "first" not in call_args + assert "second" not in call_args + assert "third" in call_args + assert "fourth" in call_args + assert "fifth" in call_args + + @patch( + "bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers.strands_intent_provider.Agent" + ) + def test_derive_intent_handles_exception(self, mock_agent_class): + """Test derive_intent returns empty string on exception.""" + mock_agent_class.side_effect = RuntimeError("LLM unavailable") + + provider = StrandsIntentProvider() + messages = [{"role": "user", "content": [{"text": "hello"}]}] + + result = provider.derive_intent(messages) + + assert result == "" + + def test_format_messages_for_prompt(self): + """Test message formatting only includes user messages.""" + provider = StrandsIntentProvider() + messages = [ + {"role": "user", "content": [{"text": "Hello"}, {"text": "world"}]}, + {"role": "assistant", "content": [{"text": "Hi there"}]}, + {"role": "user", "content": [{"text": "What is the weather?"}]}, + ] + + result = provider._format_messages_for_prompt(messages) + + assert "Hello world" in result + assert "What is the weather?" in result + assert "Hi there" not in result + + def test_format_messages_handles_missing_role(self): + """Test formatting skips messages without user role.""" + provider = StrandsIntentProvider() + messages = [{"content": [{"text": "no role"}]}] + + result = provider._format_messages_for_prompt(messages) + + assert result == "" + + def test_format_messages_handles_non_text_blocks(self): + """Test formatting skips non-text content blocks.""" + provider = StrandsIntentProvider() + messages = [ + {"role": "user", "content": [{"image": "data"}, {"text": "only this"}]}, + ] + + result = provider._format_messages_for_prompt(messages) + + assert "only this" in result + assert "data" not in result + + def test_format_messages_excludes_tool_results(self): + """Test formatting excludes assistant tool results to avoid PII leakage.""" + provider = StrandsIntentProvider() + messages = [ + {"role": "user", "content": [{"text": "Check my account"}]}, + {"role": "assistant", "content": [{"toolUse": {"name": "get_account", "input": {}}}]}, + {"role": "user", "content": [{"toolResult": {"content": [{"text": "SSN: 123-45-6789"}]}}]}, + {"role": "user", "content": [{"text": "Now send an email"}]}, + ] + + result = provider._format_messages_for_prompt(messages) + + assert "Check my account" in result + assert "Now send an email" in result + assert "SSN" not in result + assert "get_account" not in result diff --git a/tests_integ/gateway/integrations/lambda_function/lambda_function.py b/tests_integ/gateway/integrations/lambda_function/lambda_function.py new file mode 100644 index 00000000..2001f9b7 --- /dev/null +++ b/tests_integ/gateway/integrations/lambda_function/lambda_function.py @@ -0,0 +1,117 @@ +"""MCP-compatible Lambda handler for AgentCore Gateway integration tests. + +This Lambda implements the MCP JSON-RPC protocol over HTTP, responding to: +- initialize: Returns server capabilities +- tools/list: Returns available tool definitions +- tools/call: Executes a tool and returns results + +Deploy with Python 3.10+ runtime, handler: lambda_function.lambda_handler +""" + +import json +import logging + +logger = logging.getLogger() +logger.setLevel(logging.INFO) + +TOOLS = [ + { + "name": "get_weather", + "description": "Get current weather for a city", + "inputSchema": { + "type": "object", + "properties": { + "city": {"type": "string", "description": "City name"}, + }, + "required": ["city"], + }, + }, + { + "name": "send_email", + "description": "Send an email to a recipient", + "inputSchema": { + "type": "object", + "properties": { + "to": {"type": "string", "description": "Recipient email"}, + "subject": {"type": "string", "description": "Email subject"}, + "body": {"type": "string", "description": "Email body"}, + }, + "required": ["to", "subject", "body"], + }, + }, +] + + +def lambda_handler(event, context): + """Handle MCP JSON-RPC requests from AgentCore Gateway.""" + logger.info("Received event: %s", json.dumps(event)) + + body = event.get("body", "{}") + if isinstance(body, str): + body = json.loads(body) + + method = body.get("method", "") + request_id = body.get("id") + params = body.get("params", {}) + + if method == "initialize": + result = { + "protocolVersion": "2024-11-05", + "capabilities": { + "tools": {"listChanged": False}, + }, + "serverInfo": { + "name": "integ-test-mcp-server", + "version": "1.0.0", + }, + } + elif method == "notifications/initialized": + # Client acknowledgment, no response needed + return {"statusCode": 200, "body": ""} + elif method == "tools/list": + result = {"tools": TOOLS} + elif method == "tools/call": + tool_name = params.get("name", "") + arguments = params.get("arguments", {}) + result = _handle_tool_call(tool_name, arguments) + else: + return { + "statusCode": 200, + "body": json.dumps( + { + "jsonrpc": "2.0", + "id": request_id, + "error": { + "code": -32601, + "message": f"Method not found: {method}", + }, + } + ), + } + + response = { + "jsonrpc": "2.0", + "id": request_id, + "result": result, + } + + return { + "statusCode": 200, + "body": json.dumps(response), + } + + +def _handle_tool_call(tool_name, arguments): + """Execute a tool and return MCP-formatted result.""" + if tool_name == "get_weather": + city = arguments.get("city", "unknown") + return {"content": [{"type": "text", "text": f"Weather in {city}: 72°F, sunny with light clouds."}]} + elif tool_name == "send_email": + to = arguments.get("to", "") + subject = arguments.get("subject", "") + return {"content": [{"type": "text", "text": f"Email sent to {to} with subject: {subject}"}]} + else: + return { + "content": [{"type": "text", "text": f"Unknown tool: {tool_name}"}], + "isError": True, + } diff --git a/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py b/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py new file mode 100644 index 00000000..c718d712 --- /dev/null +++ b/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py @@ -0,0 +1,363 @@ +"""Integration tests for AgentCoreToolSearchPlugin. + +Requires environment variables: + BEDROCK_TEST_REGION: AWS region (default: us-west-2) + GATEWAY_ROLE_ARN: IAM role ARN with AgentCore gateway trust policy + GATEWAY_LAMBDA_ARN: Lambda ARN for the gateway target (must implement MCP tool handler) + +Prerequisites: + 1. Deploy the Lambda in tests_integ/gateway/integrations/lambda_function/lambda_function.py + (Python 3.10+ runtime, handler: lambda_function.lambda_handler) + + 2. The GATEWAY_ROLE_ARN must have: + - Trust policy for bedrock-agentcore.amazonaws.com + - lambda:InvokeFunction permission on the GATEWAY_LAMBDA_ARN + + Example inline policy: + { + "Version": "2012-10-17", + "Statement": [{ + "Effect": "Allow", + "Action": "lambda:InvokeFunction", + "Resource": "" + }] + } + + 3. Install mcp-proxy-for-aws: uv pip install mcp-proxy-for-aws + +""" + +import logging +import os +import time + +import pytest + +from bedrock_agentcore.gateway.client import GatewayClient +from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin +from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import ( + StrandsIntentProvider, + IntentProvider, +) + +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + + +class FixedIntentProvider(IntentProvider): + """Intent provider that returns a fixed string for deterministic testing.""" + + def __init__(self, intent: str): + self._intent = intent + + def derive_intent(self, messages: list[dict], model=None) -> str: + return self._intent + + +@pytest.mark.integration +class TestAgentCoreToolSearchPluginIntegration: + """Integration tests for AgentCoreToolSearchPlugin with a live gateway. + + Creates a gateway with a Lambda target exposing test tools, then verifies + the plugin can search and load those tools. + """ + + @classmethod + def setup_class(cls): + cls.region = os.environ.get("BEDROCK_TEST_REGION", "us-west-2") + cls.role_arn = os.environ.get("GATEWAY_ROLE_ARN") + cls.lambda_arn = os.environ.get("GATEWAY_LAMBDA_ARN") + + if not cls.role_arn or not cls.lambda_arn: + pytest.fail("GATEWAY_ROLE_ARN and GATEWAY_LAMBDA_ARN must be set") + + cls.gw_client = GatewayClient(region_name=cls.region) + cls.test_prefix = f"sdk-integ-plugin-{int(time.time())}" + cls.gateway_id = None + cls.target_id = None + + # Create gateway with semantic search enabled + gw = cls.gw_client.create_gateway_and_wait( + name=f"{cls.test_prefix}-gw", + roleArn=cls.role_arn, + authorizerType="NONE", + protocolType="MCP", + protocolConfiguration={ + "mcp": { + "searchType": "SEMANTIC", + }, + }, + ) + cls.gateway_id = gw["gatewayId"] + logger.info("Created gateway: %s", cls.gateway_id) + + # Create target with test tools + target = cls.gw_client.create_gateway_target_and_wait( + gatewayIdentifier=cls.gateway_id, + name=f"{cls.test_prefix}-target", + targetConfiguration={ + "mcp": { + "lambda": { + "lambdaArn": cls.lambda_arn, + "toolSchema": { + "inlinePayload": [ + { + "name": "get_weather", + "description": "Get current weather for a city", + "inputSchema": { + "type": "object", + "properties": { + "city": {"type": "string", "description": "City name"}, + }, + "required": ["city"], + }, + }, + { + "name": "send_email", + "description": "Send an email to a recipient", + "inputSchema": { + "type": "object", + "properties": { + "to": {"type": "string"}, + "subject": {"type": "string"}, + "body": {"type": "string"}, + }, + "required": ["to", "subject", "body"], + }, + }, + ] + }, + } + }, + }, + credentialProviderConfigurations=[ + {"credentialProviderType": "GATEWAY_IAM_ROLE"}, + ], + ) + cls.target_id = target["targetId"] + logger.info("Created target: %s", cls.target_id) + + # Wait for target search indexing to complete (can take up to 60s) + time.sleep(60) + + @classmethod + def teardown_class(cls): + if cls.gateway_id: + if cls.target_id: + try: + cls.gw_client.delete_gateway_target_and_wait( + gatewayIdentifier=cls.gateway_id, + targetId=cls.target_id, + ) + except Exception as e: + logger.warning("Failed to delete target %s: %s", cls.target_id, e) + try: + cls.gw_client.delete_gateway_and_wait( + gatewayIdentifier=cls.gateway_id, + ) + except Exception as e: + logger.warning("Failed to delete gateway %s: %s", cls.gateway_id, e) + + def _make_mcp_client(self): + """Create an MCPClient connected to the test gateway via Streamable HTTP with IAM auth.""" + from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client + from strands.tools.mcp import MCPClient + + endpoint = f"https://{self.gateway_id}.gateway.bedrock-agentcore.{self.region}.amazonaws.com/mcp" + return MCPClient( + lambda: aws_iam_streamablehttp_client( + endpoint=endpoint, + aws_region=self.region, + aws_service="bedrock-agentcore", + ) + ) + + @pytest.mark.order(1) + def test_plugin_with_default_intent_provider(self): + """Plugin initializes correctly with StrandsIntentProvider.""" + mcp_client = self._make_mcp_client() + plugin = AgentCoreToolSearchPlugin(mcp_client=mcp_client) + assert isinstance(plugin._intent_provider, StrandsIntentProvider) + assert plugin.name == "agentcore-tool-search-plugin" + + @pytest.mark.order(2) + def test_plugin_with_custom_intent_provider(self): + """Plugin accepts a custom IntentProvider.""" + mcp_client = self._make_mcp_client() + provider = FixedIntentProvider("weather query") + plugin = AgentCoreToolSearchPlugin(mcp_client=mcp_client, intent_provider=provider) + assert plugin._intent_provider is provider + + @pytest.mark.order(3) + def test_gateway_search_returns_results(self): + """Calling x_amz_bedrock_agentcore_search on the gateway returns tool definitions.""" + mcp_client = self._make_mcp_client() + + with mcp_client: + result = mcp_client.call_tool_sync( + tool_use_id="test-search", + name="x_amz_bedrock_agentcore_search", + arguments={"query": "get weather information"}, + ) + + assert result is not None + logger.info("Search result keys: %s", result.keys() if isinstance(result, dict) else type(result)) + + @pytest.mark.order(4) + def test_plugin_loads_tools_via_hook(self): + """Plugin loads matching tools into the agent via the before_invocation hook.""" + from strands import Agent + + mcp_client = self._make_mcp_client() + provider = FixedIntentProvider("get weather information") + plugin = AgentCoreToolSearchPlugin(mcp_client=mcp_client, intent_provider=provider) + + with mcp_client: + # First verify the search endpoint returns tools + result = mcp_client.call_tool_sync( + tool_use_id="debug-search", + name="x_amz_bedrock_agentcore_search", + arguments={"query": "get weather information"}, + ) + logger.info("Raw search result: %s", result) + + agent = Agent( + system_prompt="You are a helpful assistant. Use available tools to help the user.", + tools=[], + plugins=[plugin], + ) + # Trigger an invocation so the hook fires + agent("What is the weather in Seattle?") + + logger.info("Loaded tool names: %s", plugin._loaded_tool_names) + # The gateway should have returned the get_weather tool + assert len(plugin._loaded_tool_names) > 0, ( + f"Expected tools to be loaded but got none. Raw search result was: {result}" + ) + + @pytest.mark.order(5) + def test_empty_intent_loads_no_tools(self): + """Plugin does not search gateway when intent is empty.""" + from unittest.mock import Mock + + from strands.hooks import BeforeInvocationEvent + + mcp_client = self._make_mcp_client() + provider = FixedIntentProvider("") + plugin = AgentCoreToolSearchPlugin(mcp_client=mcp_client, intent_provider=provider) + + # Simulate a before_invocation event + event = Mock(spec=BeforeInvocationEvent) + event.messages = [{"role": "user", "content": [{"text": "hello"}]}] + event.agent = Mock() + event.agent.model = None + event.agent.tool_registry = Mock() + event.agent.tool_registry.registry = {} + + plugin.on_before_invocation(event) + + assert len(plugin._loaded_tool_names) == 0 + + @pytest.mark.order(6) + def test_tools_cleared_between_invocations(self): + """Previously loaded tools are cleared before each new search.""" + from unittest.mock import Mock + + from strands.hooks import BeforeInvocationEvent + + mcp_client = self._make_mcp_client() + provider = FixedIntentProvider("get weather information") + plugin = AgentCoreToolSearchPlugin(mcp_client=mcp_client, intent_provider=provider) + + with mcp_client: + # First: simulate invocation with a real intent + event = Mock(spec=BeforeInvocationEvent) + event.messages = [{"role": "user", "content": [{"text": "weather"}]}] + event.agent = Mock() + event.agent.model = None + event.agent.tool_registry = Mock() + event.agent.tool_registry.registry = {} + + plugin.on_before_invocation(event) + first_tools = set(plugin._loaded_tool_names) + logger.info("First invocation tools: %s", first_tools) + assert len(first_tools) > 0 + + # Second: switch to empty intent — tools should be cleared + provider._intent = "" + event.agent.tool_registry.registry = {name: Mock() for name in first_tools} + + plugin.on_before_invocation(event) + second_tools = set(plugin._loaded_tool_names) + logger.info("Second invocation tools: %s", second_tools) + + assert len(second_tools) == 0 + # Verify old tools were removed from registry + for name in first_tools: + assert name not in event.agent.tool_registry.registry + + +@pytest.mark.integration +class TestStrandsIntentProviderIntegration: + """Integration tests for StrandsIntentProvider with a real LLM.""" + + @classmethod + def setup_class(cls): + cls.region = os.environ.get("BEDROCK_TEST_REGION", "us-west-2") + + def test_derive_intent_from_messages(self): + """StrandsIntentProvider produces a non-empty intent string from messages.""" + provider = StrandsIntentProvider(message_window=3) + messages = [ + {"role": "user", "content": [{"text": "What's the weather like in Seattle today?"}]}, + {"role": "assistant", "content": [{"text": "Let me check the weather for you."}]}, + {"role": "user", "content": [{"text": "Also check tomorrow's forecast."}]}, + ] + + intent = provider.derive_intent(messages) + + logger.info("Derived intent: %s", intent) + assert isinstance(intent, str) + assert len(intent) > 0 + + def test_derive_intent_empty_messages(self): + """StrandsIntentProvider returns empty string for empty messages.""" + provider = StrandsIntentProvider() + intent = provider.derive_intent([]) + assert intent == "" + + def test_derive_intent_with_custom_model(self): + """StrandsIntentProvider works with an explicitly provided model.""" + from strands.models.bedrock import BedrockModel + + model = BedrockModel( + model_id="us.anthropic.claude-haiku-4-5-20251001-v1:0", + region_name=self.region, + ) + provider = StrandsIntentProvider(model=model) + messages = [ + {"role": "user", "content": [{"text": "I need to send an email to my team about the project update."}]}, + ] + + intent = provider.derive_intent(messages) + + logger.info("Derived intent with custom model: %s", intent) + assert isinstance(intent, str) + assert len(intent) > 0 + + def test_derive_intent_respects_message_window(self): + """StrandsIntentProvider only considers the last N messages.""" + provider = StrandsIntentProvider(message_window=2) + messages = [ + {"role": "user", "content": [{"text": "Tell me about dogs."}]}, + {"role": "assistant", "content": [{"text": "Dogs are great pets."}]}, + {"role": "user", "content": [{"text": "Now tell me about the stock market."}]}, + {"role": "assistant", "content": [{"text": "The stock market is complex."}]}, + {"role": "user", "content": [{"text": "What are the best investment strategies?"}]}, + ] + + intent = provider.derive_intent(messages) + + logger.info("Derived intent (window=2): %s", intent) + assert isinstance(intent, str) + assert len(intent) > 0 diff --git a/uv.lock b/uv.lock index 81a9ffb4..a809fc3d 100644 --- a/uv.lock +++ b/uv.lock @@ -295,6 +295,7 @@ simulation = [ { name = "strands-agents-evals" }, ] strands-agents = [ + { name = "mcp" }, { name = "strands-agents" }, ] strands-agents-evals = [ @@ -328,6 +329,7 @@ requires-dist = [ { name = "boto3", specifier = ">=1.43.0" }, { name = "botocore", specifier = ">=1.43.0" }, { name = "jinja2", marker = "extra == 'simulation'", specifier = ">=3.1.0" }, + { name = "mcp", marker = "extra == 'strands-agents'", specifier = ">=1.23.0,<2.0.0" }, { name = "pydantic", specifier = ">=2.0.0,<2.41.3" }, { name = "starlette", specifier = ">=0.46.2" }, { name = "strands-agents", marker = "extra == 'strands-agents'", specifier = ">=1.20.0" }, From 0774c7db95e365a0159b4f2c5d61fc60bdf1edc1 Mon Sep 17 00:00:00 2001 From: Senthil Mohan Date: Thu, 28 May 2026 23:45:29 +0100 Subject: [PATCH 3/6] style: fix linting and formatting for AgentCoreToolSearchPlugin --- .../strands/plugins/agentcore_tool_search/README.md | 2 +- .../integrations/strands/test_agentcore_tool_search_plugin.py | 2 +- .../gateway/integrations/strands/test_intent_providers.py | 2 +- .../gateway/integrations/test_agentcore_tool_search_plugin.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md index 162a7e83..5d5ed741 100644 --- a/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md +++ b/src/bedrock_agentcore/gateway/integrations/strands/plugins/agentcore_tool_search/README.md @@ -135,4 +135,4 @@ agent = Agent(plugins=[ - Tools registered on the gateway with descriptions - AWS credentials with access to the gateway -For more details, see the [AgentCore Gateway Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-building.html). \ No newline at end of file +For more details, see the [AgentCore Gateway Documentation](https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-building.html). diff --git a/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py b/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py index 9623df25..da34bf82 100644 --- a/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py +++ b/tests/bedrock_agentcore/gateway/integrations/strands/test_agentcore_tool_search_plugin.py @@ -6,8 +6,8 @@ import pytest from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import ( - StrandsIntentProvider, IntentProvider, + StrandsIntentProvider, ) from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.plugin import ( AgentCoreToolSearchPlugin, diff --git a/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py b/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py index d352d54a..e8a67ba6 100644 --- a/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py +++ b/tests/bedrock_agentcore/gateway/integrations/strands/test_intent_providers.py @@ -5,8 +5,8 @@ import pytest from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import ( - StrandsIntentProvider, IntentProvider, + StrandsIntentProvider, ) diff --git a/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py b/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py index c718d712..a3fa3a1f 100644 --- a/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py +++ b/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py @@ -36,8 +36,8 @@ from bedrock_agentcore.gateway.client import GatewayClient from bedrock_agentcore.gateway.integrations.strands.plugins import AgentCoreToolSearchPlugin from bedrock_agentcore.gateway.integrations.strands.plugins.agentcore_tool_search.intent_providers import ( - StrandsIntentProvider, IntentProvider, + StrandsIntentProvider, ) logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") From 8ce79b14e925c3228f8ddeb3ae6cd0ad4a5a133c Mon Sep 17 00:00:00 2001 From: Senthil Mohan Date: Fri, 29 May 2026 18:03:42 +0100 Subject: [PATCH 4/6] fix: add mcp-proxy-for-aws to dev dependencies for integration tests --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index b454ecda..474747c6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -156,6 +156,7 @@ dev = [ "strands-agents-evals>=0.1.0", "a2a-sdk[http-server]>=0.3", "ag-ui-protocol>=0.1.10", + "mcp-proxy-for-aws>=0.1.0", ] [project.optional-dependencies] From f81678960786cc068e0b599d7a466df9948ebc61 Mon Sep 17 00:00:00 2001 From: Senthil Mohan Date: Fri, 29 May 2026 18:53:34 +0100 Subject: [PATCH 5/6] test: auto-provision IAM role and Lambda for integration tests --- .../test_agentcore_tool_search_plugin.py | 174 +++++++++++++++--- 1 file changed, 150 insertions(+), 24 deletions(-) diff --git a/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py b/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py index a3fa3a1f..a10a0c54 100644 --- a/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py +++ b/tests_integ/gateway/integrations/test_agentcore_tool_search_plugin.py @@ -1,36 +1,23 @@ """Integration tests for AgentCoreToolSearchPlugin. -Requires environment variables: +If GATEWAY_ROLE_ARN and GATEWAY_LAMBDA_ARN are set, uses those directly. +Otherwise, automatically provisions the IAM role and Lambda function, +and tears them down after the test run. + +Environment variables (all optional): BEDROCK_TEST_REGION: AWS region (default: us-west-2) GATEWAY_ROLE_ARN: IAM role ARN with AgentCore gateway trust policy - GATEWAY_LAMBDA_ARN: Lambda ARN for the gateway target (must implement MCP tool handler) - -Prerequisites: - 1. Deploy the Lambda in tests_integ/gateway/integrations/lambda_function/lambda_function.py - (Python 3.10+ runtime, handler: lambda_function.lambda_handler) - - 2. The GATEWAY_ROLE_ARN must have: - - Trust policy for bedrock-agentcore.amazonaws.com - - lambda:InvokeFunction permission on the GATEWAY_LAMBDA_ARN - - Example inline policy: - { - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Action": "lambda:InvokeFunction", - "Resource": "" - }] - } - - 3. Install mcp-proxy-for-aws: uv pip install mcp-proxy-for-aws - + GATEWAY_LAMBDA_ARN: Lambda ARN for the gateway target """ +import io +import json import logging import os import time +import zipfile +import boto3 import pytest from bedrock_agentcore.gateway.client import GatewayClient @@ -43,6 +30,109 @@ logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) +# Infrastructure constants +_ROLE_NAME = "integ-test-gateway-role" +_LAMBDA_NAME = "integ-test-lambda" +_LAMBDA_HANDLER = "lambda_function.lambda_handler" +_LAMBDA_RUNTIME = "python3.10" + +_TRUST_POLICY = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": {"Service": "bedrock-agentcore.amazonaws.com"}, + "Action": "sts:AssumeRole", + }, + { + "Effect": "Allow", + "Principal": {"Service": "lambda.amazonaws.com"}, + "Action": "sts:AssumeRole", + }, + ], +} + +_LAMBDA_INVOKE_POLICY_TEMPLATE = { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "lambda:InvokeFunction", + "Resource": None, # filled in after Lambda creation + } + ], +} + + +def _get_lambda_zip() -> bytes: + """Package lambda_function.py into a zip archive.""" + lambda_path = os.path.join(os.path.dirname(__file__), "lambda_function", "lambda_function.py") + buf = io.BytesIO() + with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as zf: + zf.write(lambda_path, "lambda_function.py") + return buf.getvalue() + + +def _ensure_role(iam_client) -> str: + """Create the gateway IAM role if it doesn't exist, return its ARN.""" + try: + response = iam_client.get_role(RoleName=_ROLE_NAME) + return response["Role"]["Arn"] + except iam_client.exceptions.NoSuchEntityException: + pass + + response = iam_client.create_role( + RoleName=_ROLE_NAME, + AssumeRolePolicyDocument=json.dumps(_TRUST_POLICY), + Description="Integration test role for AgentCore gateway tests", + ) + role_arn = response["Role"]["Arn"] + + iam_client.attach_role_policy( + RoleName=_ROLE_NAME, + PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ) + # Wait for IAM propagation + time.sleep(10) + logger.info("Created role: %s", role_arn) + return role_arn + + +def _attach_lambda_invoke_policy(iam_client, lambda_arn: str): + """Attach a scoped lambda:InvokeFunction policy to the gateway role.""" + policy = _LAMBDA_INVOKE_POLICY_TEMPLATE.copy() + policy["Statement"] = [{"Effect": "Allow", "Action": "lambda:InvokeFunction", "Resource": lambda_arn}] + iam_client.put_role_policy( + RoleName=_ROLE_NAME, + PolicyName="lambda-invoke", + PolicyDocument=json.dumps(policy), + ) + + +def _ensure_lambda(lambda_client, role_arn: str) -> str: + """Create or update the test Lambda, return its ARN.""" + zip_bytes = _get_lambda_zip() + try: + response = lambda_client.get_function(FunctionName=_LAMBDA_NAME) + lambda_client.update_function_code(FunctionName=_LAMBDA_NAME, ZipFile=zip_bytes) + return response["Configuration"]["FunctionArn"] + except lambda_client.exceptions.ResourceNotFoundException: + pass + + response = lambda_client.create_function( + FunctionName=_LAMBDA_NAME, + Runtime=_LAMBDA_RUNTIME, + Role=role_arn, + Handler=_LAMBDA_HANDLER, + Code={"ZipFile": zip_bytes}, + Timeout=30, + Description="MCP test Lambda for AgentCore gateway integration tests", + ) + waiter = lambda_client.get_waiter("function_active_v2") + waiter.wait(FunctionName=_LAMBDA_NAME) + logger.info("Created Lambda: %s", response["FunctionArn"]) + return response["FunctionArn"] + class FixedIntentProvider(IntentProvider): """Intent provider that returns a fixed string for deterministic testing.""" @@ -67,9 +157,18 @@ def setup_class(cls): cls.region = os.environ.get("BEDROCK_TEST_REGION", "us-west-2") cls.role_arn = os.environ.get("GATEWAY_ROLE_ARN") cls.lambda_arn = os.environ.get("GATEWAY_LAMBDA_ARN") + cls._provisioned_infra = False if not cls.role_arn or not cls.lambda_arn: - pytest.fail("GATEWAY_ROLE_ARN and GATEWAY_LAMBDA_ARN must be set") + # Auto-provision infrastructure + session = boto3.Session(region_name=cls.region) + iam_client = session.client("iam") + lambda_client = session.client("lambda", region_name=cls.region) + cls.role_arn = _ensure_role(iam_client) + cls.lambda_arn = _ensure_lambda(lambda_client, cls.role_arn) + _attach_lambda_invoke_policy(iam_client, cls.lambda_arn) + cls._provisioned_infra = True + logger.info("Auto-provisioned infrastructure: role=%s, lambda=%s", cls.role_arn, cls.lambda_arn) cls.gw_client = GatewayClient(region_name=cls.region) cls.test_prefix = f"sdk-integ-plugin-{int(time.time())}" @@ -158,6 +257,33 @@ def teardown_class(cls): except Exception as e: logger.warning("Failed to delete gateway %s: %s", cls.gateway_id, e) + # Clean up auto-provisioned infrastructure + if cls._provisioned_infra: + session = boto3.Session(region_name=cls.region) + lambda_client = session.client("lambda", region_name=cls.region) + iam_client = session.client("iam") + try: + lambda_client.delete_function(FunctionName=_LAMBDA_NAME) + logger.info("Deleted Lambda: %s", _LAMBDA_NAME) + except Exception as e: + logger.warning("Failed to delete Lambda: %s", e) + try: + iam_client.delete_role_policy(RoleName=_ROLE_NAME, PolicyName="lambda-invoke") + except Exception: + pass + try: + iam_client.detach_role_policy( + RoleName=_ROLE_NAME, + PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + ) + except Exception: + pass + try: + iam_client.delete_role(RoleName=_ROLE_NAME) + logger.info("Deleted role: %s", _ROLE_NAME) + except Exception as e: + logger.warning("Failed to delete role: %s", e) + def _make_mcp_client(self): """Create an MCPClient connected to the test gateway via Streamable HTTP with IAM auth.""" from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client From 6d515682f4d181b879c33259db2030ea5ecbe0e2 Mon Sep 17 00:00:00 2001 From: Senthil Mohan Date: Fri, 29 May 2026 21:39:55 +0100 Subject: [PATCH 6/6] ci: add mcp-proxy-for-aws as extra dep for gateway integration tests --- .github/workflows/integration-testing.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration-testing.yml b/.github/workflows/integration-testing.yml index a4964567..40d57dc5 100644 --- a/.github/workflows/integration-testing.yml +++ b/.github/workflows/integration-testing.yml @@ -146,7 +146,7 @@ jobs: - group: gateway path: tests_integ/gateway timeout: 15 - extra-deps: "" + extra-deps: "mcp-proxy-for-aws" ignore: "" - group: identity path: tests_integ/identity/test_identity_client.py