+
    lHàj˜t  ã                   óÂ  € R t ^ RIt^ RIt^ RIt^ RIHtHtHt R;t;t	;t
;tt^ RIHtHt ^ RIHt ^ RIHtHtHt ^ RIHt ^ RIHtHtHt ^ RIHtHtHtH t  ^ R	I!H"t"H#t#H$t$H%t%H&t&H't'H(t( ]PR                  ! ]*4      t+R
 R lt,R R lt-R R lt.RwR R llt/R R lt0R R lt1R t2RxR R llt3R R lt4R R lt5R R lt6R R  lt7R! R" lt8R# R$ lt9R% R& lt:R' R( lt;R)R* R+R, R-R. R/R0 R1R2 R3R4 R5R6 R7R8 R9R: R;];/
t<]=! ]<4      t>R< R= lt?R> R? lt@]! R@RARB7      tARC RD ltBRyRE RF lltCRzRG RH lltDRI RJ ltER{RK RL lltFRM RN ltG]=! R;04      tHRO RP ltI^ RQIJHKtKHLtL RRRSRTRURVRWRXRYRZRWR[RTR\/R]RWR^RTR_R`^Ra^dRb^//RcRZ.//tMRRRdRTReRVRWRXRYRfRWRgRhRWR[/RTRiRj^/RkRWR^RTRlR`Rm//RcRf.//tN]KPž                  ! RSRn]MRo ]I]@! 4       RpRqRr7       ]KPž                  ! RdRn]NRs ]I]@! 4       RtRuRqRv7	       R# )|a  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)Ú_managed_search_fallbackÚ_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_urlsc                ó0   € V ^8„  d   QhR\         R\         /# ©é   ÚnameÚreturn©Ústr)Úformats   "Ú4/home/ubuntu/.hermes/hermes-agent/tools/web_tools.pyÚ__annotate__r#   #   s   € ÷ Iñ I”Sð IœSñ Ió    c                ó°   €  ^ RI Hp V! V 4      pVf   \        P                  ! V R4      MT;'       g    RP                  4       #   \         d    Rp LDi ; i)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.
)Úget_env_valueNÚ )Úhermes_cli.configr&   Ú	ExceptionÚosÚgetenvÚstrip)r   r&   Úvals   &  r"   Ú
_env_valuer.   #   sT   € ðÝ3Ù˜DÓ!ˆð %(¢KŒR�YŠY�t˜RÔ °S×?Ð?¸R×FÑFÓHÐHøô ô ØŠðús   ‚A ÁAÁAc                ó0   € V ^8„  d   QhR\         R\        /# r   ©r    Úbool)r!   s   "r"   r#   r#   3   s   € ÷ "ñ "”3ð "œ4ñ "r$   c                 ó*   € \        \        V 4      4      # ©N)r1   r.   )r   s   &r"   Ú_has_envr4   3   s   € Ü”
˜4Ó Ó!Ð!r$   c                ó$   € V ^8„  d   QhR\         /# ©r   r   )Údict)r!   s   "r"   r#   r#   7   s   € ÷ ñ œ$ñ r$   c                 óv   €  ^ RI Hp  V ! 4       P                  R4      ;'       g    / #   \         d    / u # i ; i)zYLoad the ``web:`` section from config.yaml; always a dict (a null section yields ``{}``).©Úload_configÚweb)r(   r:   Úgetr)   r9   s    r"   Ú_load_web_configr=   7   s9   € ðÝ1Ù‹}× Ñ  Ó'×-Ð-¨2Ð-øÜô ØŠ	ðús   ‚"( ¥( ¨8·8c                ó0   € V ^8„  d   QhR\         R\         /# )r   Úkeyr   r   )r!   s   "r"   r#   r#   @   s   € ÷ ?ñ ?œSð ?´ñ ?r$   c                ó€   € \        4       P                  V 4      ;'       g    RP                  4       P                  4       # )z?Lower-cased, stripped ``web.<key>`` value ("" when unset/null).r'   )r=   r<   Úlowerr,   )r?   s   &r"   Ú_configured_backendrB   @   s0   € äÓ×"Ñ" 3Ó'×-Ð-¨2×4Ñ4Ó6×<Ñ<Ó>Ð>r$   c                ó$   € V ^8„  d   QhR\         /# )r   Ú	func_namer   )r!   s   "r"   r#   r#   E   s   € ÷ ñ œcñ r$   c                ó�   €  ^ RI Hp \        W04      ! V!  #   \         d$   p\        P                  RYT4       Tu Rp?# Rp?ii ; i)zc``agent.web_search_registry.<func_name>(*args)``, or *default* if it raised (registry never fatal).Nz%web provider registry %s%r failed: %s)Úagent.web_search_registryÚweb_search_registryÚgetattrr)   ÚloggerÚdebug)rD   ÚdefaultÚargsÚregistry_modÚexcs   &&*  r"   Ú_registry_callrO   E   s@   € ðÝ8Ü�|Ô/°Ñ6Ð6øÜô Ü�‰Ð<¸iÈsÔSØ�ûðús   ‚ —A¢A ºAÁ Ac                ó$   € V ^8„  d   QhR\         /# )r   Úbackendr   )r!   s   "r"   r#   r#   O   s   € ÷ Nñ N¤cñ Nr$   c                ó0   € V '       d   \        RRV 4      # R# )z4Plugin-registered web provider by name, or ``None``.Úget_providerN©rO   ©rQ   s   &r"   Ú_registered_web_providerrV   O   s   € ç<CŒ>˜.¨$°Ó8ÐMÈÐMr$   c                 ó   € \        R. 4      # )z<All plugin-registered web providers (empty list on failure).Úlist_providersrT   © r$   r"   Ú_list_registered_web_providersrZ   T   s   € äÐ*¨BÓ/Ð/r$   c                óR   € V ^8„  d   QhR\         R\         R\        \        ,          /# )r   ÚmethodÚcontextr   )r    r   r1   )r!   s   "r"   r#   r#   Y   s%   € ÷ ñ œSð ¬3ð ¼Ä½ñ r$   c                ó°   €  \        \        W4      ! 4       4      #   \         d0   p\        T RT 4      p\        P	                  RYAY#4        Rp?R# Rp?ii ; i)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)r1   rH   r)   rI   rJ   )Úproviderr\   r]   rN   r   s   &&&  r"   Ú_prober`   Y   sL   € ðÜ”G˜HÔ-Ó/Ó0Ð0øÜô Ü�x ¨Ó2ˆÜ�‰Ð8¸$ÈÔUÝûðús   ‚ ›A¦$AÁAc                ó$   € V ^8„  d   QhR\         /# r6   r   )r!   s   "r"   r#   r#   d   s   € ÷ Fñ F”cñ Fr$   c                 ó®   € \        4       p V '       d   V \        8X  d   R# T # \        R4      e   R# \        4       ;'       g    \	        4       ;'       g    R# )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;   )rB   r   r	   Ú_autodetect_backendÚ_keyless_backend)Ú
configureds    r"   Ú_get_backendrg   d   sO   € ô %Ó&€Jßà(Ô,AÔAˆ{ÐQÀzÐQÜ�eÓÒ(áô Ó ×EÐEÔ$4Ó$6×EÐE¸+ÐEr$   c                ó:   € V ^8„  d   QhR\         \        ,          /# r6   ©r   r    )r!   s   "r"   r#   r#   v   s   € ÷ ñ œX¤c�]ñ r$   c                 óÚ  € R\        R4      3R\        R4      3R\        R4      3R\        R4      3R	\        R
4      3R\        R4      ;'       g    \        R4      3R\        4       3R\        R4      3R\        R4      3R\        4       33
p V  F  w  rV'       g   K  Vu # 	  \        4        F:  pVP                  \
        9  g   K  \        VR4      '       g   K.  VP                  u # 	  R# )zëAutodetect rungs above the keyless tier, or None. Explicit user credentials beat the managed-gateway
probe (a Nous OAuth token's tier may not grant web access; the gateway then fails at runtime with no
fallback). Free tiers trail paid.ÚtavilyÚTAVILY_API_KEYÚ
perplexityÚPERPLEXITY_API_KEYÚexaÚEXA_API_KEYÚparallelÚPARALLEL_API_KEYÚkeenableÚKEENABLE_API_KEYrc   ÚFIRECRAWL_API_KEYÚFIRECRAWL_API_URLÚsearxngÚSEARXNG_URLú
brave-freeÚBRAVE_SEARCH_API_KEYÚddgsÚis_availableN)r4   r   Ú_ddgs_package_importablerZ   r   Ú_LEGACY_WEB_BACKENDSr`   )Úbackend_candidatesrQ   Ú	availabler_   s       r"   rd   rd   v   sì   € ð
 
”8Ð,Ó-Ð.°¼xÐH\Ó?]Ð0^Ø	”˜Ó'Ð(Ø	”XÐ0Ó1Ð2°ZÄÐJ\ÓA]Ð4^Ø	”hÐ2Ó3×TÐT´xÐ@SÓ7TÐUØ	Ô,Ó.Ð/°)¼XÀmÓ=TÐ1UØ	”xÐ 6Ó7Ð8¸6ÔC[ÓC]Ð:^ðÐó 1Ñˆß‰9ØŠNñ 1ô
 3Ö4ˆØ�=‰=Ô 4Ö4¼ÀÈ.×9YÔ9YØ—=‘=Ò ñ 5ñ r$   c                ó:   € V ^8„  d   QhR\         \        ,          /# r6   ri   )r!   s   "r"   r#   r#   �   s   € ÷ ñ œ(¤3�-ñ r$   c                 ó  €  \        4        ^ RIHp Hp V! 4       '       d6   V ! 4        F*  p\	        V4      pVf   K  \        VR4      '       g   K(  Vu # 	  R#   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)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).)Ú_keyless_preferenceÚ_keyless_tier_enabledNÚis_keyless_availablez keyless fallback walk failed: %s)	Ú_ensure_web_plugins_loadedrF   rƒ   r„   rV   r`   r)   rI   rJ   )rƒ   r„   r   r_   rN   s        r"   re   re   �   st   € ð	>Ü"Ô$ßXÙ ×"Ò"Ù+Ö-�Ü3°DÓ9�ØÔ'¬F°8Ð=S×,TÔ,TØ’Kñ .ñ øô ô >Ü�‰Ð7¸×=Ð=Ùûð>ús(   ‚5A ¼A ÁA ÁA ÁBÁ#A?Á?Bc                ó$   € V ^8„  d   QhR\         /# r6   ©r1   )r!   s   "r"   r#   r#   ž   s   € ÷ lñ lœTñ lr$   c                 ó4  € \        R4      p V '       d
   V \        8H  # \        R4      pVe
   V\        8H  # \        4       pVR8X  d6   \	        R4      ;'       g    \	        R4      '       * ;'       d    \        4       # ^ RIHpHp VRJ ;'       d    V! VR7      RJ# )	uœ  True when web_search is on the managed Nous route: the stored ``nous`` selection (a
``web.search_backend: nous`` pin, else the shared one), or a never-configured install whose
autodetect lands on the gateway â€” the entitled Firecrawl gateway, or free Perplexity fast search
for any Nous identity when nothing else is configured (search-only: the extract ladder is
untouched). A stored vendor selection never is.Úsearch_backendr;   Nrc   ru   rv   )Úpeek_nous_access_tokenÚresolve_free_search_gateway)Útoken_reader)	rB   r   r	   rd   r4   r   Útools.managed_tool_gatewayr‹   rŒ   )Ú
search_pinÚselectedrQ   r‹   rŒ   s        r"   Ú_managed_web_searchr‘   ž   s•   € ô %Ð%5Ó6€JßØÔ2Ñ2Ð2Ü˜eÓ$€HØÒØÔ0Ñ0Ð0Ü!Ó#€GØ�+ÔÜÐ0Ó1×RÐR´XÐ>QÓ5RÔS×pÐpÔXnÓXpÐpß^Ø�dˆ?×kÐkÑ:ÐH^Ô_ÐgkÐkÐkr$   c                ó$   € V ^8„  d   QhR\         /# r6   r   )r!   s   "r"   r#   r#   ±   s   € ÷ Eñ EœSñ Er$   c                 óz   € \        R4      p V '       d   V \        8w  d   V # \        4       '       d   R# \        4       # )a  Backend for web_search: ``web.search_backend`` (strict, no probe) > ``web.backend`` > autodetect.
The managed Nous route (a ``nous`` pin or shared selection) serves search from Perplexity (extract
stays on Firecrawl); managed Firecrawl is the per-call fallback, see ``_memoized_search``.rŠ   rm   )rB   r   r‘   rg   ©Úpins    r"   Ú_get_search_backendr–   ±   s4   € ô Ð.Ó
/€Cß
ˆsÔ+Ô+Øˆ
Ü.×0Ò0ˆ<ÐD´l³nÐDr$   c                ó$   € V ^8„  d   QhR\         /# r6   r   )r!   s   "r"   r#   r#   »   s   € ÷ Rñ Rœcñ Rr$   c                 óZ   € \        R4      p V \        8X  d   R# T ;'       g    \        4       # )zÀBackend for web_extract: ``web.extract_backend`` (strict, no probe) > ``web.backend`` > autodetect.
A ``nous`` pin is managed Firecrawl; the client picks the gateway route for that capability.Úextract_backendrc   )rB   r   rg   r”   s    r"   Ú_get_extract_backendrš   »   s,   € ô Ð/Ó
0€CØÔ!6Ô6ˆ;ÐQ¸C×<QÐ<QÄ<Ã>ÐQr$   c                ó$   € V ^8„  d   QhR\         /# r6   rˆ   )r!   s   "r"   r#   r#   Â   s   € ÷ ñ ¤$ñ r$   c                 ó4   €  ^ RI p R#   \         d     R# i ; i)zXddgs is the only backend gated on package presence; single symbol so tests can patch it.NTF)r{   ÚImportError)r{   s    r"   r}   r}   Â   s   € ðÛÙøÜô Úðús   ‚ ˆ–c                ó$   € V ^8„  d   QhR\         /# r6   rˆ   )r!   s   "r"   r#   r#   Ë   s   € ÷ ñ œñ r$   c                  óB   €  ^ RI Hp  V ! 4       #   \         d     R# i ; i)é    ©Úhas_xai_credentialsF)Útools.xai_httpr¢   r)   r¡   s    r"   Ú_xai_availabler¤   Ë   s%   € ðÝ6Ù"Ó$Ð$øÜô Úðús   ‚ ��ro   c                  ó   € \        R 4      # )rp   ©r4   rY   r$   r"   Ú<lambda>r§   Ù   s	   € ”8˜MÔ*r$   rq   c                  ó   € \        R 4      # )rr   r¦   rY   r$   r"   r§   r§   Ú   ó
   € œÐ!3Ô4r$   rs   c                  ó   € \        R 4      # )rt   r¦   rY   r$   r"   r§   r§   Û   r©   r$   rc   c                  ó   € \        4       # r3   )r   rY   r$   r"   r§   r§   Ü   s   € Ô0Ô2r$   rk   c                  ó�   € \        R 4      ;'       g5    \        ;QJ d    R R 4       F  '       g   K   R# 	  R# ! R R 4       4      # )rl   c              3   ó>   "  € T F  p\        V4      R 8H  x € K  	  R# 5i)rk   N)rB   )Ú.0Úks   & r"   Ú	<genexpr>Ú<lambda>.<locals>.<genexpr>Þ   s   é € Ð
hÑ7g°!Ô˜qÓ! XÖ-Ó7gùs   ‚TF)rQ   rŠ   r™   )r4   ÚanyrY   r$   r"   r§   r§   Ý   sL   € ”hÐ/Ó0÷ ið iß
ƒsÑ
hÑ7gÓ
h‡s„sðiÚ
ðiØ
Ñ
hÑ7gÓ
hÓhðir$   rm   c                  ó<   € \        R 4      ;'       g    \        4       # )rn   )r4   r‘   rY   r$   r"   r§   r§   ß   s   € œ(Ð#7Ó8×QÐQÔ<OÓ<QÐQr$   rw   c                  ó   € \        R 4      # )rx   r¦   rY   r$   r"   r§   r§   à   s	   € ”x Ô.r$   ry   c                  ó   € \        R 4      # )rz   r¦   rY   r$   r"   r§   r§   á   s
   € œ(Ð#9Ô:r$   r{   c                  ó   € \        4       # r3   )r}   rY   r$   r"   r§   r§   â   s   € Ô,Ô.r$   Úxaic                ó0   € V ^8„  d   QhR\         R\        /# )r   rQ   r   r0   )r!   s   "r"   r#   r#   è   s   € ÷ 'ñ '¤3ð '¬4ñ 'r$   c                ó  € T ;'       g    RP                  4       P                  4       p V \        9   d   RM
\        V 4      pVe   \	        VR4      ;'       g    R# \
        P                  V 4      pV'       d   V! 4       # R# )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'   Nr|   F)rA   r,   r~   rV   r`   Ú_BUILTIN_AVAILABILITYr<   )rQ   r_   Úprobes   &  r"   Ú_is_backend_availabler¼   è   sq   € ð �}ˆ}˜"×#Ñ#Ó%×+Ñ+Ó-€GØÔ"6Ô6‰tÔ<TÐU\Ó<]€HØÒÜ�h Ó/×8Ð8°5Ð8Ü!×%Ñ% gÓ.€Eß‰5‹7Ð& Ð&r$   c                ó:   € V ^8„  d   QhR\         \        ,          /# r6   )Úlistr    )r!   s   "r"   r#   r#   ý   s   € ÷ ñ œ4¤�9ñ r$   c                 ó
   € . RO# )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.)rp   rr   rl   rn   rt   ru   rv   ÚFIRECRAWL_GATEWAY_URLÚPERPLEXITY_GATEWAY_URLÚTOOL_GATEWAY_DOMAINÚTOOL_GATEWAY_SCHEMEÚTOOL_GATEWAY_USER_TOKENrY   rY   r$   r"   Ú_web_requires_envrÅ   ý   s   € òð r$   Ú	web_toolsÚWEB_TOOLS_DEBUG)Úenv_varc                ó   € V ^8„  d   QhRR/# )r   r   NrY   )r!   s   "r"   r#   r#     s   € ÷ Kñ K Dñ Kr$   c                 ó€   €  ^ RI Hp  V ! 4        R#   \         d"   p\        P	                  RT4        Rp?R# Rp?ii ; i)u9  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.
)Ú_ensure_plugins_discoveredz+Web plugin discovery failed (non-fatal): %sN)Úhermes_cli.pluginsrË   r)   rI   Úwarning)rË   rN   s     r"   r†   r†     s5   € ðKÝAÙ"Ö$øÜô Kä�‰ÐDÀc×JÒJûðKús   ‚ ‘=œ8¸=c          	      ót   € V ^8„  d   QhR\         R\        R\        \         ,          R\        \         ,          /# )r   Ú	call_nameÚdebug_call_dataÚ	error_msgr   )r    r7   r   )r!   s   "r"   r#   r#   $  s9   € ÷ @ñ @œSð @´4ð @ÄHÌSÅMð @Ô]eÔfiÕ]jñ @r$   c                ó²   € Ve   \         P                  RV4       W!R&   \        P                  W4       \        P	                  4        Vf   R# \        V4      # )zhLog the call into the debug session; with *error_msg*, record it and return its ``tool_error`` envelope.Nz%sÚerror)rI   rJ   Ú_debugÚlog_callÚsaveÚ
tool_error)rÏ   rÐ   rÑ   s   &&&r"   Ú_finish_debugrØ   $  sH   € àÒÜ�‰�T˜9Ô%Ø#,˜Ñ Ü
‡O�O�IÔ/Ü
‡K�K„MØÒ$ˆ4Ð?¬*°YÓ*?Ð?r$   c                ó<   € V ^8„  d   QhR\         R\        R\         /# ©r   ÚqueryÚlimitr   )r    Úint)r!   s   "r"   r#   r#   .  s&   € ÷ -`ñ -`œ3ð -`¤sð -`´3ñ -`r$   c           
     óÂ  €  \        \        \        V4      ^4      ^d4      pRRV RV/RRR^ R^ R^ /p ^ R	IHp V! 4       '       d   \        R
RR7      # \        4        ^ RI	H
pHp \        4       pV'       d	   V! V4      MRpVe   VP                  4       '       g^   VfS   V'       dK   \        R4      '       d:   \        RV4      ;q‚R&   \!        RV4       \"        P$                  ! RRRV/^RR7      # V! 4       pVf   Rp	RRR\'        RV	4      /p
M-\(        P+                  RVP,                  W4       \/        WpV4      p
\1        V
P3                  R/ 4      P3                  R. 4      4      VR&   \"        P$                  ! V
^RR7      p\1        V4      VR&   \!        RV4       V#   \        \        3 d    ^p EL“i ; i  \4         d   p\!        RTRT: 24      u Rp?# Rp?ii ; i)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": ...}``.
Ú
parametersrÛ   rÜ   rÓ   NÚresults_countÚoriginal_response_sizeÚfinal_response_size)Úis_interruptedÚInterruptedF)Úsuccess)Úget_active_search_providerrS   r;   ÚsearchÚweb_search_toolrå   ©ÚindentÚensure_asciizDNo web search provider configured. Run `hermes tools` to set one up.z#Web search via %s: '%s' (limit: %d)ÚdatazError searching web: )ÚminÚmaxrÝ   Ú	TypeErrorÚ
ValueErrorÚtools.interruptrã   r×   r†   rF   ræ   rS   r–   Úsupports_searchr
   r   rØ   ÚjsonÚdumpsr   rI   Úinfor   Ú_memoized_searchÚlenr<   r)   )rÛ   rÜ   rÐ   rã   ræ   Ú_wsp_get_providerrQ   r_   Ú
error_textÚfallbackÚresponse_dataÚresult_jsonÚes   &&           r"   rè   rè   .  sà  € ðÜ”Cœ˜E›
 AÓ&¨Ó,ˆð 	�w  w°Ð6¸ÀÀÐXYØ  !Ð%:¸Að€Oð
`Ý2Ù×ÒÜ˜m°UÔ;Ð;ä"Ô$ßkÜ%Ó'ˆß18Ñ$ WÔ-¸dˆØÒ 8×#;Ñ#;×#=Ò#=ØÒ§GÔ0@À×0GÒ0GÜ8OÐPXÐZaÓ8bÐb�
¨WÑ5ÜÐ/°ÔAÜ—z’z 9¨e°W¸jÐ"IÐRSÐbgÔhÐhá1Ó3ˆHàÒØ]ˆHØ&¨¨wÔ8JÈ8ÐU]Ó8^Ð_‰Mä�K‰KÐ=¸x¿}¹}ÈeÔ[Ü,¨X¸eÓDˆMä+.¨}×/@Ñ/@ÀÈÓ/L×/PÑ/PÐQVÐXZÓ/[Ó+\ˆ˜Ñ(Ü—j’j °qÀuÔMˆÜ14°[Ó1AˆÐ-Ñ.ÜÐ'¨Ô9ØÐøôG ”zÐ"ô Ø‹ðûôH ô `ÜÐ.°ÐCXÐYZÑX]ÐA^Ó_Õ_ûð`úsO   ‚F ²F8 ÁF8 ÁAF8 ÂF8 Â&A	F8 Ã0B-F8 ÆF5Æ4F5Æ8GÇGÇGÇGc                ó<   € V ^8„  d   QhR\         R\        R\        /# rÚ   )r    rÝ   r7   )r!   s   "r"   r#   r#   ^  s!   € ÷ '7ñ '7¤cð '7´#ð '7¼$ñ '7r$   c                óÄ  a aaaa	€ ^ RI Ho	HpHp R VV	VV V3R llpR V V3R lloVP	                  S P
                  SS4      pVf   VP                  S P
                  SS4      ;_uu_ 4        VP	                  S P
                  SS4      pVf0   V! 4       w  rgV'       g   VP                  S P
                  SSV4       RRR4       V! VS4      #   + '       g   i     L; i)ut  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.)Úbucket_limitÚsearch_memoÚslice_search_responsec                óF   € V ^8„  d   QhR\         \        \        3,          /# r6   )Útupler7   r1   )r!   s   "r"   r#   Ú&_memoized_search.<locals>.__annotate__e  s   € ÷ ñ œ%¤¤d 
Õ+ñ r$   c                  ó*  <€ S! S4      p  SP                  SV 4      pTP                  R4      '       g+   S! \        TP                  RR4      4      T 4      pTe   TR3# TR3#   \         d&   pS! \        T4      T 4      pTf   h TR3u R p?# R p?ii ; i)NTrå   rÓ   r'   F)rç   r)   r    r<   )	Úfetch_limitÚresprN   ÚservedÚ_served_after_failurer   rÜ   r_   rÛ   s	       €€€€€r"   Ú_paid_searchÚ&_memoized_search.<locals>._paid_searche  s�   ø€ Ù" 5Ó)ˆð	 Ø—?‘? 5¨+Ó6ˆDð �x‰x˜	×"Ò"Ù*¬3¨t¯x©x¸ÀÓ/DÓ+EÀ{ÓSˆFØÒ!Ø˜t�|Ð#Ø�Uˆ{Ðøô ô 	 Ù*¬3¨s«8°[ÓAˆFØŠ~ØØ˜4�<Õûð		 ús   ‹A" Á"BÁ-BÂBÂBc                óR   € V ^8„  d   QhR\         R\        R\        \        ,          /# )r   rÓ   r  r   )r    rÝ   r   r7   )r!   s   "r"   r#   r  t  s*   € ÷ pñ p¤Sð p´sð p¼xÌ½~ñ pr$   c                ó‚   <€ \        SV SV4      pVe   V# \        S4      '       d   \        SP                  V SV4      # R# )z“Managed Firecrawl for a failed managed Perplexity call, else the one-shot keyless rescue when
eligible; None means the vendor's own failure stands.N)r   r   r   r   )rÓ   r  rú   r_   rÛ   s   && €€r"   r
  Ú/_memoized_search.<locals>._served_after_failuret  sF   ø€ ô ,¨H°e¸UÀKÓPˆØÒØˆOÜK[Ð\d×KeÒKeŒ~˜hŸm™m¨U°E¸;ÓGÐoÐkoÐor$   N)Útools.web_result_cacher   r  r  Úlookupr   Úflight_lockÚstore)
r_   rÛ   rÜ   r  r  r  rû   Úwas_rescuedr
  r   s
   fff     @@r"   rö   rö   ^  s½   ü€ ÷
 XÑW÷ô ÷pñ pð  ×&Ñ& x§}¡}°e¸UÓC€MØÒØ×$Ñ$ X§]¡]°E¸5×AÕAà'×.Ñ.¨x¯}©}¸eÀUÓKˆMØÒ$Ù-9«^Ñ*�ß"Ø×%Ñ% h§m¡m°U¸EÀ=ÔQ÷ Bñ ! °Ó6Ð6÷ B×Aús   Á-ACÃC	c                ó‚   € V ^8„  d   QhR\         \        ,          R\        R,          R\        \        ,          R\        /# )r   Úurlsr!   NÚ
char_limitr   )r¾   r   r    r   rÝ   )r!   s   "r"   r#   r#   ˆ  s>   € ÷ <fñ <f¤¤c¥ð <f´C¸$µJð <fÔS[Ô\_ÕS`ð <fÔloñ <fr$   c              ƒ  óœ  "  € \        V 4      w  r4rVVe   V# RRVRVRV/RRR^ R^ R	^ R
^ R. R. /p \        P                  R\        V4      4       . . / r©p\	        WC4       FP  w  r¼\        V4      G Rj  x€L
 '       d%   VP                  V4       V	P                  V4       KB  \        VR4      W«&   KR  	  . pV'       d=   \        4       p\        4        \        V4      w  ppVe   V# \        WøV4      G Rj  x€L
 pV'       g	   V
'       d   / V
CVCp\        \        V 4      VW˜V4      p\        P                  R\        V4      4       \        V4      VR&   \        \        P                  ! RV/4      4      VR	&   VR,          P                  R4       \        V\!        V4      V4       \#        V4      pV'       d   \        P                  ! RV/^RR7      M
\%        R4      p\'        V4      p\        V4      VR
&   VR,          P                  R4       \)        RV4       V#  EL« EL,  \*         d   p\)        RTRT: 24      u Rp?# Rp?ii ; i5i)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": [...]}``.
Nrß   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 pagesÚresultsÚtruncate_and_storeFré   z%Content was inaccessible or not foundÚbase64_image_conversionÚweb_extract_toolzError extracting content: )r   rI   rõ   r÷   Úzipr   Úappendr   rš   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  rQ   r_   Ú
error_jsonÚfixedÚtrimmedrü   Úcleaned_resultrý   s   &&&                   r"   r   r   ˆ  sV  é € ô BXÐX\ÓA]Ñ>€O¨ØÒØˆà�v˜°¸&À,ÐPZÐ[Ð]dÐfjØ˜1Ð/°Ð4LÈaÐQfÐhiØ˜bÐ"6¸ð€Oð,fÜ�‰Ð7¼¸_Ó9MÔNà02°B¸ ˆ	ÜÐ0ÖB‰JˆEÜ& sÓ+×+Ö+Ø× Ñ  Ô%Ø×#Ñ# EÖ*ä&3ØÐUó'�Ó#ñ Cð ˆßÜ*Ó,ˆGÜ&Ô(Ü#<¸WÓ#EÑ ˆH�jØÒ%Ø!Ð!Ü.¨xÀFÓK×KˆG÷ Ÿ<Ø4�|Ð4 |Ð4ˆEÜ%¤c¨$£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øô. ô fÜÐ/°ÐD^Ð_`Ñ^cÐBdÓeÕeûðfüs~   ‚-I°AH# Á2HÁ3	H# Á=?H# Â='H# Ã$IÃ%H# Ã4H Ã5H# ÄH# Ä	DH# ÈIÈH# È H# È#I	È.IÈ>I	È?IÉI	É	Ic                ó$   € V ^8„  d   QhR\         /# r6   rˆ   )r!   s   "r"   r#   r#   Ç  s   € ÷ ^ñ ^¤Dñ ^r$   c                ót   € V f   R# \        V RR4      pVf   R# \        T;'       g    \        V RR4      4      # )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.
Fr|   z during readiness checkr…   )r`   r1   )r_   Úreadys   & r"   Ú_provider_is_readyr3  Ç  sB   € ð ÒÙÜ�8˜^Ð-FÓG€EØ‚}ÙÜ�×\Ð\œ Ð*@ÐB[Ó\Ó]Ð]r$   c                ó$   € V ^8„  d   QhR\         /# r6   rˆ   )r!   s   "r"   r#   r#   ß  s   € ÷ ñ œ4ñ r$   c                 óT  € \        4       3 U u. uF  q '       g   K  V NK  	  up \         Uu. uF  q\        9  g   K  VNK  	  up,           p\        ;QJ d    R V 4       F  '       g   K   RM	  RM! R V 4       4      '       d   R#  \	        4        ^ RIHpHp V! 4       V! 4       3 FB  pVe(   \        VRR4      \        9   d   \        4       e    R# K.  \        V4      '       g   KA   R# 	  R# u up i u upi   \         d"   p\        P                  RT4        Rp?R# Rp?ii ; i)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   ó8   "  € T F  p\        V4      x € K  	  R # 5ir3   )r¼   )r®   rQ   s   & r"   r°   Ú$check_web_api_key.<locals>.<genexpr>ê  s   é € Ð
D¹¨gÔ  ×)Ð)»ùs   ‚TF)ræ   Úget_active_extract_providerNr   z3web provider registry availability check failed: %s)rB   r~   Ú_WEB_CHECK_SKIPr²   r†   rF   ræ   r8  rH   re   r3  r)   rI   rJ   )ÚcÚbÚ
candidatesræ   r8  r_   rN   s          r"   Úcheck_web_api_keyr=  ß  sô   € ô  3Ó4Ñ6Ó<Ñ6˜»!—1�1Ñ6Ñ<Ý 4ÓQÑ 4˜1ÄÑ8P—Q�QÑ 4ÑQõR€Jç
ƒsÑ
D¹Ó
D‡s‡s‚sÑ
D¹Ó
D×DÒDÙðÜ"Ô$ßeÙ3Ó5Ñ7RÓ7TÓUˆHØÒ#¬°¸&À$Ó(GÌ?Ô(Zô $Ó%Ò1ÚÙÜ! (×+Ô+Úñ Vñ ùò% =ùÚQøô$ ô Ü�‰ÐJÈCÔPÝûðús?   Ž	C1œC1¬C6½C6Â	A
C; ÃC; Ã*C; Ã-C; Ã;D'ÄD"Ä"D')Úregistryr×   r   Ú
web_searchÚdescriptiona  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.rß   ÚtypeÚobjectÚ
propertiesrÛ   Ú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".rÜ   Úintegerz3Maximum number of results to return. Defaults to 5.ÚminimumÚmaximumrK   ÚrequiredÚ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.r  ÚarrayÚitemsz:List of URLs to extract content from (max 5 URLs per call)ÚmaxItemsr  zÂ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;   c                 ó\   € \        V P                  R R4      V P                  R^4      R7      # )rÛ   r'   rÜ   )rÜ   )rè   r<   ©rL   Úkws   &,r"   r§   r§   1  s#   € œ¨t¯x©x¸ÀÓ/DÈDÏHÉHÐU\Ð^_ÓL`Õar$   u   ðŸ”�i † )r   ÚtoolsetÚschemaÚhandlerÚcheck_fnÚrequires_envÚemojiÚmax_result_size_charsc                 ó¸   € \        \        V P                  R 4      \        4      '       d   V P                  R . 4      R,          M. RV P                  R4      R7      # )r  :Né   NÚmarkdownr  )r  )r   Ú
isinstancer<   r¾   rN  s   &,r"   r§   r§   7  sC   € Ô/Ü$.¨t¯x©x¸Ó/?Ä×$FÒ$Fˆ�‰�˜Ó˜RÖ ÈBÐPZØ—8‘8˜LÓ)õ r$   Tu   ðŸ“„)	r   rP  rQ  rR  rS  rT  Úis_asyncrU  rV  rU   )r'   r3   )rX  )NN)PÚ__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   r   Útools.web_tools_truncater   r   r   r   Útools.web_tools_extractr   r   r   r   r   r   r   Ú	getLoggerÚ__name__rI   r.   r4   r=   rB   rO   rV   rZ   r`   rg   rd   re   r‘   r–   rš   r}   r¤   rº   Ú	frozensetr~   r¼   rÅ   rÔ   r†   rØ   rè   rö   r   r3  r9  r=  Útools.registryr>  r×   ÚWEB_SEARCH_SCHEMAÚWEB_EXTRACT_SCHEMAÚregisterrY   r$   r"   Ú<module>rr     s·  ðñó Û Û 	ß &Ñ &àimÐ mÐ Ð mÐ,Ð mÐ/?Ð mÐBXÐ[fç ZÝ ,ß ^Ñ ^Ý .ß ]Ñ ]ß |Ó |÷÷ ñ ð
 
×	Ò	˜8Ó	$€õ
Iõ "õ÷?õ
õNò
0÷
õFõ$õ.õ"lõ&EõRõõð 
Ñ*ØÑ4ØÑ4ØÑ2Øñ iàÑQØÑ.ØÑ:Ø
Ñ.Ø	ˆ>ðÐ ñ !Ð!6Ó7Ð õ'õ*ñ 
�kÐ+<Ô	=€õK÷(@÷-`õ`'7÷T<fõ~^ñ* ˜U˜GÓ$€õ÷D 0ð ˆLØð  wØØ�ØØØ˜Øð   Gðð Ø˜	ØÐTØ˜1Ø˜3Ø˜1ðð
ð 	�W�IððÐ ð. ˆMØð  A	ØØ�ØØØ˜Ø˜& (Ð+ØÐ[Ø˜Að	ð Ø˜	Øð   dØ˜4ðð
ð 	�V�HððÐ ð, 	× Ò Ø	˜uÐ->ÙaØÑ->Ó-@ÈØ!õ	ð 	× Ò Ø	 Ð.@ñð Ñ->Ó-@È4ÐW]Ø!÷r$   