
    PL
jT                       U d Z ddlZddlZddlZddlZddlZddlmZmZm	Z	m
Z
mZ ddlZerddlmZ ddlmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZmZ ddlm Z m!Z!m"Z" ddl#m$Z$m%Z% ddl&m'Z' dZ(e
e	         e)d	<   dZ*e
e	         e)d
<   dZ+e
e	         e)d<   dZ,e
e	         e)d<   dZ-e
e	         e)d<   ddl.m/Z/m0Z0m1Z1 ddl2m3Z3 ddl4m5Z5m6Z7m8Z8 ddl9m:Z:m;Z; ddl<m=Z= ddl>m?Z? ddl@Z@ ejA        eB          ZCdeDdeEfdZFdeGfdZHdeDfdZIdeDfdZJdeDfdZKdeDdeDfdZLdeDdeEfdZMdeEfdZNdeOeD         fd ZPd!ZQd"e	deEfd#ZRdd$e
eD         deSe
e	         e
eD         eeDe	f         f         fd%ZTde
eD         fd&ZU e3d'd()          ZVd*d*deQfd+eDd,eDd-eDd$e
eD         d.eWde
eD         fd/ZX	 	 	 dd+eDd2eDd$e
eD         d3eWd4eEd5eDde
eD         fd6ZYd+eDd2eDd$e
eD         d7eWd8eWde
eD         fd9ZZd:eDdeDfd;Z[dd=eDd>eWdeDfd?Z\dd@deQfdAeeD         dBeDdCeEd$e
eD         d.eWdeDfdDZ]ddEd@deQfd,eDdFeDdGeDdCeEd$e
eD         d.eWdeDfdHZ^deEfdIZ_deEfdJZ`eBdKk    r	  eadL            eadM            e_            Zb e            Zc eE ejd        dNd*          e                                          Zf eE ejd        dOd*          e                                          Zg e`            Zh eU            Ziebr! eI            Zj eadPej            ejdQk    r eadR           nejdSk    r eadT           nejdUk    r eadV           nejdWk    r0 eadX ejd        dYd*          e                                            nejdZk    r ead[           nejd\k    r ead]           negrB ead^ ejd        dO          e                                k                    d_                      nTefr ead`           nFecr eada e                        n- eadb           n! eadc            eadd e                        ehs" eade            eadf            eadg           n eadhei            ebs e@jl        di            eadj           ehr eadkei             eadleQ dm           eVjm        r0 eadneVjn                     eadoeVjo         dpeVjn         dq           n eadr            eads            eadt            eadu            ead*            eadv            eadw            ead*            eadx            eady            eadz            ead{            ead|           ehr ead}            ead~            ead            ead*            ead            ead            ead            ead            ead            ead            ead            ead*            ead            ead            ead            ead            ead            ead            ead            ead            ead            ead            ead            ead           ddlpmqZqmrZr dddddddddidd<ddd=gddZsddddAdddidd<didAgddZt eqju        ddesd e_ eP            dd            eqju        ddetd e_ eP            d@dd	  	         dS )a%  
Standalone Web Tools Module

This module provides generic web tools that work with multiple backend providers.
Backend is selected during ``hermes tools`` setup (web.backend in config.yaml).
When available, Hermes can route Firecrawl calls through a Nous-hosted tool-gateway
for Nous Subscribers only.

Available tools:
- web_search_tool: Search the web for information
- web_extract_tool: Extract content from specific web pages
- web_crawl_tool: Crawl websites with specific instructions

Backend compatibility:
- Exa: https://exa.ai (search, extract)
- Firecrawl: https://docs.firecrawl.dev/introduction (search, extract, crawl; direct or derived firecrawl-gateway.<domain> for Nous Subscribers)
- Parallel: https://docs.parallel.ai (search, extract)
- Tavily: https://tavily.com (search, extract, crawl)

LLM Processing:
- Uses OpenRouter API with Gemini 3 Flash Preview for intelligent content extraction
- Extracts key excerpts and creates markdown summaries to reduce token usage

Debug Mode:
- Set WEB_TOOLS_DEBUG=true to enable detailed logging
- Creates web_tools_debug_UUID.json in ./logs directory
- Captures all tool calls, results, and compression metrics

Usage:
    from web_tools import web_search_tool, web_extract_tool, web_crawl_tool
    
    # Search the web
    results = web_search_tool("Python machine learning libraries", limit=3)
    
    # Extract content from URLs  
    content = web_extract_tool(["https://example.com"], format="markdown")
    
    # Crawl a website
    crawl_data = web_crawl_tool("example.com", "Find contact information")
    N)ListDictAnyOptionalTYPE_CHECKING)	Firecrawl)r   _FirecrawlProxy_FIRECRAWL_CLS_CACHE_extract_scrape_payload_extract_web_search_results_firecrawl_backend_help_suffix_get_direct_firecrawl_config_get_firecrawl_client_get_firecrawl_gateway_url_has_direct_firecrawl_config_is_tool_gateway_ready_load_firecrawl_cls_normalize_result_list&_raise_web_backend_configuration_error_to_plain_objectcheck_firecrawl_api_key)_normalize_tavily_documents _normalize_tavily_search_results_tavily_request)_get_async_parallel_client_get_parallel_client)_get_exa_client_firecrawl_client_firecrawl_client_config_parallel_client_async_parallel_client_exa_client)async_call_llmextract_content_or_reasoningget_async_text_auxiliary_client)DebugSession)build_vendor_gateway_urlread_nous_access_tokenresolve_managed_tool_gateway)managed_nous_tools_enabledprefers_gateway)is_safe_url)check_website_accessnamereturnc                 p    t          j        |           }t          |o|                                          S N)osgetenvboolstrip)r.   vals     3/home/kuhnn/.hermes/hermes-agent/tools/web_tools.py_has_envr8   {   s*    
)D//C#		$$$    c                  ~    	 ddl m}   |                                 di           S # t          t          f$ r i cY S w xY w)z5Load the ``web:`` section from ~/.hermes/config.yaml.r   load_configweb)hermes_cli.configr<   getImportError	Exceptionr;   s    r7   _load_web_configrB      s^    111111{}}  +++#   			s   #& <<c            	         t                                          d          pd                                                                } | dv r| S dt	          d          pt	          d          pt                      fdt	          d          fd	t	          d
          fdt	          d          fdt	          d          fdt	          d          fdt                      ff}|D ]\  }}|r|c S dS )zDetermine which web backend to use (shared fallback).

    Reads ``web.backend`` from config.yaml (set by ``hermes tools``).
    Falls back to whichever API key is present for users who configured
    keys manually without running setup.
    backend >   exaddgstavilysearxngparallel	firecrawl
brave-freerK   FIRECRAWL_API_KEYFIRECRAWL_API_URLrJ   PARALLEL_API_KEYrH   TAVILY_API_KEYrF   EXA_API_KEYrI   SEARXNG_URLrL   BRAVE_SEARCH_API_KEYrG   )rB   r?   lowerr5   r8   r   _ddgs_package_importable)
configuredbackend_candidatesrD   	availables       r7   _get_backendrY      s    #$$((339r@@BBHHJJJ``` 
h233px@S7T7TpXnXpXpq	X0112	8,--.	''(	H]++,	x 6778	)++, 1   	NNN	 ;r9   c                       t          d          S )uj  Determine which backend to use for web_search specifically.

    Selection priority:
    1. ``web.search_backend`` (per-capability override)
    2. ``web.backend`` (shared fallback — existing behavior)
    3. Auto-detect from env vars

    This enables using different providers for search vs extract
    (e.g. SearXNG for search + Firecrawl for extract).
    search_get_capability_backend r9   r7   _get_search_backendr_      s     #8,,,r9   c                       t          d          S )u   Determine which backend to use for web_extract specifically.

    Selection priority:
    1. ``web.extract_backend`` (per-capability override)
    2. ``web.backend`` (shared fallback — existing behavior)
    3. Auto-detect from env vars
    extractr\   r^   r9   r7   _get_extract_backendrb      s     #9---r9   
capabilityc                     t                      }|                    |  d          pd                                                                }|rt	          |          r|S t                      S )zShared helper for per-capability backend selection.

    Reads ``web.{capability}_backend`` from config; if set and available,
    uses it. Otherwise falls through to the shared ``_get_backend()``.
    _backendrE   )rB   r?   rT   r5   _is_backend_availablerY   )rc   cfgspecifics      r7   r]   r]      sm     

C:///006B==??EEGGH )(33 >>r9   rD   c                 (   | dk    rt          d          S | dk    rt          d          S | dk    rt                      S | dk    rt          d          S | dk    rt          d	          S | d
k    rt          d          S | dk    rt                      S dS )z:Return True when the selected backend is currently usable.rF   rQ   rJ   rO   rK   rH   rP   rI   rR   rL   rS   rG   F)r8   r   rU   )rD   s    r7   rf   rf      s    %&&&**++++&((((())))&&&,.///&')))5r9   c                  2    	 ddl } dS # t          $ r Y dS w xY w)aJ  Return True when the ``ddgs`` Python package can be imported.

    ddgs is the only backend whose availability is driven by a package
    presence rather than an env var / config entry.  Wrapped in a helper
    so auto-detect and ``_is_backend_available`` share the same check
    (and tests can monkeypatch a single symbol).
    r   NTF)rG   r@   )rG   s    r7   rU   rU      s7    t   uus    
c                  
    g dS )u  Return tool metadata env vars for the currently enabled web backends.

    The gateway env vars are always reported — they're metadata strings
    used by the tool registry to light up the tool when the variable is
    set.  Gating them on ``managed_nous_tools_enabled()`` only saved
    string noise in the metadata list, but cost a synchronous HTTP
    refresh against the Nous portal on every CLI startup (invoked at
    tool-registration time).  The behavioral contract is: if the env var
    is set, the tool sees it; if not, it doesn't.  Not-logged-in users
    simply don't have the vars set, so the extra entries are harmless.
    )	rQ   rO   rP   rM   rN   FIRECRAWL_GATEWAY_URLTOOL_GATEWAY_DOMAINTOOL_GATEWAY_SCHEMETOOL_GATEWAY_USER_TOKENr^   r^   r9   r7   _web_requires_envrp      s    
 
 
 
r9     clientc                     ddl m} t          t          | dd          pd          } ||          j        pd                                }|dk    p|                    d          S )z?Return True when the resolved auxiliary backend is Nous Portal.r   )urlparsebase_urlrE   znousresearch.comz.nousresearch.com)urllib.parsert   strgetattrhostnamerT   endswith)rr   rt   ru   hosts       r7   _is_nous_auxiliary_clientr|      ss    %%%%%%76:r228b99HHX'-24466D%%K7J)K)KKr9   modelc                     t          d          \  }}t          j        dd                                          }| p|p|}i }|1t	          |          r"ddlm} ddlm}  |            pd |            i}|||fS )	zHResolve the current web-extract auxiliary client, model, and extra body.web_extractAUXILIARY_WEB_EXTRACT_MODELrE   Nr   )get_auxiliary_extra_body)nous_portal_tagstags)	r%   r2   r3   r5   r|   agent.auxiliary_clientr   agent.portal_tagsr   )r}   rr   default_modelconfigured_modeleffective_model
extra_bodyr   r   s           r7   _resolve_web_extract_auxiliaryr   )  s    ;MJJFMy!>CCIIKK@/@=O!#J7??CCCCCC666666--//OF<L<L<N<N3O
?J..r9   c                  *    t                      \  } }} |S )zBReturn the current default model for web extraction summarization.r   )_r}   s     r7   _get_default_summarizer_modelr   8  s    022KAuaLr9   	web_toolsWEB_TOOLS_DEBUG)env_varrE   contenturltitle
min_lengthc                   K   d}d}d}d}	 t          |           }	|	|k    r'|	dz  }
t                              d|
           d|
dd	S |	|k     rt                              d
|	|           dS g }|r|                    d|            |r|                    d|            |rd                    |          dz   nd}|	|k    r4t                              d|	           t          | ||||           d{V S t                              d|	           t          | ||           d{V }|r\t          |          |k    r|d|         dz   }t          |          }|	dk    r||	z  nd}t                              d|	||dz             |S # t          $ rt}t                              dt          |          dd                    | d|         }t          |           |k    r|d|ddt          |           ddz  }|cY d}~S d}~ww xY w)a  
    Process web content using LLM to create intelligent summaries with key excerpts.
    
    This function uses Gemini 3 Flash Preview (or specified model) via OpenRouter API 
    to intelligently extract key information and create markdown summaries,
    significantly reducing token usage while preserving all important information.
    
    For very large content (>500k chars), uses chunked processing with synthesis.
    For extremely large content (>2M chars), refuses to process entirely.
    
    Args:
        content (str): The raw content to process
        url (str): The source URL (for context, optional)
        title (str): The page title (for context, optional)
        model (str): The model to use for processing (default: google/gemini-3-flash-preview)
        min_length (int): Minimum content length to trigger processing (default: 5000)
        
    Returns:
        Optional[str]: Processed markdown content, or None if content too short or processing fails
    i i  順 rq   i@B z<Content too large (%.1fMB > 2MB limit). Refusing to process.z[Content too large to process: z.1fzdMB. Try using web_crawl with specific extraction instructions, or search for a more focused source.]z:Content too short (%d < %d chars), skipping LLM processingNzTitle: zSource: 


rE   z5Content large (%d chars). Using chunked processing...z+Processing content with LLM (%d characters)4

[... summary truncated for context management ...]r         ?z*Content processed: %d -> %d chars (%.1f%%)d   zweb_extract LLM summarization failed (%s). Tip: increase auxiliary.web_extract.timeout in config.yaml or switch to a faster auxiliary model.x   u'   

[Content truncated — showing first , of z chars. LLM summarization timed out. To fix: increase auxiliary.web_extract.timeout in config.yaml, or use a faster auxiliary model. Use browser_navigate for the full page.])lenloggerwarningdebugappendjoininfo_process_large_content_chunked_call_summarizer_llmrA   rw   )r   r   r   r}   r   MAX_CONTENT_SIZECHUNK_THRESHOLD
CHUNK_SIZEMAX_OUTPUT_SIZEcontent_lensize_mbcontext_infocontext_strprocessed_contentprocessed_lengthcompression_ratioe	truncateds                     r7   process_content_with_llmr   @  s     8 !OJO@'ll )))!I-GNNY[bccc gW  g  g  g  g  g ##LLUWbdnooo4  	3 1% 1 1222 	2 03 0 0111:FNdii--66B ((KKOQ\]]]7eZ        
 	A;OOO"6wU"S"SSSSSSS 	~$%%77$56F6F$G  KC  %C!  ##455BMPQ// 0; > >WZKKDkScevy|e|}}}     5 FF4C4L		
 	
 	
 ,_,-	w<</))]OS ] ]w<<H] ] ]I %s2   ;F
 "F
 ,BF
 8BF
 

HA)H=HH N  Fr   
max_tokensis_chunk
chunk_infoc           
        K   |rd}d| | d|  d}nd}d| d|  d}d	}d	}	d
}
t          |          D ]}	 t          |          \  }}}||st                              d            d
S d|d|dd|dgd|d}|r||d<   t	          di | d
{V }t          |          } | r| c S t                              d|dz   |           ||dz
  k     r.t          j        |	           d
{V  t          |	d	z  d          }	| c S # t          $ r t                              d           Y  d
S t          $ r}|}
||dz
  k     r~t                              d|dz   |t          |          d
d                    t                              d|	           t          j        |	           d
{V  t          |	d	z  d          }	n|
Y d
}~d
}~ww xY wd
S )a  
    Make a single LLM call to summarize content.
    
    Args:
        content: The content to summarize
        context_str: Context information (title, URL)
        model: Model to use
        max_tokens: Maximum output tokens
        is_chunk: Whether this is a chunk of a larger document
        chunk_info: Information about chunk position (e.g., "Chunk 2/5")
        
    Returns:
        Summarized content or None on failure
    a  You are an expert content analyst processing a SECTION of a larger document. Your job is to extract and summarize the key information from THIS SECTION ONLY.

Important guidelines for chunk processing:
1. Do NOT write introductions or conclusions - this is a partial document
2. Focus on extracting ALL key facts, figures, data points, and insights from this section
3. Preserve important quotes, code snippets, and specific details verbatim
4. Use bullet points and structured formatting for easy synthesis later
5. Note any references to other sections (e.g., "as mentioned earlier", "see below") without trying to resolve them

Your output will be combined with summaries of other sections, so focus on thorough extraction rather than narrative flow.zAExtract key information from this SECTION of a larger document:

z

SECTION CONTENT:
z

Extract all important information from this section in a structured format. Focus on facts, data, insights, and key details. Do not add introductions or conclusions.a~  You are an expert content analyst. Your job is to process web content and create a comprehensive yet concise summary that preserves all important information while dramatically reducing bulk.

Create a well-structured markdown summary that includes:
1. Key excerpts (quotes, code snippets, important facts) in their original format
2. Comprehensive summary of all other important information
3. Proper markdown formatting with headers, bullets, and emphasis

Your goal is to preserve ALL important information while reducing length. Never lose key facts, figures, insights, or actionable information. Make it scannable and well-organized.zNPlease process this web content and create a comprehensive markdown summary:

zCONTENT TO PROCESS:
z

Create a markdown summary that captures all key information in a well-organized, scannable format. Include important quotes and code snippets in their original formatting. Focus on actionable information, specific details, and unique insights.   Nz7No auxiliary model available for web content processingr   systemroler   user皙?taskr}   messagestemperaturer   r   z4LLM returned empty content (attempt %d/%d), retrying   <   z'LLM API call failed (attempt %d/%d): %sr   zRetrying in %ds...r^   )ranger   r   r   r#   r$   asynciosleepminRuntimeErrorrA   rw   )r   r   r}   r   r   r   system_promptuser_promptmax_retriesretry_delay
last_errorattempt
aux_clientr   r   call_kwargsresponse	api_errors                     r7   r   r     s     ,  &w	~iii i
 	i i iwww w 	w w w KKJ%% -! -!,	!6TUZ6[6[3J!!XYYYtt%(%-@@#<<  #( K  7,6L)+::k::::::::H28<<G NNQSZ]^S^`klllq((mK000000000!+/266NNN 	 	 	NNTUUU444 	! 	! 	!"Jq((H'TU+Wbdghqdrdrswtwswdxyyy3[AAAmK000000000!+/266   	! 4s1   1D+;D)AD?D$G
+	G
4BGG

chunk_sizemax_output_sizec           	        K   g t          dt          |           |          D ]$}| |||z            }                    |           %t                              dt                    |           dt
          dt          dt          t
          t          t                   f         ffdfdt                    D             }t          j        |dd	i d
{V }g }	|D ]H}
t          |
t                    rt                              d|
           3|	                    |
           Ig }t          |	d           D ]%\  }}|r|                    d|dz    d|            &|st                              d           dS t                              dt          |          t                               t          |          dk    r*|d         }t          |          |k    r|d
|         dz   }|S t                              dt          |                     d                    |          }d| d d| d}	 t%                    \  }}}||sQt                              d           d                    |          }t          |          |k    r|d
|         dz   }|S d|ddd d!|d gd"d#d$}|r||d%<   t'          d.i | d
{V }t)          |          }|s;t                              d&           t'          d.i | d
{V }t)          |          }|sQt                              d'           d                    |          }t          |          |k    r|d
|         dz   }|S t          |          |k    r|d
|         d(z   }t          |           }t          |          }|dk    r||z  nd)}t                              d*|||d+z             |S # t*          $ rq}t                              d,t          |          d
d+                    d                    |          }t          |          |k    r|d
|         d-z   }|cY d
}~S d
}~ww xY w)/a  
    Process large content by chunking, summarizing each chunk in parallel,
    then synthesizing the summaries.
    
    Args:
        content: The large content to process
        context_str: Context information
        model: Model to use
        chunk_size: Size of each chunk in characters
        max_output_size: Maximum final output size
        
    Returns:
        Synthesized summary or None on failure
    r   z&Split into %d chunks of ~%d chars each	chunk_idxchunk_contentr/   c           	        K   	 d| dz    dt                     d}t          |dd|           d{V }|rHt                              d	| dz   t                    t          |          t          |                     | |fS # t          $ rP}t                              d
| dz   t                    t          |          dd                    | dfcY d}~S d}~ww xY w)zSummarize a single chunk.z[Processing chunk r   r   ]i'  T)r   r   r   Nz&Chunk %d/%d summarized: %d -> %d charszChunk %d/%d failed: %s2   )r   r   r   r   rA   r   rw   )r   r   r   summaryr   chunksr   r}   s        r7   summarize_chunkz7_process_large_content_chunked.<locals>.summarize_chunk8  sL     	#Oi!mOOVOOOJ0 %        G  DDiRSmUXY_U`U`befsbtbtvy  {B  wC  wC  D  D  Dg%% 	# 	# 	#NN3Y]CKKQTUVQWQWX[Y[X[Q\]]]d?""""""	#s   BB 
C!ACC!C!c                 .    g | ]\  }} ||          S r^   r^   ).0ichunkr   s      r7   
<listcomp>z2_process_large_content_chunked.<locals>.<listcomp>L  s)    III81e__Q&&IIIr9   return_exceptionsTNz#Chunk summarization task failed: %sc                     | d         S )Nr   r^   )xs    r7   <lambda>z0_process_large_content_chunked.<locals>.<lambda>Z  s
    qt r9   )keyz## Section r   r   zAll chunk summarizations failedzB[Failed to process large content: all chunk summarizations failed]zGot %d/%d chunk summariesz

[... truncated ...]zSynthesizing %d summaries...z

---

a'  You have been given summaries of different sections of a large document. 
Synthesize these into ONE cohesive, comprehensive summary that:
1. Removes redundancy between sections
2. Preserves all key facts, figures, and actionable information
3. Is well-organized with clear structure
4. Is under z characters

zSECTION SUMMARIES:
z,

Create a single, unified markdown summary.z9No auxiliary model for synthesis, concatenating summariesr   r   r   zdYou synthesize multiple summaries into one cohesive, comprehensive summary. Be thorough but concise.r   r   r   r   r   r   z3Synthesis LLM returned empty content, retrying onceu>   Synthesis failed after retry — concatenating chunk summariesr   r   z+Synthesis complete: %d -> %d chars (%.2f%%)r   zSynthesis failed: %sz.

[... truncated due to synthesis failure ...]r^   )r   r   r   r   r   intrw   tupler   	enumerater   gather
isinstanceBaseExceptionr   sortedr   r   r   r#   r$   rA   )r   r   r}   r   r   r   r   tasksresultssuccessful_resultsresult_item	summariesr   r   resultcombined_summariessynthesis_promptr   r   r   fallbackr   r   final_summaryoriginal_len	final_lencompressionr   r   r   s    ``                         @@r7   r   r     sr     , F1c'llJ//  !j.()e
KK8#f++zRRR# #S #U3PXY\P]K]E^ # # # # # # # #( JIIIy7H7HIIIE NEBTBBBBBBBBG  / /k=11 	NN@+NNN!!+....I$%7^^LLL G G	7 	GE9q=EEGEEFFF T6777SS
KK+S^^S[[III 9~~1v;;((,_,-0IIF KK.I???&++I66
.
 
. 
. 
. 
. 
. 
. 
.72PQV2W2W/
OZ_NNVWWW{{9--H8}}..#$4_$458QQO "$!  /U  V  V,<== 	
 	
  	3(2K%'66+666666664X>>  	CNNPQQQ+::k::::::::H8BBM  	NN[\\\{{9--H8}}..#$4_$458QQO }//)*:?*:;>vvM7||&&	2>2B2Bi,..A<QZ\gjm\mnnn   -s1vvdsd|<<<;;y))x==?** 0 014ffHs.   A'O  +CO  3A,O   
Q*A&QQQtextc                 f    d}d}t          j        |d|           }t          j        |d|          }|S )a  
    Remove base64 encoded images from text to reduce token count and clutter.
    
    This function finds and removes base64 encoded images in various formats:
    - (data:image/png;base64,...)
    - (data:image/jpeg;base64,...)
    - (data:image/svg+xml;base64,...)
    - data:image/[type];base64,... (without parentheses)
    
    Args:
        text: The text content to clean
        
    Returns:
        Cleaned text with base64 images replaced with placeholders
    z+\(data:image/[^;]+;base64,[A-Za-z0-9+/=]+\)z'data:image/[^;]+;base64,[A-Za-z0-9+/=]+z[BASE64_IMAGE_REMOVED])resub)r   base64_with_parens_patternbase64_patterncleaned_texts       r7   clean_base64_imagesr    sG    $ "P @N 646NPTUUL 6.*BLQQLr9      querylimitc                 @   	 t          |          }n# t          t          f$ r d}Y nw xY wt          t	          |d          d          }| |dddddd}	 ddlm}  |            rt          d	d
          S ddlm	}m
} t                      }|r ||          nd}||                                s
 |            }|d
dd}n8t                              d|j        | |           |                    | |          }t#          |                    di                               dg                     |d<   t'          j        |dd
          }	t#          |	          |d<   t*                              d|           t*                                           |	S # t0          $ r}
dt3          |
           }t                              d|           ||d<   t*                              d|           t*                                           t          |          cY d}
~
S d}
~
ww xY w)aE  
    Search the web for information using available search API backend.

    This function provides a generic interface for web search that can work
    with multiple backends (Parallel or Firecrawl).

    Note: This function returns search result metadata only (URLs, titles, descriptions).
    Use web_extract_tool to get full content from specific URLs.
    
    Args:
        query (str): The search query to look up
        limit (int): Maximum number of results to return (default: 5)
    
    Returns:
        str: JSON string containing search results with the following structure:
             {
                 "success": bool,
                 "data": {
                     "web": [
                         {
                             "title": str,
                             "url": str,
                             "description": str,
                             "position": int
                         },
                         ...
                     ]
                 }
             }
    
    Raises:
        Exception: If search fails or API key is not set
    r  r   r   r	  r
  Nr   )
parameterserrorresults_countoriginal_response_sizefinal_response_sizeis_interruptedInterruptedFsuccess)get_active_search_providerget_providerzDNo web search provider configured. Run `hermes tools` to set one up.r  r  z#Web search via %s: '%s' (limit: %d)datar=   r  r   indentensure_asciir  web_search_toolzError searching web: %sr  )r   	TypeError
ValueErrorr   maxtools.interruptr  
tool_erroragent.web_search_registryr  r  r_   supports_searchr   r   r.   r[   r   r?   jsondumps_debuglog_callsaverA   rw   r   )r	  r
  debug_call_datar  r  _wsp_get_providerrD   providerresponse_dataresult_jsonr   	error_msgs               r7   r  r    s   DE

z"   E1s##E 
 
 "# 	 	O4%222222> 	<mU;;;;	
 	
 	
 	
 	
 	
 	
 	

 &''18B$$W---d8#;#;#=#= 2133H 8 MM KK5ue   %OOE599M+.}/@/@/L/L/P/PQVXZ/[/[+\+\(jquMMM14[1A1A-.)?;;; % % %4CFF44	T9%%%#, )?;;;)$$$$$$$$%s2    (( F 6DF 
HA4HHHTurlsformatuse_llm_processingc           	      T  #$K   ddl m} ddlm} | D ]N}|                    |          s|                     ||                    rt          j        ddd          c S O| |||ddddddg g d	}	 t                              d
t          |                      g }	g }
| D ]A}t          |          s|
                    |dddd           ,|	                    |           B|	sg }n#t                      }ddlm}m} |r ||          nd}||                                s\|5|                                s!t          j        d|j         ddd          S  |            }|t          j        dddd          S t                              d|j        t          |	                     ddl}|                    |j                  r|                    |	|           d{V }n"t-          j        |j        |	|           d{V }|
r|
|z   }d|i}t          |                    dg                     }t                              d|           ||d<   t          t          j        |                    |d<   |pt3                      #t5                      }|rq|rnt                              d           |d                             d           #fd$|                    dg           }$fd|D             }t-          j        |ddi d{V }|D ]}t9          |t:                    rt                              d|           3|\  }}}|                    d d!          }|d"k    rG|d#                             |           |d$xx         d%z  cc<   t                              d&|           |d'k    r7|d#                             |           t                              d(|           t                              d)|           n|r7|s5t                              d*           |d                             d+           |                    dg           D ]W}|                    d d!          }t          |                    d,d                    }t                              d-||           Xd. |                    dg           D             }d|i}|                    d          g k    rt?          d/          }tA          |          } n&t          j        |d0d1          }tA          |          } t          |           |d2<   |d                             d3           tB          "                    d4|           tB          #                                 | S # tH          $ r}!d5tK          |!           }"t          &                    d6|"           |"|d7<   tB          "                    d4|           tB          #                                 t?          |"          cY d}!~!S d}!~!ww xY w)8a#  
    Extract content from specific web pages using available extraction API backend.

    This function provides a generic interface for web content extraction that
    can work with multiple backends. Currently uses Firecrawl.

    Args:
        urls (List[str]): List of URLs to extract content from
        format (str): Desired output format ("markdown" or "html", optional)
        use_llm_processing (bool): Whether to process content with LLM for summarization (default: True)
        model (Optional[str]): The model to use for LLM processing (defaults to current auxiliary backend model)
        min_length (int): Minimum content length to trigger LLM processing (default: 5000)

    Security: URLs are checked for embedded secrets before fetching.
    
    Returns:
        str: JSON string containing extracted content. If LLM processing is enabled and successful,
             the 'content' field will contain the processed markdown summary instead of raw content.
    
    Raises:
        Exception: If extraction fails or API key is not set
    r   )
_PREFIX_RE)unquoteFz_Blocked: URL contains what appears to be an API key or token. Secrets must not be sent in URLs.r  )r2  r3  r4  r}   r   N)r  r  pages_extractedpages_processed_with_llmr  r  compression_metricsprocessing_appliedz!Extracting content from %d URL(s)rE   :Blocked: URL targets a private or internal network addressr   r   r   r  )get_active_extract_providerr  zy is a search-only backend and cannot extract URL content. Set web.extract_backend to firecrawl, tavily, exa, or parallel.r  zcNo web extract provider configured. Set web.extract_backend to firecrawl, tavily, exa, or parallel.zWeb extract via %s: %d URL(s))r3  r   zExtracted content from %d pagesr8  r  z3Processing extracted content with LLM (parallel)...r;  llm_processingc                   K   |                      dd          }|                      dd          }|                      dd          p|                      dd          }|s| ddfS t          |          }t          |||	
           d{V }|r3t          |          }|d	k    r||z  nd
}|| d<   || d<   ||||	d}| |dfS |||d
ddd}| |dfS )zHProcess a single result with LLM and return updated result with metrics.r   Unknown URLr   rE   raw_contentr   N
no_contentr   r   r   original_sizeprocessed_sizer   
model_used	processedcontent_too_shortr   rF  rG  r   rH  reason	too_shortr?   r   r   )r   r   r   rC  rF  rI  rG  r   metricsr   r   s            r7   process_single_resultz/web_extract_tool.<locals>.process_single_result  sR     jj66

7B//$jj;;Xvzz)UW?X?X" 6!455 #K 0 0 #;e_j# #      	  8%(^^NJWZ[J[J[(F(Fad% )2F9%,7F=)  #)6*8->&5 G "7K77  #)6*7-0&*"5 G "7K77r9   c                 &    g | ]} |          S r^   r^   )r   r   rP  s     r7   r   z$web_extract_tool.<locals>.<listcomp>  s%    NNNv**622NNNr9   r   Tz%Web result processing task failed: %sr   rB  rI  r:  r9  r   z%s (processed)rM  z&%s (no processing - content too short)z%s (no content to process)PLLM processing requested but no auxiliary model available, returning raw contentllm_processing_unavailablerC  z%s (%d characters)c                     g | ]h}|                     d d          |                     dd          |                     dd          |                     d          dd|v r
d|d         ini iS r   rE   r   r   r  r=  blocked_by_policyr?   r   rs     r7   r   z$web_extract_tool.<locals>.<listcomp>9  s     	
 	
 	
  uuUB''w++55B//w	 
 GZ]^F^F^)1-@+ABBdf	
 	
 	
r9   z%Content was inaccessible or not foundr   r  r  base64_image_removalweb_extract_toolzError extracting content: r  r  )'agent.redactr6  rv   r7  r[   r'  r(  r   r   r   r,   r   rb   r%  r>  r  supports_extractdisplay_namer.   inspectiscoroutinefunctionra   r   	to_threadr?   r   check_auxiliary_modelr   r   r   r   r$  r  r)  r*  r+  rA   rw   r   )%r2  r3  r4  r}   r   r6  r7  _urlr,  	safe_urlsssrf_blockedr   r   rD   r>  r-  r.  r_  r   r8  auxiliary_availableresults_listr   processed_resultsr   r   rO  statuscontent_lengthtrimmed_resultstrimmed_responser0  cleaned_resultr   r1  r   rP  s%       `                              @@r7   r[  r[  J  s     > ('''''$$$$$$  T"" 	j&7&7&F&F 	: =      	 "4$
 
 $%"# !  O"\%7TCCC 	-/ 	& 	&Cs## &##Y% %    
   %%%%  B	GG*,,G       
 6=F((111$Hx'@'@'B'B '0I0I0K0K':',#+#8 !< !< !<  &+    7688#:',!<  &+
 
 
 
 KK/I   NNN**8+;<<  ( 0 06 0 J JJJJJJJ !( 1$i! ! !      
  	-"W,Gw'hll9b99::5GGG-<)*47
88L8L4M4M01B#@#B#B355  O	G"5 O	GKKMNNN01889IJJJ)8 )8 )8 )8 )8 )8X $<<	266LNNNNNNNE '.ne&Tt&T&T T T T T T T  1 F Fk=99 NN#JKXXX*5'jj66[((#$9:AA'JJJ#$>???1D???KK 0#6666{**#$9:AA'JJJKK H#NNNNNN#?EEEEF  " [*= [qrrr 45<<=YZZZ",,y"55 G Gjj66!$VZZr%B%B!C!C0#~FFFF	
 	
 \\)R00	
 	
 	
 &7	**b00$%LMMK0==NN *%5aeTTTK0==N14^1D1D-.,-445KLLL 	*O<<< % % %9Q99	T9%%%#, *O<<<)$$$$$$$$%s-   5C'V $V PV 
X'(A4X"X'"X'basicinstructionsdepthc                 
  K   | ||||ddddddg g d}	 |pt                      t                      }t                      }ddlm}	m}
 |r |
|          nd}|K|                                s7|                                s!t          j	        d|j
         ddd	          S d}|
 |	            }|=|                                s)t          j	        dd
t                       ddd	          S ||                     d          sd|  } t          |           st          j	        d| ddddgid	          S t          |           }|rct                               d|d         |d                    t          j	        d| dd|d         |d         |d         |d         ddgid	          S ddlm}  |            rt)          dd          S t                               d|j        |            ddl}|dd}|r||d<   |                    |j                  r |j        | fi | d{V }nt3          j        |j        | fi | d{V }t7          |t8                    sdg i}|                    dg            t=          |                    dg                     }t                               d |           ||d!<   t=          t          j	        |                    |d"<   |r|rt                               d#           |d$                              d%           fd&fd'|                    dg           D             }t3          j!        |d(d)i d{V }|D ]j}t7          |tD                    rt           #                    d*|           3|\  }}}|d+k    r+|d,                              |           |d-xx         d.z  cc<   k|r7|s5t           #                    d/           |d$                              d0           d1 |                    dg           D             }t          j	        d|id2d3          }tI          |          }t=          |          |d4<   tJ          &                    d5|           tJ          '                                 |S t          j	        dd6t                       d7dd	          S # tP          $ r}d8tS          |           }t           *                    d9|           ||d:<   tJ          &                    d5|           tJ          '                                 t)          |          cY d}~S d}~ww xY w);a  
    Crawl a website with specific instructions using available crawling API backend.
    
    This function provides a generic interface for web crawling that can work
    with multiple backends. Currently uses Firecrawl.
    
    Args:
        url (str): The base URL to crawl (can include or exclude https://)
        instructions (str): Instructions for what to crawl/extract using LLM intelligence (optional)
        depth (str): Depth of extraction ("basic" or "advanced", default: "basic")
        use_llm_processing (bool): Whether to process content with LLM for summarization (default: True)
        model (Optional[str]): The model to use for LLM processing (defaults to current auxiliary backend model)
        min_length (int): Minimum content length to trigger LLM processing (default: 5000)
    
    Returns:
        str: JSON string containing crawled content. If LLM processing is enabled and successful,
             the 'content' field will contain the processed markdown summary instead of raw content.
             Each page is processed individually.
    
    Raises:
        Exception: If crawling fails or API key is not set
    )r   ro  rp  r4  r}   r   Nr   )r  r  pages_crawledr9  r  r  r:  r;  )get_active_crawl_providerr  Fzo is a search-only backend and cannot crawl URLs. Set FIRECRAWL_API_KEY for crawling, or use web_search instead.r  r?  zFweb_crawl requires Firecrawl. Set FIRECRAWL_API_KEY, FIRECRAWL_API_URLz*, or use web_search + web_extract instead.)zhttp://https://rt  r   rE   r<  r=  z#Blocked web_crawl for %s by rule %sr{   rulemessagesource)r{   ru  rw  )r   r   r   r  rV  r  r  r  zWeb crawl via %s: %s   )rp  r
  ro  zCrawled %d pagesrr  r  z1Processing crawled content with LLM (parallel)...r;  r@  c                 z  K   |                      dd          }|                      dd          }|                      dd          }|s| d dfS t          |          }t          |||           d {V }|r8|| d<   || d<   ||t          |          |rt          |          |z  ndd	}| |d
fS |||dd dd}| |dfS )Nr   rB  r   rE   r   rD  rC  r   rE  rI  rJ  rK  rM  rN  )	r   page_urlr   r   rF  rI  rO  r   r   s	          r7   _process_tavily_crawlz-web_crawl_tool.<locals>._process_tavily_crawl  s     %zz%??H"JJw33E$jjB77G" :%t\99$'LLM&>wRWYhjt&u&u u u u u u uI  <07}-,5y)*2]fijsftftZg8pI8V8Vmp  AP#Q #Q%w;;&.bo47tWjl lG!7K77r9   c                 &    g | ]} |          S r^   r^   )r   rY  r{  s     r7   r   z"web_crawl_tool.<locals>.<listcomp>  s%    WWWa..q11WWWr9   r   Tz'Tavily crawl processing task failed: %srI  r:  r9  r   rR  rS  c                     g | ]h}|                     d d          |                     dd          |                     dd          |                     d          dd|v r
d|d         ini iS rU  rW  rX  s     r7   r   z"web_crawl_tool.<locals>.<listcomp>'  s     O O Omn ()uuUB'7'7!%%QSBTBTabafafgprtauau  AB  AF  AF  GN  AO  AO  i  iFY]^F^F^)1-@+ABBdf i O O Or9   r   r  r  web_crawl_toolz`web_crawl has no available backend. Set FIRECRAWL_API_KEY (or FIRECRAWL_API_URL for self-hosted)zW, or set TAVILY_API_KEY for Tavily. Alternatively use web_search + web_extract instead.zError crawling website: r  r  )+r   rb  rY   r%  rs  r  supports_crawlr]  r'  r(  r^  is_availabler   
startswithr,   r-   r   r   r#  r  r$  r.   r_  r`  crawlr   ra  r   dict
setdefaultr   r?   r   r   r   r   r  r)  r*  r+  rA   rw   r   )r   ro  rp  r4  r}   r   r,  rf  rD   rs  r-  crawl_providerblocked_is_intr_  crawl_kwargsr   rr  r   rh  r   r   rO  ri  rk  r0  rm  r   r1  r{  r   s        `                       @@r7   r~  r~  c  sj     @ ("4$
 
 $%"# !  O$v%B#@#B#B355..	
 	
 	
 	
 	
 	
 	
 	

 8?H**7333D%n.K.K.M.M% "2244 z#(-: 2 2 2  "'    "N!6688N %n.I.I.K.K%:$C,J,L,LC C C  #
 
 
 
 %>>"9:: '&&& s## rz9sRTVY0[ 0[ /\ #]kpr r r r +3//G QA76?T[\bTcdddz9sRTVahiras29&/7SY?fmnvfw)x)x0z 0z /{ #| KPQ Q Q Q BAAAAAwyy @!-????KK.0CSIII NNN%*R88L </;^,**>+?@@ !5!5c!J!J\!J!JJJJJJJ!(!2"(#" "1=" "       h-- +%r?	2...  Y ; ;<<MKK*M:::/<OO,8;DJx<P<P8Q8QO45 " !I&9 !IOPPP 45<<=MNNN8 8 8 8 8 8$ XWWW8<<	SU;V;VWWW +2.%*XSW*X*X$X$X$X$X$X$X!#4 I IK!+}== !'PR]^^^ .9+FGV,,'(=>EEgNNN'(BCCCqHCCC! [*= [qrrr 45<<=YZZZO Orzr~r~  @I  KM  sN  sNO O OO*i%A!Z_```K0==N585H5HO12OO,o>>>KKMMM!!
 z J#A#C#CJ J J	 	 
 
 
 	
  % % %7s1vv77	T9%%%#, (/:::)$$$$$$$$%sG   BR> 'AR> 4A	R> >A3R> 2 R> KR> (R> >
UA4U<UUc                      t                                          dd                                                                          } | dv rt	          |           S t          d dD                       S )z6Check whether the configured web backend is available.rD   rE   >   rF   rG   rH   rI   rJ   rK   rL   c              3   4   K   | ]}t          |          V  d S r1   )rf   )r   rD   s     r7   	<genexpr>z$check_web_api_key.<locals>.<genexpr>R  s>         	g&&     r9   )rF   rJ   rK   rH   rI   rL   rG   )rB   r?   rT   r5   rf   any)rV   s    r7   check_web_api_keyr  M  s}    !##''	266<<>>DDFFJ```$Z000  b     r9   c                  .    t                      \  } }}| duS )zICheck if an auxiliary text model is available for LLM content processing.Nr   )rr   r   s     r7   rb  rb  X  s    133LFAqr9   __main__u    🌐 Standalone Web Tools Modulez(========================================rM   rN   u   ✅ Web backend: rF   z!   Using Exa API (https://exa.ai)rJ   z+   Using Parallel API (https://parallel.ai)rH   z(   Using Tavily API (https://tavily.com)rI   z    Using SearXNG (search only): rR   rL   z-   Using Brave Search free tier (search only)rG   z2   Using DuckDuckGo via ddgs package (search only)z    Using self-hosted Firecrawl: /z#   Using direct Firecrawl cloud APIz!   Using Firecrawl tool-gateway: z0   Firecrawl backend selected but not configuredu$   ❌ No web search backend configuredzWSet EXA_API_KEY, PARALLEL_API_KEY, TAVILY_API_KEY, FIRECRAWL_API_KEY, FIRECRAWL_API_URLu;   ❌ No auxiliary model available for LLM content processingzVSet OPENROUTER_API_KEY, configure Nous Portal, or set OPENAI_BASE_URL + OPENAI_API_KEYuK   ⚠️  Without an auxiliary model, LLM content processing will be disabledu   ✅ Auxiliary model available: r   u!   🛠️  Web tools ready for use!u+   🧠 LLM content processing available with z&   Default min length for processing: z charsu&   🐛 Debug mode ENABLED - Session ID: z    Debug logs will be saved to: z/web_tools_debug_z.jsonu=   🐛 Debug mode disabled (set WEB_TOOLS_DEBUG=true to enable)z
Basic usage:zI  from web_tools import web_search_tool, web_extract_tool, web_crawl_toolz  import asyncioz  # Search (synchronous)z/  results = web_search_tool('Python tutorials')z$  # Extract and crawl (asynchronous)z  async def main():z?      content = await web_extract_tool(['https://example.com'])zC      crawl_data = await web_crawl_tool('example.com', 'Find docs')z  asyncio.run(main())z
LLM-enhanced usage:zC  # Content automatically processed for pages >5000 chars (default)zA  content = await web_extract_tool(['https://python.org/about/'])z#  # Customize processing parametersz$  crawl_data = await web_crawl_tool(z      'docs.python.org',z      'Find key concepts',z,      model='google/gemini-3-flash-preview',z      min_length=3000z  )z  # Disable LLM processingzY  raw_content = await web_extract_tool(['https://example.com'], use_llm_processing=False)z
Debug mode:z  # Enable debug loggingz  export WEB_TOOLS_DEBUG=truez  # Debug logs capture:z$  # - All tool calls with parametersz  # - Original API responsesz  # - LLM compression metricsz  # - Final processed resultsz3  # Logs saved to: ./logs/web_tools_debug_UUID.jsonuL   
📝 Run 'python test_web_tools_llm.py' to test LLM processing capabilities)registryr$  
web_searcha  Search the web for information. Returns up to 5 results by default with titles, URLs, and descriptions. The query is passed through to the configured backend, so operators such as site:domain, filetype:pdf, intitle:word, -term, and "exact phrase" may work when the backend supports them.objectstringzThe search query to look up on the web. You may include backend-supported operators such as site:example.com, filetype:pdf, intitle:word, -term, or "exact phrase".)typedescriptionintegerz3Maximum number of results to return. Defaults to 5.r   )r  r  minimummaximumdefaultr  )r  
propertiesrequired)r.   r  r  r   u  Extract content from web page URLs. Returns page content in markdown format. Also works with PDF URLs (arxiv papers, documents, etc.) — pass the PDF link directly and it converts to markdown text. Pages under 5000 chars return full markdown; larger pages are LLM-summarized and capped at ~5000 chars per page. Pages over 2M chars are refused. If a URL fails or times out, use the browser tool to access it instead.arrayr  z:List of URLs to extract content from (max 5 URLs per call))r  itemsr  maxItemsr=   c                 t    t          |                     dd          |                     dd                    S )Nr	  rE   r
  r  )r
  )r  r?   argskws     r7   r   r     s1    txx/D/DDHHU\^_L`L`aaa r9   u   🔍r   )r.   toolsetschemahandlercheck_fnrequires_envemojimax_result_size_charsc                     t          t          |                     d          t                    r|                     dg           d d         ng d          S )Nr2  r  markdown)r[  r   r?   listr  s     r7   r   r     sP    /$.txx/?/?$F$FNRaR  BPZ \  \ r9   u   📄)	r.   r  r  r  r  r  is_asyncr  r  r1   )r   FrE   )r  )v__doc__r'  loggingr2   r  r   typingr   r   r   r   r   httpxrK   r   plugins.web.firecrawl.providerr	   r
   r   r   r   r   r   r   r   r   r   r   r   r   r   plugins.web.tavily.providerr   r   r   plugins.web.parallel.providerr   r   plugins.web.exa.providerr   r   __annotations__r   r    r!   r"   r   r#   r$   r%   tools.debug_helpersr&   tools.managed_tool_gatewayr'   r(   _read_nous_access_tokenr)   tools.tool_backend_helpersr*   r+   tools.url_safetyr,   tools.website_policyr-   sys	getLogger__name__r   rw   r4   r8   r  rB   rY   r_   rb   r]   rf   rU   r  rp   $DEFAULT_MIN_LENGTH_FOR_SUMMARIZATIONr|   r   r   r   r)  r   r   r   r   r  r  r[  r~  r  rb  printweb_availabletool_gateway_availabler3   r5   firecrawl_key_availablefirecrawl_url_availablenous_availabledefault_summarizer_modelrD   rstripexitactive
session_idlog_dirtools.registryr  r$  WEB_SEARCH_SCHEMAWEB_EXTRACT_SCHEMAregisterr^   r9   r7   <module>r     s#  ' ' 'R   				 				  ; ; ; ; ; ; ; ; ; ; ; ; ; ;   $######                                   (                 5 4 4 4 4 4
 $( 8C= ' ' '*. (3- . . ."& (3- & & &(,  , , ,!Xc] ! ! !         
 - , , , , ,         
 S R R R R R R R ( ( ( ( ( ( 5 5 5 5 5 5 



		8	$	$
%3 %4 % % % %$    c    @-S - - - -.c . . . .
 
 
 
 
 
3 4    &$    049    L (, $Lc Ld L L L L/ /(3- /5RUX`adXegkloqtltguIuCv / / / /x}    
 
k+<	=	=	=
 :a aa	a a C=	a
 a c]a a a aP s sss C=s 	s
 s s c]s s s slYYY C=Y 	Y
 Y c]Y Y Y Yxc c    Tg% g%3 g%s g%3 g% g% g% g%X #:V% V%
s)V%V% V% C=	V%
 V% 	V% V% V% V%v #:f% f%	f%f% f% 	f%
 C=f% f% 	f% f% f% f%T4    t     z 
E
,---	E(OOO &%''M3355"d929-@"#E#E#K#K#M#MNN"d929-@"#E#E#K#K#M#MNN**,,N<<>> 
,..+'++,,,eE56666
""E?@@@@  E<====	!!E[YRY}b5Q5Q5W5W5Y5Y[[\\\\$$EABBBBEFGGGG$ 	FEiYRY?R5S5S5Y5Y5[5[5b5bcf5g5giijjjj$ 	FE78888# 	FET6P6P6R6RTTUUUUEDEEEE45552--//2 2	
 	
 	

  LKLLLfggg[\\\\J0HJJKKK 	E
-... eV<TVVWWWc7[cccddd } OJv7HJJKKKjjjRXRcjjjkkkkMNNN	E
	E
UVVV	E
	E"III	E
$%%%	E
;<<<	E"III	E
0111	E
   	E
KLLL	E
OPPP	E
!""" k%&&&STTTQRRRb			34444555()))*+++<===%&&&eb			*+++ijjj	E/	E
$%%%	E
)***	E
#$$$	E
0111	E
()))	E
)***	E
)***	E
?@@@	E
YZZZ 0 / / / / / / /  w !  G 
 "T 
 
 I   .  v (+[	 
 H   "  	aa""$$
!	 	 	 	  	\ \""$$
!     r9   