Ë
    ãß²jé,  ã                   óâ  — d Z ddlZddlZddlZddlmZmZmZmZ ddl	m
Z
mZ ddlmZ ddlmZmZ  ej"                  d«      ZdZd	Zd
ZdZdedee   fd„Zdededefd„Zdededefd„Zdededefd„Zdedee   deeef   fd„Zdedefd„Zdefd„Zdedeee f   dee   dee   dee    dee    fd „Z!d!ee   fd"„Z"defd#„Z#de$fd$„Z%dee   d%ee   dee    fd&„Z&d'ee   d%ee   dee    fd(„Z'y))a„  web_extract helpers: URL validation, provider resolution, cache-aware dispatch.

Order of controls (each is a gate, never skipped by a cache hit): secret-URL
refusal -> SSRF filter (in web_tools.web_extract_tool) -> provider resolution
(strict selection) -> per-URL website policy -> disk cache -> vendor call with
one-shot keyless rescue. Logs under the origin (tools.web_tools) logger.
é    N)ÚAnyÚDictÚListÚOptional)Úselection_errorÚselection_exists)Únormalize_url_for_request)Ú_rescue_eligibleÚ_rescue_extractztools.web_toolsz/Extract backend returned no result for this URLg      ^@z.firecrawl, tavily, keenable, exa, or parallel.zdInvalid URL item at index {}: expected a URL string or an object with a string 'url' or 'href' fieldÚvalueÚreturnc                 ó¶   — t        | t        «      r$| j                  d«      xs | j                  d«      } t        | t        «      r| j	                  «       xs dS dS )a  URL from a model-supplied extract item (str, or dict with ``url``/``href``); None if unusable.

    Models sometimes forward a whole search result instead of its URL, hence the dict form. Never
    stringify arbitrary objects into misleading fetch targets.
    ÚurlÚhrefN)Ú
isinstanceÚdictÚgetÚstrÚstrip)r   s    ú</home/ubuntu/.hermes/hermes-agent/tools/web_tools_extract.pyÚ_web_extract_urlr      sI   € ô �%œÔØ—	‘	˜%Ó Ò5 E§I¡I¨fÓ$5ˆÜ&0°¼Ô&<ˆE�K‰K‹MÒ!˜TÐFÀ$ÐFó    Ú
capabilityÚdisabled_keyc           	      óJ   — |j                  dd«      d   }d| › d|› d|› d|› d�	S )	zNError text when the configured backend's bundled plugin is disabled in config.ú/é   éÿÿÿÿzweb.z_backend is set to 'z', but its plugin ('zC') is disabled in config. Re-enable it with `hermes plugins enable z'` (or remove it from plugins.disabled).)Úsplit)r   r   Úvendors      r   Ú_disabled_plugin_errorr!   '   sI   € à×Ñ  QÓ'¨Ñ+€Fà
ˆzˆlÐ.¨v¨hÐ6JÈ<È.ð Y?Ø?K¸nð M0ð	0ðr   Úfallbackc                 ó@   — ddl m}  || ¬«      }|rt        | |«      S |S )z^Error when no provider resolved: point at a disabled bundled plugin if that is the real cause.r   )Ú_disabled_web_plugin_for)r   )Úagent.web_search_registryr$   r!   )r   r"   r$   r   s       r   Ú_no_provider_errorr&   1   s%   € åBÙ+°zÔB€LÙ?KÔ! *¨lÓ;ÐYÐQYÐYr   Úbackendc           	      óD   — d| › d�}t        | t        dd|› d�|«      «      S )z´Error for a stored-but-unregistered backend: name the disabled plugin, else the bad selection.
    Strict selection never silently switches to whatever the availability walk finds.zno registered web z provider has that nameÚwebú')r&   r   )r   r'   Úfailures      r   Ú_strict_selection_errorr,   8   s2   € ð # : ,Ð.EÐF€GÜ˜j¬/¸%À1ÀWÀIÈQÀÐQXÓ*YÓZÐZr   r   Úerrorc                 ó   — | dd|dœS )NÚ )r   ÚtitleÚcontentr-   © )r   r-   s     r   Ú_result_entryr3   ?   s   € Ø °¸UÑCÐCr   c                 ó6   — t        j                  d| dœd¬«      S )NF©Úsuccessr-   )Úensure_ascii©ÚjsonÚdumps©r-   s    r   Ú_extract_error_jsonr<   C   s   € Ü�:‰: %°%Ñ8ÀuÔMÐMr   c                 ó:   — dddt        j                  d| dœ«      fS )zRWhole-call refusal tuple for ``_validate_extract_urls`` (exfiltration prevention).NFr5   r8   r;   s    r   Ú_refuse_allr>   G   s   € à��tœTŸZ™Z°EÀEÑ(JÓKÐKÐKr   ÚtotalÚfixedÚfetch_positionsÚ
fetch_urlsÚresultsc                 óÖ   — t        |«      }t        |«      D ]0  \  }}t        ||   t        «      }|t	        |«      k  r||   n|||<   Œ2 t        | «      D �	cg c]  }	||	   ‘Œ	 c}	S c c}	w )z·Rebuild a ``total``-long result list: *fixed* entries by position, fetched *results* at
    *fetch_positions* (a short provider list yields ``_NO_RESULT_ERROR`` entries for the rest).)r   Ú	enumerater3   Ú_NO_RESULT_ERRORÚlenÚrange)
r?   r@   rA   rB   rC   ÚmergedÚposÚpositionÚmissingÚis
             r   Ú_merge_in_orderrN   L   sq   € ô
 �%‹[€FÜ" ?Ó3ò K‰ˆˆXÜ 
¨3¡Ô1AÓBˆØ+.´°W³Ò+=˜7 3š<À7ˆˆxÒðKô  % U›|Ö,˜!ˆF�1‹IÒ,Ð,ùÒ,s   ÁA&Úurlsc           
      óv  ‡	— ddl mŠ	 ddlm} g g i }}}t	        | «      D ]“  \  }}t        |«      }|€#t        dt        j                  |«      «      ||<   Œ6t        |«      }t        ˆ	fd„| ||«      | ||«      fD «       «      rt        d«      c S |j                  |«       |j                  |«       Œ• |||dfS )a  Normalize model-supplied items and block URLs carrying secrets (percent-encoded forms are unquoted
    and checked too). Returns ``(normalized_urls, normalized_indices, invalid_urls, blocked_json)``;
    ``blocked_json`` is a whole-call refusal (exfiltration prevention) or None.r   )Ú
_PREFIX_RE)ÚunquoteNr/   c              3   ó@   •K  — | ]  }‰j                  |«      –— Œ y ­w©N)Úsearch)Ú.0ÚcrQ   s     €r   ú	<genexpr>z)_validate_extract_urls.<locals>.<genexpr>f   s   øè ø€ Òl¨ˆz× Ñ  ×#Ñlùs   ƒz_Blocked: URL contains what appears to be an API key or token. Secrets must not be sent in URLs.)Úagent.redactrQ   Úurllib.parserR   rE   r   r3   Ú_INVALID_ITEM_ERRORÚformatr	   Úanyr>   Úappend)
rO   rR   Únormalized_urlsÚnormalized_indicesÚinvalid_urlsÚindexÚitemÚ_urlÚnormalized_urlrQ   s
            @r   Ú_validate_extract_urlsrf   X   sÍ   ø€ õ (Ý$à8:¸BÀ¨Ð'€OÜ  “ò )‰ˆˆtÜ Ó%ˆØˆ<Ü"/°Ô4G×4NÑ4NÈuÓ4UÓ"VˆL˜ÑØÜ2°4Ó8ˆÜÓl¨d±G¸D³MÀ>ÑSZÐ[iÓSjÐ-kÔlÔlÜð4óò ð 	×Ñ˜~Ô.Ø×!Ñ! %Õ(ð)ð Ð.°¸dÐBÐBr   c                 ó>  — ddl m}m} | r || «      nd}|�|j                  «       r|dfS |�!dt	        |j
                  › d�t        z   «      fS | r"t        d«      rdt	        t        d| «      «      fS  |«       }|€ dt        z   }dt	        t        d|«      «      fS |dfS )a#  Resolve the extract provider for *backend*; returns ``(provider, error_json)``.

    A registered search-only backend is a typed error (never a silent switch). An unregistered name with
    a stored web selection is a strict-selection error; with no selection, fall through to the walk.
    r   )Úget_active_extract_providerÚget_providerNzU is a search-only backend and cannot extract URL content. Set web.extract_backend to r)   Úextractz?No web extract provider configured. Set web.extract_backend to )
r%   rh   ri   Úsupports_extractr<   Údisplay_nameÚ_EXTRACT_BACKENDS_HINTr   r,   r&   )r'   rh   Ú_wsp_get_providerÚproviderr"   s        r   Ú_resolve_extract_providerrp   p   sÎ   € ÷ iÙ-4Ñ  Ô)¸$€HØÐ × 9Ñ 9Ô ;Ø˜ˆ~ÐØÐØÔ(Ø×$Ñ$Ð%ð &*ð *Ü,BñCó
ð 
ð 	
ñ Ô# EÔ*ØÔ(Ô)@ÀÈGÓ)TÓUÐUÐUÙ*Ó,€HØÐØTÔWmÑmˆØÔ(Ô);¸IÀxÓ)PÓQÐQÐQØ�Tˆ>Ðr   c                  óŽ   — ddl m}  	 t         | «       j                  dt        «      «      S # t
        t        f$ r	 t        cY S w xY w)a  Wall-clock cap for one provider ``extract()`` dispatch (``web.extract_timeout``, default 120s).

    A hanging backend (server keeps the response open without finishing) otherwise stalls the
    tool call indefinitely. 0 or a negative value disables the cap.
    r   ©Ú_load_web_configÚextract_timeout)Útools.web_toolsrs   Úfloatr   Ú_DEFAULT_EXTRACT_TIMEOUT_SÚ	TypeErrorÚ
ValueErrorrr   s    r   Ú_extract_timeout_secondsrz   ˆ   sD   € õ 1ð*ÜÑ%Ó'×+Ñ+Ð,=Ô?YÓZÓ[Ð[øÜ”zÐ"ò *Ü)Ò)ð*ús   ˆ#, ¬AÁAr\   c              ƒ   óè  ‡K  — ddl }ddlm} t        «       }	 |j	                  | j
                  «      r| j                  ||¬«      }n"t        j                  | j
                  ||¬«      }|dkD  r t        j                  ||¬«      ƒ d{  –—† }n
|ƒ d{  –—† }|rKt'        d
„ |D «       «      r9t        | «      r.t        j                  t         | j                  ||«      ƒ d{  –—† S t)        |«      Š|D ]Á  }|j+                  d«      }t-        |t.        «      r|j+                  d«      nd}t1        ˆfd„|j+                  d«      |fD «       d«      }|j+                  dd«      xs |j+                  dd«      }|sŒ†|sŒ‰|j+                  d«      rŒ› ||||j+                  dd«      || j                  ¬«       ŒÃ |S 7 �Œ07 �Œ(# t        j                  $ r¦}t        j                  d| j                  |t        |«      «       |D �	cg c]  }	t        |	d|d›d	| j                  › �«      ‘Œ! nc c}	w }
}	t        | «      s|
cY d}~S t        j                  t         | j                  ||
«      ƒ d{  –—†7  cY d}~S d}~wt"        $ rm}t        | «      s‚ |D �	cg c]  }	t        |	t%        |«      «      ‘Œ nc c}	w }
}	t        j                  t         | j                  ||
«      ƒ d{  –—†7  cY d}~S d}~ww xY w7 �Œ­w)u  Call ``provider.extract`` (async or sync-in-thread), with one-shot keyless rescue.

    Rescue fires on a raised exception â€” including a dispatch timeout â€” or when the WHOLE batch
    failed (backend outage, not per-page problems). Rescued batches are never cached.
    r   N)Úextract_cache_put)r\   )Útimeoutz=web_extract provider '%s' timed out after %.0fs for %d URL(s)zExtract timed out after z.0fzs via c              3   ó>   K  — | ]  }|j                  d «      –— Œ y­w)r-   N)r   )rV   Úrs     r   rX   z$_dispatch_extract.<locals>.<genexpr>´   s   è ø€ Ò7¨!�q—u‘u˜W—~Ñ7ùs   ‚ÚmetadataÚ	sourceURLc              3   ó,   •K  — | ]  }|‰v sŒ|–— Œ y ­wrT   r2   )rV   ÚuÚ	requesteds     €r   rX   z$_dispatch_extract.<locals>.<genexpr>À   s   øè ø€ ÒN˜!¸qÀIº~”AÑNùs   ƒ	�r   Úraw_contentr/   r1   r-   r0   ©r\   ro   )ÚinspectÚtools.web_result_cacher|   rz   Úiscoroutinefunctionrj   ÚasyncioÚ	to_threadÚwait_forÚTimeoutErrorÚloggerÚwarningÚnamerG   r3   r
   r   Ú	Exceptionr   ÚallÚsetr   r   r   Únext)ro   rB   r\   r‡   r|   r}   ÚcororC   Úexcrƒ   ÚfailedÚfetchedÚmetaÚsourcer   Ú_contentr„   s                   @r   Ú_dispatch_extractrœ   •   s‹  øè ø€ ó Ý8Ü&Ó(€Gð[Ø×&Ñ& x×'7Ñ'7Ô8Ø×#Ñ# J°vÐ#Ó>‰Dä×$Ñ$ X×%5Ñ%5°zÈ&ÔQˆDØ�QŠ;Ü#×,Ñ,¨T¸7ÔC×C‰Gà —jˆGñ ”3Ñ7¨wÔ7Ô7Ô<LÈXÔ<VÜ×&Ñ&¤¸¿¹ÀzÐSZÓ[×[Ð[ô �J“€IØò nˆØ�{‰{˜:Ó&ˆÜ*4°T¼4Ô*@�—‘˜+Ô&ÀdˆÜÓN §¡¨EÓ 2°FÐ;ÔNÐPTÓUˆØ—;‘;˜}¨bÓ1ÒO°W·[±[ÀÈBÓ5OˆÚ’8 G§K¡K°Õ$8Ù˜c 8¨W¯[©[¸À"Ó-EÈfÐ_g×_lÑ_lÖmðnð €NðA Dùà úÜ×Ñò [Ü�‰ÐVØ—}‘} g¬s°:«ô	@ð &ö'Øô   Ð%=¸gÀc¸]È&ÐQY×Q^ÑQ^ÐP_Ð#`Õañ 'ùò 'ˆð 'ä Ô)Ø�MÜ×&Ñ&¤¸¿¹ÀzÐSYÓZ×ZÐZÕZûÜò [Ü Ô)ØØ6@ÖA°”- ¤3 s£8Õ,ÑAùÒAˆÐAÜ×&Ñ&¤¸¿¹ÀzÐSYÓZ×ZÐZÕZûð	[úð \ûsÓ   ƒK2™A0F? Â	F9Â

F? ÂF<ÂF? ÂAK2Ã!K/Ã"BK2Å9K2Å<K2Æ+K2Æ9F? Æ<F? Æ?K,Ç/I3È$H&È%I3È9K,È:K2È?)I3É(I+É)I3É-K,É.K2É3K,É?K'ÊJ,Ê+1K'ËKËK'Ë!K,Ë"K2Ë'K,Ë,K2Ú	safe_urlsc              ƒ   ó   K  — ddl m} ddlm} i g g }}}t	        |«      D ]Q  \  }}		  ||	«      }
|
€ ||	|| j                  ¬«      nd}|�|||<   Œ0|j                  |	«       |j                  |«       ŒS |s%t        t        |«      «      D �cg c]  }||   ‘Œ	 c}S t        j                  d| j                  t        |«      «       t        | ||«      ƒ d{  –—† }|s|S t        t        |«      ||||«      S # t
        $ r d}
Y ŒÔw xY wc c}w 7 Œ6­w)u·  Serve cache hits, fetch the rest, and merge back in ``safe_urls`` order.

    The disk cache (tools/web_result_cache.py) sits AFTER the secret-URL gate, SSRF gate, and provider
    resolution, and is gated per-URL on the website policy â€” a hit skips only the vendor call, never a
    control; policy-blocked URLs are cache misses. Keys include provider and format, so switching either
    within the TTL never serves the other's content.r   )Úextract_cache_get)Úcheck_website_accessNr†   zWeb extract via %s: %d URL(s))rˆ   rŸ   Útools.website_policyr    rE   r‘   r�   r^   rH   rG   rŽ   Úinforœ   rN   )ro   r�   r\   rŸ   Ú_check_siteÚcached_resultsrB   rA   rK   r   Ú_policy_blockÚhitrM   rC   s                 r   Ú_extract_safe_urlsr§   Ç   s  è ø€ õ 9ÝHØ24°b¸" �J€NÜ" 9Ó-ò 
-‰ˆ�#ð	!Ù'¨Ó,ˆMð P]ÐOdÑ ¨F¸X¿]¹]ÕKÐjnˆØˆ?Ø'*ˆN˜8Ò$à×Ñ˜cÔ"Ø×"Ñ" 8Õ,ð
-ñ Ü+0´°Y³Ó+@ÖA a�˜qÓ!ÒAÐAÜ
‡K�KÐ/°·±ÄÀJÃÔPÜ% h°
¸FÓC×C€GÙØˆÜœ3˜y›>¨>¸?ÈJÐX_Ó`Ð`øô ò 	!Ø ŠMð	!üò BàCús@   ‚$D§C6¯ADÂDÂ=DÃDÃ DÃ6DÄDÄDÄ	D)(Ú__doc__rŠ   r9   ÚloggingÚtypingr   r   r   r   Útools.tool_backend_helpersr   r   Útools.url_safetyr	   Útools.web_tools_rescuer
   r   Ú	getLoggerrŽ   rF   rw   rm   r[   r   r   r!   r&   r,   r3   r<   r>   Úintr   rN   rf   rp   rv   rz   rœ   r§   r2   r   r   ú<module>r°      sá  ðñó Û Û ß ,Ó ,ç HÝ 6ß Dà	ˆ×	Ñ	Ð,Ó	-€àDÐ Ø"Ð ØIÐ àjð ð
G˜Cð G H¨S¡Mó Gð sð ¸#ð À#ó ðZ 3ð Z°#ð Z¸#ó Zð[¨ð [°cð [¸có [ðD�sð D 8¨C¡=ð D°T¸#¸s¸(±^ó DðN˜sð N só NðL�só Lð
	-Øð	-Ø˜C ˜I‘ð	-Ø9=¸c¹ð	-ØPTÐUXÑPYð	-ØdhÐimÑdnð	-à	ˆ$�Zó	-ðC  c¡ó Cð0 só ð0
* %ó 
*ð/°$°s±)ð /ÀXÈcÁ]ð /ÐW[Ð\`ÑWaó /ðda°$°s±)ð aÀXÈcÁ]ð aÐW[Ð\`ÑWaô ar   