Ë
    ãß²jÿs  ã                   ó  — d Z ddlZddlZddlZddlmZmZmZ dxZxZ	xZ
xZZddlmZmZ ddlmZ ddlmZmZmZ ddlmZ ddlmZmZ dd	lmZmZmZmZ dd
l m!Z!m"Z"m#Z#m$Z$m%Z%m&Z&m'Z'  ejP                  e)«      Z*de+de+fd„Z,de+de-fd„Z.de/fd„Z0dpde+de+fd„Z1de+fd„Z2de+fd„Z3d„ Z4dqde+de+dee-   fd„Z5de+fd„Z6dee+   fd„Z7de+fd„Z8de+fd„Z9de-fd„Z:de-fd„Z;d „ d!„ d"„ d#„ d$„ d%„ d&„ d'„ d(„ e;d)œ
Z< e=e<«      Z>de+de-fd*„Z?de@e+   fd+„ZA ed,d-¬.«      ZBdrd/„ZCdsd0e+d1e/d2ee+   dee+   fd3„ZDdtd5e+d6eEde+fd7„ZFd5e+d6eEde/fd8„ZGdud9ee   d:e+d;eeE   de+fd<„ZHde-fd=„ZI e=d>h«      ZJde-fd?„ZKdd@lLmMZMmNZN dAdBdCdDdEdFœdGdHdIdJd4dKœdLœd5gdMœdNœZOdOdPdCdQdRdDidSd4dTœdGdUdVdWœdXœd9gdMœdNœZP eMj¢                  dAdYeOdZ„ eK eA«       d[d\¬]«        eMj¢                  dOdYePd^„ eK eA«       d_d`d\¬a«	       ddblmRZR ddclmSZS ddlTZTddlUZUddlVZVddlWZWdddedfdgdhdidjdkdldmdnœ
ZXdo„ ZYy)va  Generic web_search / web_extract tools over pluggable backends.

Backend is selected during ``hermes tools`` (``web.backend`` in config.yaml; per
capability via ``web.search_backend`` / ``web.extract_backend``). Every vendor
implementation lives in ``plugins/web/<vendor>/provider.py`` and registers with
``agent.web_search_registry``; this module owns selection, safety gates,
caching, keyless rescue, and the truncate-and-store result pipeline.
Debug: ``WEB_TOOLS_DEBUG=true`` writes ``logs/web_tools_debug_<UUID>.json``.
é    N)ÚListÚAnyÚOptional)Ú_is_tool_gateway_readyÚcheck_firecrawl_api_key)ÚDebugSession)ÚNOUS_MANAGED_PROVIDERÚread_selectionÚselection_exists)Úasync_is_safe_url)Ú_rescue_eligibleÚ_rescue_search)Ú_effective_char_limitÚ_trim_resultsÚ_truncate_resultsÚconvert_base64_images_to_links)Ú_extract_safe_urlsÚ_merge_in_orderÚ_no_provider_errorÚ_resolve_extract_providerÚ_result_entryÚ_strict_selection_errorÚ_validate_extract_urlsÚnameÚreturnc                 óš   — 	 ddl m}  || «      }|€t        j                  | d«      n|xs dj                  «       S # t        $ r d}Y Œ9w xY w)uá  Resolve ``name`` via the config-aware env layer (``hermes config set`` values), then process env.

    Mirrors the SearXNG provider's ``_searxng_url()`` so that values set through Hermes' config/.env layer
    (``hermes config set``, ``hermes tools``) are honored here too â€” not just raw process-env exports.
    Without this, a config-only ``SEARXNG_URL`` (or any provider key) leaves the backend auto-detect cascade
    and ``check_web_api_key()`` blind to it. See #34290.
    r   )Úget_env_valueNÚ )Úhermes_cli.configr   Ú	ExceptionÚosÚgetenvÚstrip)r   r   Úvals      ú4/home/ubuntu/.hermes/hermes-agent/tools/web_tools.pyÚ
_env_valuer&   #   sQ   € ðÝ3Ù˜DÓ!ˆð %( KŒR�Y‰Y�t˜RÔ °SÒ?¸R×FÑFÓHÐHøô ò ØŠðús   ‚< ¼A
Á	A
c                 ó*   — t        t        | «      «      S ©N)Úboolr&   )r   s    r%   Ú_has_envr*   3   s   € Ü”
˜4Ó Ó!Ð!ó    c                  óf   — 	 ddl m}   | «       j                  d«      xs i S # t        $ r i cY S w xY w)zYLoad the ``web:`` section from config.yaml; always a dict (a null section yields ``{}``).r   ©Úload_configÚweb)r   r.   Úgetr    r-   s    r%   Ú_load_web_configr1   7   s6   € ðÝ1Ù‹}× Ñ  Ó'Ò-¨2Ð-øÜò ØŠ	ðús   ‚" ¢0¯0ÚbackendÚkeyc                 ót   — t        «       j                  | «      xs dj                  «       j                  «       S )z?Lower-cased, stripped ``web.<key>`` value ("" when unset/null).r   )r1   r0   Úlowerr#   )r3   s    r%   Ú_configured_backendr6   @   s-   € äÓ×"Ñ" 3Ó'Ò-¨2×4Ñ4Ó6×<Ñ<Ó>Ð>r+   Ú	func_namec                 óŽ   — 	 ddl m}  t        || «      |Ž S # t        $ r$}t        j                  d| ||«       |cY d}~S d}~ww xY w)zc``agent.web_search_registry.<func_name>(*args)``, or *default* if it raised (registry never fatal).r   Nz%web provider registry %s%r failed: %s)Úagent.web_search_registryÚweb_search_registryÚgetattrr    ÚloggerÚdebug)r7   ÚdefaultÚargsÚregistry_modÚexcs        r%   Ú_registry_callrB   E   sF   € ðÝ8Ø/Œw�| YÓ/°Ð6Ð6øÜò Ü�‰Ð<¸iÈÈsÔSØ�ûðús   ‚ —	A ?¹A¿Ac                 ó$   — | rt        dd| «      S dS )z4Plugin-registered web provider by name, or ``None``.Úget_providerN©rB   ©r2   s    r%   Ú_registered_web_providerrG   O   s   € á<CŒ>˜.¨$°Ó8ÐMÈÐMr+   c                  ó   — t        dg «      S )z<All plugin-registered web providers (empty list on failure).Úlist_providersrE   © r+   r%   Ú_list_registered_web_providersrK   T   s   € äÐ*¨BÓ/Ð/r+   ÚmethodÚcontextc                 ó°   — 	 t         t        | |«      «       «      S # t        $ r0}t        | d| «      }t        j	                  d||||«       Y d}~yd}~ww xY w)z¬``bool(provider.<method>())``, or ``None`` if it raised (a broken provider is unavailable; *context* is
    appended to the debug log line, e.g. " during readiness check").r   z!web provider %r.%s() raised%s: %sN)r)   r;   r    r<   r=   )ÚproviderrL   rM   rA   r   s        r%   Ú_proberP   Y   sV   € ðÜÐ-”G˜H fÓ-Ó/Ó0Ð0øÜò Ü�x ¨Ó2ˆÜ�‰Ð8¸$ÀÈÐQTÔUÜûðús   ‚ œ	A¥&AÁAc                  ó
  — t        «       } | r| t        k(  rdS | S t        d«      �ydt        d«      fdt        d«      fdt        d«      fd	t        d
«      fdt        d«      fdt        d«      xs t        d«      fdt	        «       fdt        d«      fdt        d«      fdt        «       ff
}|D ]  \  }}|sŒ	|c S  t        «       D ]/  }|j                  t        vsŒt        |d«      sŒ#|j                  c S  t        «       xs dS )uö  Shared web backend name. A stored ``web.backend`` is returned as-is â€” no availability probe, no
    fallback â€” so a broken selection surfaces the vendor's honest error rather than silently rerouting.
    The managed ``use_gateway`` selection also resolves to firecrawl with no ladder. Autodetect runs
    whenever no SHARED web selection was ever stored: per-capability keys (``web.search_backend``,
    ``web.extract_backend``) name only their own capability and never reroute the other (#113017).Ú	firecrawlr/   ÚtavilyÚTAVILY_API_KEYÚ
perplexityÚPERPLEXITY_API_KEYÚexaÚEXA_API_KEYÚparallelÚPARALLEL_API_KEYÚkeenableÚKEENABLE_API_KEYÚFIRECRAWL_API_KEYÚFIRECRAWL_API_URLÚsearxngÚSEARXNG_URLú
brave-freeÚBRAVE_SEARCH_API_KEYÚddgsÚis_available)r6   r	   r
   r*   r   Ú_ddgs_package_importablerK   r   Ú_LEGACY_WEB_BACKENDSrP   Ú_keyless_backend)Ú
configuredÚbackend_candidatesr2   Ú	availablerO   s        r%   Ú_get_backendrk   d   s)  € ô %Ó&€JÙà(Ô,AÒAˆ{ÐQÀzÐQÜ�eÓÐ(àð 
”8Ð,Ó-Ð.°¼xÐH\Ó?]Ð0^Ø	”˜Ó'Ð(Ø	”XÐ0Ó1Ð2°ZÄÐJ\ÓA]Ð4^Ø	”hÐ2Ó3ÒT´xÐ@SÓ7TÐUØ	Ô,Ó.Ð/°)¼XÀmÓ=TÐ1UØ	”xÐ 6Ó7Ð8¸6ÔC[ÓC]Ð:^ðÐð 1ò Ñˆ�ÚØŠNðô
 3Ó4ò !ˆØ�=‰=Ô 4Ò4¼ÀÈ.Õ9YØ—=‘=Ò ð!ô ÓÒ, Ð,r+   c                  óä   — 	 t        «        ddlm} m}  |«       r* | «       D ]   }t	        |«      }|€Œt        |d«      sŒ|c S  y# t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)zíKeyless free-tier backend name, or None. Strictly the last autodetect rung so it never
    pre-empts a keyed backend. Discovery must run first: reachable from contexts that haven't
    loaded plugins (subprocess runs, delegate children).r   )Ú_keyless_preferenceÚ_keyless_tier_enabledNÚis_keyless_availablez keyless fallback walk failed: %s)	Ú_ensure_web_plugins_loadedr9   rm   rn   rG   rP   r    r<   r=   )rm   rn   r   rO   rA   s        r%   rg   rg   ‰   ss   € ð	>Ü"Ô$ßXÙ Ô"Ù+Ó-ò  �Ü3°DÓ9�ØÑ'¬F°8Ð=SÕ,TØ’Kð ð øô ò >Ü�‰Ð7¸×=Ð=Øûð>ús(   ‚0A ³A Á A ÁA Á	A/ÁA*Á*A/c                  ó0   — t        d«      xs
 t        «       S )zaBackend for web_search: ``web.search_backend`` (strict, no probe) > ``web.backend`` > autodetect.Úsearch_backend©r6   rk   rJ   r+   r%   Ú_get_search_backendrt   š   s   € äÐ/Ó0ÒB´L³NÐBr+   c                  ó0   — t        d«      xs
 t        «       S )zcBackend for web_extract: ``web.extract_backend`` (strict, no probe) > ``web.backend`` > autodetect.Úextract_backendrs   rJ   r+   r%   Ú_get_extract_backendrw   Ÿ   s   € äÐ0Ó1ÒC´\³^ÐCr+   c                  ó,   — 	 ddl } y# t        $ r Y yw xY w)zXddgs is the only backend gated on package presence; single symbol so tests can patch it.r   NTF)rc   ÚImportError)rc   s    r%   re   re   ¤   s   € ðÛØøÜò Ùðús   ‚ ‡	’c                  ó<   — 	 ddl m}   | «       S # t        $ r Y yw xY w)Nr   ©Úhas_xai_credentialsF)Útools.xai_httpr|   r    r{   s    r%   Ú_xai_availabler~   ­   s%   € ðÝ6Ù"Ó$Ð$øÜò Ùðús   ‚ �	šc                  ó   — t        d«      S )NrX   ©r*   rJ   r+   r%   ú<lambda>r�   »   s   € ”8˜MÓ*€ r+   c                  ó   — t        d«      S )NrZ   r€   rJ   r+   r%   r�   r�   ¼   ó   € œÐ!3Ó4€ r+   c                  ó   — t        d«      S )Nr\   r€   rJ   r+   r%   r�   r�   ½   rƒ   r+   c                  ó   — t        «       S r(   )r   rJ   r+   r%   r�   r�   ¾   s
   € Ô0Ó2€ r+   c                  ó@   — t        d«      xs t        d„ dD «       «      S )NrT   c              3   ó8   K  — | ]  }t        |«      d k(  –— Œ y­w)rS   N)r6   )Ú.0Úks     r%   ú	<genexpr>z<lambda>.<locals>.<genexpr>À   s   è ø€ Ò
h°!Ô˜qÓ! XÕ-Ñ
hùs   ‚)r2   rr   rv   )r*   ÚanyrJ   r+   r%   r�   r�   ¿   s$   € ”hÐ/Ó0ò iÜ
Ñ
hÐ7gÔ
hÓhð r+   c                  ó   — t        d«      S )NrV   r€   rJ   r+   r%   r�   r�   Á   s   € œ(Ð#7Ó8€ r+   c                  ó   — t        d«      S )Nr`   r€   rJ   r+   r%   r�   r�   Â   s   € ”x Ó.€ r+   c                  ó   — t        d«      S )Nrb   r€   rJ   r+   r%   r�   r�   Ã   s   € œ(Ð#9Ó:€ r+   c                  ó   — t        «       S r(   )re   rJ   r+   r%   r�   r�   Ä   s
   € Ô,Ó.€ r+   )
rW   rY   r[   rR   rS   rU   r_   ra   rc   Úxaic                 óÔ   — | xs dj                  «       j                  «       } | t        v rdn
t        | «      }|�t	        |d«      xs dS t
        j                  | «      }|r |«       S dS )uÀ  True when *backend* is usable â€” the single availability chokepoint. Non-legacy names delegate to the
    registered provider's ``is_available()`` (unregistered names fall through); built-ins use cheap probes.

    For plugin-registered backends (any name outside :data:`_LEGACY_WEB_BACKENDS`), availability is
    delegated to the provider's ``is_available()`` via the web_search_registry. This is the single
    chokepoint through which ``_get_backend``, ``_get_capability_backend``, and ``check_web_api_key`` all
    resolve availability â€” fixing custom-provider discovery for every caller at once (issues #28651, #31873,
    #32698). Built-in backends keep their cheap hardcoded probes below.
    r   Nrd   F)r5   r#   rf   rG   rP   Ú_BUILTIN_AVAILABILITYr0   )r2   rO   Úprobes      r%   Ú_is_backend_availabler”   Ê   sl   € ð Š}˜"×#Ñ#Ó%×+Ñ+Ó-€GØÔ"6Ñ6‰tÔ<TÐU\Ó<]€HØÐÜ�h Ó/Ò8°5Ð8Ü!×%Ñ% gÓ.€EÙ‰5‹7Ð& Ð&r+   c                  ó
   — g d¢S )a  Tool-registry metadata env vars for the web backends. Gateway vars are always listed: gating them
    on ``managed_nous_tools_enabled()`` cost a synchronous portal HTTP refresh at every CLI startup.
    Contract: set var -> tool sees it; extras are harmless for the not-logged-in.)rX   rZ   rT   rV   r\   r]   r^   ÚFIRECRAWL_GATEWAY_URLÚTOOL_GATEWAY_DOMAINÚTOOL_GATEWAY_SCHEMEÚTOOL_GATEWAY_USER_TOKENrJ   rJ   r+   r%   Ú_web_requires_envrš   ß   s   € òð r+   Ú	web_toolsÚWEB_TOOLS_DEBUG)Úenv_varc                  óx   — 	 ddl m}   | «        y# t        $ r }t        j	                  d|«       Y d}~yd}~ww xY w)u]  Idempotently run plugin discovery so the web registry is populated. Dispatch is reachable from contexts
    that never triggered discovery (subprocess agent runs, delegate children, scripts); without it a
    configured backend yields a misleading "No web ... provider" error.

    Every bundled web provider (brave-free, ddgs, searxng, exa, parallel, tavily, firecrawl, keenable)
    registers itself via ``plugins/web/<vendor>/__init__.py`` during plugin discovery. Tool dispatch can be
    reached from contexts that haven't already triggered discovery â€” subprocess agent runs, delegate
    children, standalone scripts, certain test paths â€” and without it the registry is empty and
    ``get_provider('firecrawl')`` returns ``None`` even when the user has ``web.extract_backend: firecrawl``
    configured and ``FIRECRAWL_API_KEY`` set. See #27580.
    r   )Ú_ensure_plugins_discoveredz+Web plugin discovery failed (non-fatal): %sN)Úhermes_cli.pluginsrŸ   r    r<   Úwarning)rŸ   rA   s     r%   rp   rp   ò   s5   € ðKÝAÙ"Õ$øÜò Kä�‰ÐDÀc×JÑJûðKús   ‚ �	9™4´9Ú	call_nameÚdebug_call_dataÚ	error_msgc                 ó®   — |�t         j                  d|«       ||d<   t        j                  | |«       t        j	                  «        |€dS t        |«      S )zhLog the call into the debug session; with *error_msg*, record it and return its ``tool_error`` envelope.Nz%sÚerror)r<   r=   Ú_debugÚlog_callÚsaveÚ
tool_error)r¢   r£   r¤   s      r%   Ú_finish_debugr«     sL   € àÐÜ�‰�T˜9Ô%Ø#,ˆ˜Ñ Ü
‡O�O�I˜Ô/Ü
‡K�K„MØÐ$ˆ4Ð?¬*°YÓ*?Ð?r+   é   ÚqueryÚlimitc           	      óv  — 	 t        t        t        |«      d«      d«      }| |dœdddddœ}	 ddlm}  |«       rt        d	d
¬«      S t        «        ddl	m
}m} t        «       }|r ||«      nd}|�|j                  «       sN|€E|rCt        d«      r8t        d|«      x}|d<   t!        d|«       t#        j$                  d
|dœdd
¬«      S  |«       }|€d}	d
t'        d|	«      dœ}
n/t(        j+                  d|j,                  | |«       t/        || |«      }
t1        |
j3                  di «      j3                  dg «      «      |d<   t#        j$                  |
dd
¬«      }t1        |«      |d<   t!        d|«       |S # t        t        f$ r d}Y �Œgw xY w# t4        $ r#}t!        d|dt7        |«      › �«      cY d}~S d}~ww xY w)u  Search the web via the configured backend.

    Returns a JSON string ``{"success": bool, "data": {"web": [{"title", "url", "description", "position"},
    ...]}}`` (metadata only â€” use web_extract_tool for page content) or ``{"success": false, "error": ...}``.
    é   éd   r¬   ©r­   r®   Nr   )Ú
parametersr¦   Úresults_countÚoriginal_response_sizeÚfinal_response_size)Úis_interruptedÚInterruptedF)Úsuccess)Úget_active_search_providerrD   r/   Úsearchr¦   Úweb_search_tool)r¹   r¦   é   ©ÚindentÚensure_asciizDNo web search provider configured. Run `hermes tools` to set one up.z#Web search via %s: '%s' (limit: %d)Údatar´   r¶   zError searching web: )ÚminÚmaxÚintÚ	TypeErrorÚ
ValueErrorÚtools.interruptr·   rª   rp   r9   rº   rD   rt   Úsupports_searchr   r   r«   ÚjsonÚdumpsr   r<   Úinfor   Ú_memoized_searchÚlenr0   r    Ústr)r­   r®   r£   r·   rº   Ú_wsp_get_providerr2   rO   Ú
error_textÚfallbackÚresponse_dataÚresult_jsonÚes                r%   r¼   r¼     sÍ  € ðÜ”Cœ˜E›
 AÓ&¨Ó,ˆð !&°Ñ6ÀÐXYØ"#¸Añ€Oð
cÝ2ÙÔÜ˜m°UÔ;Ð;ä"Ô$ßkÜ%Ó'ˆÙ18Ñ$ WÔ-¸dˆØÐ 8×#;Ñ#;Ô#=ØÐ¡GÔ0@ÀÔ0GÜ8OÐPXÐZaÓ8bÐb�
˜_¨WÑ5ÜÐ/°ÔAÜ—z‘z¨e¸jÑ"IÐRSÐbgÔhÐhá1Ó3ˆHàÐØ]ˆHØ(-Ô8JÈ8ÐU]Ó8^Ñ_‰Mä�K‰KÐ=¸x¿}¹}ÈeÐUZÔ[Ü,¨X°u¸eÓDˆMä+.¨}×/@Ñ/@ÀÈÓ/L×/PÑ/PÐQVÐXZÓ/[Ó+\ˆ˜Ñ(Ü—j‘j °qÀuÔMˆÜ14°[Ó1AˆÐ-Ñ.ÜÐ'¨Ô9ØÐøôG ”zÐ"ò Ø‹ðûôH ò cÜÐ.°ÐCXÔY\Ð]^ÓY_ÐX`ÐAaÓbÕbûðcús<   ‚E4 ­F ÁB F ÃB+F Å4F	ÆF	Æ	F8ÆF3Æ-F8Æ3F8c                 óœ  ‡ ‡‡‡— ddl mŠm}m} dt        t
        t        f   fˆˆˆ ˆfd„}|j                  ‰ j                  ‰‰«      }|€o|j                  ‰ j                  ‰‰«      5  |j                  ‰ j                  ‰‰«      }|€* |«       \  }}|s|j                  ‰ j                  ‰‰|«       ddd«        ||‰«      S # 1 sw Y   ŒxY w)u€  TTL memo + single-flight around the paid vendor call (tools/web_result_cache.py); sits after every
    safety/config check. The provider is asked for the BUCKETED count so near-identical limits share an entry;
    the caller's count is sliced out. Only successful, non-rescued responses are cached â€” caching a rescue
    would make the one-shot ring fallback sticky for a whole TTL.r   )Úbucket_limitÚsearch_memoÚslice_search_responser   c            	      ój  •—  ‰‰«      } 	 ‰j                  ‰| «      }|j                  d«      s>t        ‰«      r3t        ‰j                  t        |j                  dd«      «      ‰| «      dfS |dfS # t        $ r9}t        ‰«      s‚ t        ‰j                  t        |«      ‰| «      dfcY d }~S d }~ww xY w)NTr¹   r¦   r   F)r»   r    r   r   r   rÎ   r0   )Úfetch_limitÚresprA   rÖ   r®   rO   r­   s      €€€€r%   Ú_paid_searchz&_memoized_search.<locals>._paid_searchG  s­   ø€ Ù" 5Ó)ˆð	UØ—?‘? 5¨+Ó6ˆDð
 �x‰x˜	Ô"Ô'7¸Ô'AÜ! (§-¡-´°T·X±X¸gÀrÓ5JÓ1KÈUÐT_Ó`ÐbfÐfÐfØ�Uˆ{Ðøô ò 	UÜ# HÔ-ØÜ! (§-¡-´°S³¸5À+ÓNÐPTÐTÕTûð	Uús   ‹A0 Á0	B2Á9.B-Â'B2Â-B2N)Útools.web_result_cacherÖ   r×   rØ   ÚtupleÚdictr)   Úlookupr   Úflight_lockÚstore)	rO   r­   r®   r×   rØ   rÜ   rÒ   Úwas_rescuedrÖ   s	   ```     @r%   rÌ   rÌ   @  sÉ   û€ ÷
 XÑWð
œ%¤¤d 
Ñ+÷ 
ð 
ð  ×&Ñ& x§}¡}°e¸UÓC€MØÐØ×$Ñ$ X§]¡]°E¸5ÓAñ 	Rà'×.Ñ.¨x¯}©}¸eÀUÓKˆMØÐ$Ù-9«^Ñ*�˜{Ù"Ø×%Ñ% h§m¡m°U¸EÀ=ÔQ÷	Rñ ! °Ó6Ð6÷	Rð 	Rús   Á'A
CÃCÚurlsÚformatÚ
char_limitc           	   ƒ   óH  K  — t        | «      \  }}}}|�|S |||dœdddddg g dœ}	 t        j                  dt        |«      «       g g i }
}	}t	        ||«      D ]J  \  }}t        |«      ƒ d{  –—† r#|j                  |«       |	j                  |«       Œ<t        |d«      |
|<   ŒL g }|r;t        «       }t        «        t        |«      \  }}|�|S t        |||«      ƒ d{  –—† }|s|
ri |
¥|¥}t        t        | «      ||	||«      }t        j                  dt        |«      «       t        |«      |d<   t        t        j                  d	|i«      «      |d
<   |d   j                  d«       t        |t!        |«      |«       t#        |«      }|rt        j                  d	|idd¬«      n
t%        d«      }t'        |«      }t        |«      |d<   |d   j                  d«       t)        d|«       |S 7 �Œ�7 �Œ# t*        $ r#}t)        d|dt-        |«      › �«      cY d}~S d}~ww xY w­w)aš  Extract clean page content (no LLM) from URLs via the configured backend.

    Pages over ``char_limit`` (default web.extract_char_limit or 15000) are head+tail truncated with a footer
    pointing at the stored full text; inline base64 images become ``[IMAGE: alt]``. URLs carrying secrets are
    refused before any fetch; private-network URLs are blocked per entry. Returns JSON ``{"results": [...]}``.
    N)rä   rå   ræ   r   )r³   r¦   Úpages_extractedÚpages_truncatedrµ   r¶   Útruncation_metricsÚprocessing_appliedz!Extracting content from %d URL(s)z:Blocked: URL targets a private or internal network addresszExtracted content from %d pagesrè   Úresultsrµ   rë   Útruncate_and_storer½   Fr¾   z%Content was inaccessible or not foundr¶   Úbase64_image_conversionÚweb_extract_toolzError extracting content: )r   r<   rË   rÍ   Úzipr   Úappendr   rw   rp   r   r   r   rÉ   rÊ   r   r   r   rª   r   r«   r    rÎ   )rä   rå   ræ   Únormalized_urlsÚnormalized_indicesÚinvalid_urlsÚblockedr£   Ú	safe_urlsÚsafe_indicesÚssrf_blockedÚindexÚurlrì   r2   rO   Ú
error_jsonÚfixedÚtrimmedrÓ   Úcleaned_resultrÔ   s                         r%   rï   rï   _  sX  è ø€ ô BXÐX\ÓA]Ñ>€OÐ'¨°wØÐØˆà.¸&ÐPZÑ[ÐfjØ°ÈaÐhiØ ¸ñ€Oð,iÜ�‰Ð7¼¸_Ó9MÔNà02°B¸ �<ˆ	ÜÐ0°/ÓBò 	‰JˆE�3Ü& sÓ+×+Ð+Ø× Ñ  Ô%Ø×#Ñ# EÕ*ä&3ØÐUó'�˜UÒ#ð	ð ˆÙÜ*Ó,ˆGÜ&Ô(Ü#<¸WÓ#EÑ ˆH�jØÐ%Ø!Ð!Ü.¨x¸ÀFÓK×KˆGñ ™<Ø4�|Ð4 |Ð4ˆEÜ%¤c¨$£i°¸ÀiÐQXÓYˆGä�‰Ð5´s¸7³|ÔDÜ-0°«\ˆÐ)Ñ*Ü47¼¿
¹
ÀIÈwÐCWÓ8XÓ4YˆÐ0Ñ1ØÐ,Ñ-×4Ñ4Ð5IÔJÜ˜'Ô#8¸Ó#DÀoÔVÜ Ó(ˆáNUŒD�J‰J˜	 7Ð+°AÀEÕJÜÐCÓDð 	ô
 8¸ÓDˆÜ14°^Ó1DˆÐ-Ñ.ØÐ,Ñ-×4Ñ4Ð5NÔOÜÐ(¨/Ô:ØÐðK ,ùð Lúô. ò iÜÐ/°ÐD^Ô_bÐcdÓ_eÐ^fÐBgÓhÕhûðiüsg   ‚$H"§AG3 Á,G-Á-A!G3 ÃH"ÃG3 ÃG0Ã DG3 Ç,H"Ç-G3 Ç0G3 Ç3	HÇ<HÈHÈH"ÈHÈH"c                 ó\   — | €yt        | dd«      }|€yt        |xs t        | dd«      «      S )a˜  True when *provider* is keyed-available OR keyless-capable, without raising.

    ``get_active_*_provider()`` returns an explicitly configured backend even when ``is_available()`` is
    False (so dispatch can emit a precise error), so readiness gates (tool check_fn, ``hermes doctor``)
    must probe for real. Keyless mode (Exa/Parallel free tier) is a working state, not a misconfig.

    See #78412.
    Frd   z during readiness checkro   )rP   r)   )rO   Úreadys     r%   Ú_provider_is_readyr  ž  s?   € ð ÐØÜ�8˜^Ð-FÓG€EØ€}ØÜ�Ò\œ Ð*@ÐB[Ó\Ó]Ð]r+   r�   c                  ó´  — t        «       fD � cg c]  } | sŒ| ‘Œ	 c} t        D �cg c]  }|t        vsŒ|‘Œ c}z   }t        d„ |D «       «      ry	 t	        «        ddlm}m}  |«        |«       fD ]1  }|� t        |dd«      t        v rt        «       � yŒ%t        |«      sŒ1 y yc c} w c c}w # t        $ r }t        j                  d|«       Y d}~yd}~ww xY w)	a#  ``check_fn`` gate for web_search / web_extract: is any web backend available?

    A plugin-registered provider reporting ``is_available()`` must light the tools up even with no
    built-in credentials; resolution funnels through :func:`_is_backend_available`.

    See #28651, #31873.
    c              3   ó2   K  — | ]  }t        |«      –— Œ y ­wr(   )r”   )rˆ   r2   s     r%   rŠ   z$check_web_api_key.<locals>.<genexpr>Á  s   è ø€ Ò
D¨gÔ  ×)Ñ
Dùs   ‚Tr   )rº   Úget_active_extract_providerNr   Fz3web provider registry availability check failed: %s)r6   rf   Ú_WEB_CHECK_SKIPr‹   rp   r9   rº   r  r;   rg   r  r    r<   r=   )ÚcÚbÚ
candidatesrº   r  rO   rA   s          r%   Úcheck_web_api_keyr	  ¶  sÔ   € ô  3Ó4Ð6Ö<˜º!’1Ò<Ü 4ÖQ˜1¸ÄÒ8P’QÒQñR€Jä
Ñ
D¸Ô
DÔDØðÜ"Ô$ßeÙ3Ó5Ñ7RÓ7TÐUò 		ˆHØÐ#¬°¸&À$Ó(GÌ?Ñ(Zô $Ó%Ð1ÙØÜ! (Õ+Ùð		ð ùò% =ùÚQøô$ ò Ü�‰ÐJÈCÔPÜûðús?   ŽB$–B$¤B)²B)ÁAB. ÂB. Â B. Â"B. Â.	CÂ7CÃC)Ú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Ústringz£The 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  r  ÚminimumÚmaximumr>   r²   )r  Ú
propertiesÚrequired)r   r  r³   Úweb_extractu+  Extract content from web page URLs. Returns clean page content in markdown/text (no LLM summarization â€” fast). Also works with PDF URLs (arxiv papers, documents) â€” pass the PDF link directly. Pages within the char budget (default 15000) return whole; larger pages return a head+tail window with a footer telling you the full text's saved file path and the read_file call to page through the omitted middle. Inline images appear as [IMAGE: alt] placeholders; real image URLs are kept as links. If a URL fails or times out, use the browser tool instead.Úarrayr  z:List of URLs to extract content from (max 5 URLs per call))r  Úitemsr  ÚmaxItemszÂOptional per-page character budget sent back (default 15000). Pages larger than this are head+tail truncated with the full text stored to disk. Raise it when you need more of a long page inline.iÐ  )r  r  r  )rä   ræ   r/   c                 ó\   — t        | j                  dd«      | j                  dd«      ¬«      S )Nr­   r   r®   r¬   )r®   )r¼   r0   ©r?   Úkws     r%   r�   r�     s%   € œ¨t¯x©x¸ÀÓ/DÈDÏHÉHÐU\Ð^_ÓL`Ôa€ r+   u   ðŸ”�i † )r   ÚtoolsetÚschemaÚhandlerÚcheck_fnÚrequires_envÚemojiÚmax_result_size_charsc                 ó¤   — t        t        | j                  d«      t        «      r| j                  dg «      d d ng d| j                  d«      ¬«      S )Nrä   r¬   Úmarkdownræ   )ræ   )rï   Ú
isinstancer0   Úlistr  s     r%   r�   r�     sD   € Ô/Ü$.¨t¯x©x¸Ó/?ÄÔ$Fˆ�‰�˜Ó˜R˜aÑ ÈBÐPZØ—8‘8˜LÓ)ô € r+   Tu   ðŸ“„)	r   r  r  r  r  r   Úis_asyncr!  r"  )ÚDict)ÚTYPE_CHECKING)útools.web_tools_truncateÚDEFAULT_EXTRACT_CHAR_LIMIT)zplugins.web.firecrawl.providerÚ	Firecrawl)r*  ÚMAX_STORED_TEXT_CHARS)útools.managed_tool_gatewayÚbuild_vendor_gateway_url)útools.tool_backend_helpersÚmanaged_nous_tools_enabled)útools.url_safetyÚnormalize_url_for_request)r0  Ú%nous_tool_gateway_unavailable_message)r0  Úprefers_gateway)r.  Úresolve_managed_tool_gateway)r2  Úsensitive_query_param_name)
r+  r,  r-  r/  r1  r3  r4  r5  r6  r7  c                 óÌ   — t         j                  | «      }|€t        dt        ›d| ›�«      ‚dd l}ddlm}  |t        | g|¢­Ž  t        |j                  |d   «      |d   «      S )Nzmodule z has no attribute r   )Ú	warn_oncer°   )	Ú_PLUGIN_COMPAT_LAZYr0   ÚAttributeErrorÚ__name__Ú	importlibÚhermes_cli.plugin_compatr9  r;   Úimport_module)r   Útargetr=  r9  s       r%   Ú__getattr__rA  1  se   € Ü ×$Ñ$ TÓ*€FØ€~Ü˜w¤x lÐ2DÀTÀHÐMÓNÐNÛÝ2ÙŒh˜Ð&˜vÓ&Ü�9×*Ñ*¨6°!©9Ó5°v¸a±yÓAÐAr+   rF   )r   )r   Nr(   )r¬   )NN)ZÚ__doc__rÉ   Úloggingr!   Útypingr   r   r   Ú_firecrawl_clientÚ_firecrawl_client_configÚ_parallel_clientÚ_async_parallel_clientÚ_exa_clientÚplugins.web.firecrawl.providerr   r   Útools.debug_helpersr   Útools.tool_backend_helpersr	   r
   r   Útools.url_safetyr   Útools.web_tools_rescuer   r   Útools.web_tools_truncater   r   r   r   Útools.web_tools_extractr   r   r   r   r   r   r   Ú	getLoggerr<  r<   rÎ   r&   r)   r*   rß   r1   r6   rB   rG   rK   rP   rk   rg   rt   rw   re   r~   r’   Ú	frozensetrf   r”   r&  rš   r§   rp   r«   rÄ   r¼   rÌ   rï   r  r  r	  Útools.registryr
  rª   ÚWEB_SEARCH_SCHEMAÚWEB_EXTRACT_SCHEMAÚregisterr(  r)  ÚasyncioÚhttpxÚreÚsysr:  rA  rJ   r+   r%   ú<module>r[     sÃ  ðñó Û Û 	ß &Ñ &àimÐ mÐ Ð mÐ,Ð mÐ/?Ð mÐBXÐ[fç ZÝ ,ß ^Ñ ^Ý .ß Cß |Ó |÷÷ ñ ð
 
ˆ×	Ñ	˜8Ó	$€ð
I�Sð I˜Só Ið "�3ð "˜4ó "ð˜$ó ñ?˜Sð ?°ó ?ð
˜có ðN có Nò
0ñ
˜Sð ¨3ð ¸À¹ó ð"-�có "-ðJ˜( 3™-ó ð"C˜Só Cð
D˜có Dð
 $ó ð˜ó ñ +Ù4Ù4Ù2ñiá8Ù.Ù:Ù.ØñÐ ñ !Ð!6Ó7Ð ð' 3ð '¨4ó 'ð*˜4 ™9ó ñ 
�kÐ+<Ô	=€óKñ(@˜Sð @°4ð @ÀHÈSÁMð @Ð]eÐfiÑ]jó @ñ-c˜3ð -c sð -c°3ó -cð`7 cð 7°#ð 7¸$ó 7ñ><i  c¡ð <i°Cð <iÈHÐUXÉMð <iÐehó <ið~^ Dó ^ñ* ˜U˜GÓ$€ð˜4ó ÷D 0ð ð wàð !ð  Gñð
 "ØTØØØññ
ð �IññÐ ð. ð A	àð  Ø  (Ð+Ø[Øñ	ð "ð  dØññ
ð �HññÐ ð, €× Ñ Ø	˜uÐ->ÙaØÑ->Ó-@ÈØ!õ	ð €× Ñ Ø	 Ð.@ñð Ñ->Ó-@È4ÐW]Ø!õõ Ý  Û Û Û 	Û 
ð #]Ø@ØRØ ZØ"^Ø!RØ-tØHØ$bØ"TñÐ óBr+   