
    `gj                        d Z ddlmZ ddlZddlZddlmZmZmZ ddl	m
Z
  ej                  e      Zd
dZddZ G d d	e
      Zy)u:  Exa web search + content extraction — plugin form.

Subclasses :class:`agent.web_search_provider.WebSearchProvider`. Uses the
official Exa SDK (``exa-py``) which is lazy-loaded via
:func:`tools.lazy_deps.ensure` so that cold-start CLI users don't pay the
SDK import cost when Exa isn't configured.

Config keys this provider responds to::

    web:
      search_backend: "exa"      # explicit per-capability
      extract_backend: "exa"     # explicit per-capability
      backend: "exa"             # shared fallback for both

Env var::

    EXA_API_KEY=...    # https://exa.ai (paid tier; free trial available)

The previous in-tree implementation lived at
``tools.web_tools._exa_search`` / ``_exa_extract``; this file is the
canonical replacement. Behavior is bit-for-bit identical aside from the
ABC method-name change.
    )annotationsN)AnyDictList)WebSearchProviderc                 4   ddl m}  t        | dd      }||S ddlm}  |d      }|st        d      	 ddlm}  |dd	
       ddlm}  ||      }d|j                  d<   || _        |S # t        $ r Y 2t        $ r}t        t        |            d}~ww xY w)zLazy-import and cache an Exa SDK client.

    Cache lives on :mod:`tools.web_tools` (as ``_exa_client``) so unit
    tests that reset that name between cases keep working. Raises
    ``ValueError`` when ``EXA_API_KEY`` is unset.
    r   N_exa_clientget_provider_envEXA_API_KEYzLEXA_API_KEY environment variable not set. Get your API key at https://exa.ai)ensurez
search.exaF)prompt)Exa)api_keyzhermes-agentzx-exa-integration)tools.web_tools	web_toolsgetattragent.web_search_providerr   
ValueErrortools.lazy_depsr   ImportError	Exceptionstrexa_pyr   headersr	   )_wtcachedr   r   _lazy_ensureexcr   clients           K/root/.hermes/venv/lib/python3.12/site-packages/plugins/web/exa/provider.py_get_exa_clientr"   )   s     "S-.F:}-G1
 	

$:\%0 !F*8FNN&'COM   $#c(##$s   A+ +	B6B>BBc                     ddl m}  d| _        y)z?Drop the cached Exa client so tests can re-instantiate cleanly.r   N)r   r   r	   )r   s    r!   _reset_client_for_testsr$   P   s    !CO    c                  f    e Zd ZdZedd       Zedd       ZddZddZddZ	dddZ
ddZdd	Zy
)ExaWebSearchProvideru   Exa search + extract provider.

    Both methods are sync — Exa's SDK is sync-only. The web_extract_tool
    dispatcher wraps sync extracts via ``asyncio.to_thread`` when it
    needs to keep the event loop responsive.
    c                     y)Nexa selfs    r!   namezExaWebSearchProvider.name_       r%   c                     y)Nr   r*   r+   s    r!   display_namez!ExaWebSearchProvider.display_namec   r.   r%   c                0    ddl m} t         |d            S )z=Return True when ``EXA_API_KEY`` is set to a non-empty value.r   r
   r   )r   r   bool)r,   r   s     r!   is_availablez!ExaWebSearchProvider.is_availableg   s    >$]344r%   c                     yNTr*   r+   s    r!   supports_searchz$ExaWebSearchProvider.supports_searchm       r%   c                     yr5   r*   r+   s    r!   supports_extractz%ExaWebSearchProvider.supports_extractp   r7   r%   c                n   	 ddl m}  |       rdddS t        j                  d||       t	               j                  ||ddi	      }g }t        |j                  xs g       D ]]  \  }}|j                  xs g }|j                  |j                  xs d
|j                  xs d
|rdj                  |      nd
|dz   d       _ dd|idS # t        $ r}	dt        |	      dcY d}	~	S d}	~	wt        $ r}	dd|	 dcY d}	~	S d}	~	wt         $ r(}	t        j#                  d|	       dd|	 dcY d}	~	S d}	~	ww xY w)zExecute an Exa search.

        Returns ``{"success": True, "data": {"web": [{...}, ...]}}`` on
        success, ``{"success": False, "error": str}`` on failure (incl.
        missing API key and SDK install errors).
        r   is_interruptedFInterrupted)successerrorzExa search: '%s' (limit=%d)
highlightsT)num_resultscontents     )urltitledescriptionpositionweb)r>   dataNExa SDK not installed: zExa search error: %szExa search failed: )tools.interruptr<   loggerinfor"   search	enumerateresultsr@   appendrF   rG   joinr   r   r   r   warning)
r,   querylimitr<   responseweb_resultsiresultr@   r   s
             r!   rP   zExaWebSearchProvider.searchs   sJ   !	L6#(=AAKK5ueD&(//!&- 0 H K&x'7'7'=2> 		6#..4"
""%zz/R!'!3?Isxx
';r$%E		  $e[-ABB 	9$s3x88 	P$1H/NOO 	LNN137$1DSE/JKK	LsG   C B6C 	D4C'!D4'D43D :D4 D4D/)D4/D4c           
        	 ddl m}  |       r|D cg c]  }|ddd
 c}S t        j                  dt	        |             t               j                  |d      }g }|j                  xs g D ]L  }|j                  xs d}|j                  xs d}	|j                  xs d}
|j                  |	|
|||	|
d	d
       N |S c c}w # t        $ r.}|D cg c]  }|ddt        |      d nc c}w c}cY d}~S d}~wt        $ r(}|D cg c]  }|ddd| d nc c}w c}cY d}~S d}~wt        $ r>}t        j!                  d|       |D cg c]  }|ddd| d nc c}w c}cY d}~S d}~ww xY w)zExtract content from one or more URLs via Exa.

        Returns a list of result dicts shaped for the legacy LLM
        post-processing pipeline. On per-URL or whole-batch failure,
        results carry an ``error`` field rather than raising.
        r   r;   r=   rC   )rF   r?   rG   zExa extract: %d URL(s)T)text)	sourceURLrG   )rF   rG   contentraw_contentmetadata)rF   rG   r_   r?   NrL   zExa extract error: %szExa extract failed: )rM   r<   rN   rO   lenr"   get_contentsrR   r]   rF   rG   rS   r   r   r   r   rU   )r,   urlskwargsr<   urX   rR   r[   r_   rF   rG   r   s               r!   extractzExaWebSearchProvider.extract   s   &	6MQHIAC  KK0#d)<&(55d5FH,.G"**0b  +++jj&B*"!&#*'.25$F	 N+,  	]W[\RSArCHM\\\ 	  B2BYZ]Y^@_`    	NN2C8  B2BVWZV[@\]  	s{   C C C BC  C 	E0C7C*)C71E07E0D&DD& E0&E02E+EE+%E0+E0c                    dddddddgdS )	Nr   paidz5Semantic + neural web search with content extraction.r   zExa API keyzhttps://exa.ai)keyr   rF   )r-   badgetagenv_varsr*   r+   s    r!   get_setup_schemaz%ExaWebSearchProvider.get_setup_schema   s*    J )++	
 	
r%   N)returnr   )ro   r2   )   )rV   r   rW   intro   Dict[str, Any])rd   z	List[str]re   r   ro   zList[Dict[str, Any]])ro   rr   )__name__
__module____qualname____doc__propertyr-   r0   r3   r6   r9   rP   rg   rn   r*   r%   r!   r'   r'   W   sP        5(LT-^
r%   r'   )ro   r   )ro   None)rv   
__future__r   loggingostypingr   r   r   r   r   	getLoggerrs   rN   r"   r$   r'   r*   r%   r!   <module>r~      sJ   0 #  	 " " 7			8	$$NA
, A
r%   