+
    lHàjw,  ã                   ó6  € R t ^ RIt^ RIt^ RIt^ RIHtHtHtHt ^ RI	H
t
Ht ^ RIHt ^ RIHtHt ]P"                  ! R4      tRtRtR	tR
tR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R ltR R lt R R  lt!R! R" lt"R# R$ lt#R# )%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' fieldc                óF   € V ^8„  d   QhR\         R\        \        ,          /# )é   ÚvalueÚreturn)r   r   Ústr)Úformats   "Ú</home/ubuntu/.hermes/hermes-agent/tools/web_tools_extract.pyÚ__annotate__r      s"   € ÷ Gñ GœCð G¤H¬S¥Mñ Gó    c                óæ   € \        V \        4      '       d+   V P                  R4      ;'       g    V P                  R4      p \        V \        4      '       d   V P	                  4       ;'       g    R# R# )zù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Úgetr   Ústrip)r   s   &r   Ú_web_extract_urlr      sU   € ô �%œ×ÒØ—	‘	˜%Ó ×5Ð5 E§I¡I¨fÓ$5ˆÜ&0°¼×&<Ò&<ˆE�K‰K‹M×!Ð!˜TÐFÀ$ÐFr   c                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   Ú
capabilityÚdisabled_keyr   ©r   )r   s   "r   r   r   '   s!   € ÷ ñ ¤sð ¼#ð Ä#ñ r   c           	     óR   € VP                  R^4      R,          pRV  RV RV RV R2	# )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   c                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   r   Úfallbackr   r   )r   s   "r   r   r   1   s&   € ÷ Zñ Z¤3ð Z´#ð Z¼#ñ Zr   c                óJ   € ^ RI Hp V! V R7      pV'       d   \        W4      # T# )z^Error when no provider resolved: point at a disabled bundled plugin if that is the real cause.)Ú_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Ô! *Ó;ÐYÐQYÐYr   c                ó<   € V ^8„  d   QhR\         R\         R\         /# )r   r   Úbackendr   r   )r   s   "r   r   r   8   s&   € ÷ [ñ [¬ð [´cð [¼cñ [r   c           	     óD   € RV  R2p\        V \        RRV R2V4      4      # )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_errorr2   8   s2   € ð # : ,Ð.EÐF€GÜ˜j¬/¸%À1ÀWÀIÈQÀÐQXÓ*YÓZÐZr   c                ót   € V ^8„  d   QhR\         R\        \         ,          R\        \         \        3,          /# )r   r   Úerrorr   )r   r   r   r   )r   s   "r   r   r   ?   s2   € ÷ Dñ D”sð D¤8¬C¥=ð D´T¼#¼s¸(µ^ñ Dr   c                 ó   € R V RRRRRV/# )r   ÚtitleÚ Úcontentr4   © )r   r4   s   &&r   Ú_result_entryr:   ?   s   € Ø�3˜  Y°°G¸UÐCÐCr   c                ó0   € V ^8„  d   QhR\         R\         /# )r   r4   r   r   )r   s   "r   r   r   C   s   € ÷ Nñ Nœsð N¤sñ Nr   c                 ó:   € \         P                  ! R RRV /RR7      # )ÚsuccessFr4   )Úensure_ascii©ÚjsonÚdumps©r4   s   &r   Ú_extract_error_jsonrC   C   s   € Ü�:Š:�y %¨°%Ð8ÀuÔMÐMr   c                ó$   € V ^8„  d   QhR\         /# )r   r4   r   )r   s   "r   r   r   G   s   € ÷ Lñ L”sñ Lr   c           	     ó>   € RRR\         P                  ! RRRV /4      3# )zRWhole-call refusal tuple for ``_validate_extract_urls`` (exfiltration prevention).Nr=   Fr4   r?   rB   s   &r   Ú_refuse_allrF   G   s#   € à��tœTŸZšZ¨°E¸7ÀEÐ(JÓKÐKÐKr   c                óÚ   € V ^8„  d   QhR\         R\        \         \        3,          R\        \         ,          R\        \        ,          R\        \        ,          R\        \        ,          /# )r   ÚtotalÚfixedÚfetch_positionsÚ
fetch_urlsÚresultsr   )Úintr   Úlistr   )r   s   "r   r   r   L   sX   € ÷ 	-ñ 	-Üð	-ÜœC¤˜I•ð	-Ü9=¼c½ð	-ÜPTÔUXÕPYð	-ÜdhÔimÕdnð	-ä	Œ$…Zñ	-r   c                óð   € \        V4      p\        V4       F7  w  rg\        W6,          \        4      pV\	        V4      8  d	   WF,          MTWW&   K9  	  \        V 4       U	u. uF  q•V	,          NK  	  up	# u up	i )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   Ú	enumerater:   Ú_NO_RESULT_ERRORÚlenÚrange)
rH   rI   rJ   rK   rL   ÚmergedÚposÚpositionÚmissingÚis
   &&&&&     r   Ú_merge_in_orderrY   L   sf   € ô
 �%‹[€FÜ" ?Ö3‰ˆÜ 
¥Ô1AÓBˆØ+.´°W³Ô+=˜7ž<À7ˆÓñ 4ô  % Uœ|Ó,™|˜!�1�IˆI™|Ñ,Ð,ùÒ,s   ÁA3c                ó:   € V ^8„  d   QhR\         \        ,          /# )r   Úurls)rN   r   )r   s   "r   r   r   X   s   € ÷ Cñ C¤¤c¥ñ Cr   c           
     óè  a	€ ^ RI Ho	 ^ RIHp . . / rCp\	        V 4       FÍ  w  rV\        V4      pVf$   \        R\        P                  V4      4      WE&   K7  \        V4      p\        ;QJ d-    V	3R lWq! V4      W�! V4      3 4       F  '       g   K   RM"	  RM! V	3R lWq! V4      W�! V4      3 4       4      '       d   \        R4      u # VP                  V4       VP                  V4       KÏ  	  W#VR3# )	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.)Ú
_PREFIX_RE)ÚunquoteNr7   c              3   óF   <"  € T F  pSP                  V4      x € K  	  R # 5i©N)Úsearch)Ú.0Úcr]   s   & €r   Ú	<genexpr>Ú)_validate_extract_urls.<locals>.<genexpr>f   s!   øé € ÐlÑ-k¨ˆz× Ñ  ×#Ð#Ó-kùs   ƒ!TFz_Blocked: URL contains what appears to be an API key or token. Secrets must not be sent in URLs.)Úagent.redactr]   Úurllib.parser^   rP   r   r:   Ú_INVALID_ITEM_ERRORr   r   ÚanyrF   Úappend)
r[   r^   Únormalized_urlsÚnormalized_indicesÚinvalid_urlsÚindexÚitemÚ_urlÚnormalized_urlr]   s
   &        @r   Ú_validate_extract_urlsrr   X   så   ø€ õ (Ý$à8:¸BÀ¨€OÜ  –‰ˆÜ Ó%ˆØŠ<Ü"/°Ô4G×4NÑ4NÈuÓ4UÓ"VˆLÑÙÜ2°4Ó8ˆß‹3Ôl¨d°G¸D³MÀ>ÐSZÐ[iÓSjÑ-kÓl�3�3Š3Ôl¨d°G¸D³MÀ>ÐSZÐ[iÓSjÑ-kÓl×lÒlÜð4óò ð 	×Ñ˜~Ô.Ø×!Ñ! %Ö(ñ 'ð °¸dÐBÐBr   c                ó$   € V ^8„  d   QhR\         /# )r   r-   r   )r   s   "r   r   r   p   s   € ÷ ñ ¤sñ r   c                óŠ  € ^ RI HpHp V '       d	   V! V 4      MRpVe   VP                  4       '       d   VR3# Ve&   R\	        VP
                   R2\        ,           4      3# V '       d)   \        R4      '       d   R\	        \        RV 4      4      3# V! 4       pVf%   R\        ,           pR\	        \        RV4      4      3# VR3# )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.
)Ú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*   ru   rv   Úsupports_extractrC   Údisplay_nameÚ_EXTRACT_BACKENDS_HINTr   r2   r+   )r-   ru   Ú_wsp_get_providerÚproviderr'   s   &    r   Ú_resolve_extract_providerr}   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                ó$   € V ^8„  d   QhR\         /# )r   r   )Úfloat)r   s   "r   r   r   ˆ   s   € ÷ 
*ñ 
*¤%ñ 
*r   c                 ó’   € ^ RI Hp   \        V ! 4       P                  R\        4      4      #   \
        \        3 d
    \        u # i ; i)zû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.
©Ú_load_web_configÚextract_timeout)Útools.web_toolsr‚   r   r   Ú_DEFAULT_EXTRACT_TIMEOUT_SÚ	TypeErrorÚ
ValueErrorr�   s    r   Ú_extract_timeout_secondsrˆ   ˆ   sD   € õ 1ð*ÜÑ%Ó'×+Ñ+Ð,=Ô?YÓZÓ[Ð[øÜ”zÐ"ô *Ü)Ò)ð*ús   ˆ#, ¬AÁAc                ó~   € V ^8„  d   QhR\         \        ,          R\        \        ,          R\         \        ,          /# )r   rK   r   r   ©rN   r   r   r   )r   s   "r   r   r   •   s0   € ÷ /ñ /´$´sµ)ð /ÄXÌcÅ]ð /ÔW[Ô\`ÕWañ /r   c              ƒ  ó¤  a"  € ^ RI p^ RIHp \        4       p VP	                  V P
                  4      '       d   V P                  WR7      pM"\        P                  ! V P
                  WR7      pV^ 8”  d!   \        P                  ! WeR7      G Rj  x€L
 pMVG Rj  x€L
 p V'       dz   \&        ;QJ d    R	 V 4       F  '       d   K   R
M	  RM! R	 V 4       4      '       d@   \        V 4      '       d/   \        P                  ! \         V P                  W4      G Rj  x€L
 # \)        V4      oV Fâ  pVP+                  R4      p\-        V\.        4      '       d   VP+                  R4      MRp\1        V3R lVP+                  R4      V3 4       R4      pVP+                  RR4      ;'       g    VP+                  RR4      pV'       g   Kš  V'       g   K¤  VP+                  R4      '       d   K½  V! WïVP+                  RR4      W P                  R7       Kä  	  V#  EL‡ EL  \         d¥    \        P                  RT P                  T\        T4      4       T Uu. uF   p\        TRTR RT P                   24      NK"  	  Mu upi p	p\        T 4      '       g   T	u # \        P                  ! \         T P                  Y4      G Rj  x€L 
 u # \"         dv   p
\        T 4      '       g   h T Uu. uF  p\        T\%        T
4      4      NK  	  Mu upi p	p\        P                  ! \         T P                  Y4      G Rj  x€L 
 u Rp
?
# Rp
?
ii ; i EL05i)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.
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   óB   "  € T F  qP                  R 4      x € K  	  R# 5i)r4   N)r   )rb   Úrs   & r   rd   Ú$_dispatch_extract.<locals>.<genexpr>´   s   é € Ð7©w¨!—u‘u˜W—~�~«wùs   ‚FTÚmetadataÚ	sourceURLc              3   ó8   <"  € T F  qS9   g   K  Vx € K  	  R # 5ir`   r9   )rb   ÚuÚ	requesteds   & €r   rd   r�   À   s   øé € ÐNÑ;˜!ÀI¹~—A’AÓ;ùs   ƒ�
r   Úraw_contentr7   r8   r4   r6   ©r   r|   )ÚinspectÚtools.web_result_cacherŒ   rˆ   Úiscoroutinefunctionrw   ÚasyncioÚ	to_threadÚwait_forÚTimeoutErrorÚloggerÚwarningÚnamerR   r:   r	   r
   Ú	Exceptionr   ÚallÚsetr   r   r   Únext)r|   rK   r   r˜   rŒ   r�   ÚcororL   r”   ÚfailedÚexcÚfetchedÚmetaÚsourcer   Ú_contentr•   s   &&&             @r   Ú_dispatch_extractr­   •   s”  øé € ó Ý8Ü&Ó(€Gð[Ø×&Ñ& x×'7Ñ'7×8Ò8Ø×#Ñ# JÐ#Ó>‰Dä×$Ò$ X×%5Ñ%5°zÔQˆDØ�QŒ;Ü#×,Ò,¨TÔC×C‰Gà —j‰G÷ —3“3Ñ7©wÓ7—3—3’3Ñ7©wÓ7×7Ò7Ô<LÈX×<VÒ<VÜ×&Ò&¤¸¿¹ÀzÓ[×[Ð[ô �J“€IÛˆØ�{‰{˜:Ó&ˆÜ*4°T¼4×*@Ò*@�—‘˜+Ô&ÀdˆÜÔN §¡¨EÓ 2°FÑ;ÓNÐPTÓUˆØ—;‘;˜}¨bÓ1×OÐO°W·[±[ÀÈBÓ5Oˆß‰3—8‘8 G§K¡K°×$8Ô$8Ù˜c¨W¯[©[¸À"Ó-EÈf×_lÑ_l×mñ ð €NòA Dâ øÜô [Ü�‰ÐVØ—}‘} g¬s°:«ô	@ñ &ó'Ù%�aô   Ð%=¸gÀc¸]È&ÐQY×Q^ÑQ^ÐP_Ð#`ÖaÚ%ùò'ˆð 'ä ×)Ò)ØŠMÜ×&Ò&¤¸¿¹ÀzÓZ×ZÐZÒZÜô [Ü ×)Ò)ØÙ6@ÓA±j°”- ¤3 s£8Ö,²jùÒAˆÐAÜ×&Ò&¤¸¿¹ÀzÓZ×ZÐZÕZûð	[úò \ùsð   ƒM™A7H ÂHÂH ÂMÂH ÂHÂH Â MÂ)
MÂ4MÃMÃ#:MÄMÄBMÆ,MÇ	MÇMÇ,+MÈH ÈH È:M
É&I>É=M
ÊMÊ)M
ËKËM
Ë	MËM
ËM
ËMË+L
Ì	1MÌ:L=Ì;MÌ?M
Í MÍM
Í
Mc                ó~   € V ^8„  d   QhR\         \        ,          R\        \        ,          R\         \        ,          /# )r   Ú	safe_urlsr   r   rŠ   )r   s   "r   r   r   Ç   s5   € ÷ añ a´$´sµ)ð aÄXÌcÅ]ð aÔW[Ô\`ÕWañ ar   c              ƒ  óF  "  € ^ RI Hp ^ RIHp / . . rvp\	        V4       FT  w  r‰ V! V	4      p
V
f   V! W’V P                  R7      MRpVe   WµV&   K2  VP                  V	4       VP                  V4       KV  	  V'       g+   \        \        V4      4       Uu. uF  qÅV,          NK  	  up# \        P                  RV P                  \        V4      4       \        WV4      G Rj  x€L
 pV'       g   V# \        \        V4      WWWm4      #   \
         d    Rp
 Léi ; iu upi  L<5i)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.)Úextract_cache_get)Úcheck_website_accessNr—   zWeb extract via %s: %d URL(s))r™   r±   Útools.website_policyr²   rP   r¢   r¡   rj   rS   rR   rŸ   Úinfor­   rY   )r|   r¯   r   r±   Ú_check_siteÚcached_resultsrK   rJ   rV   r   Ú_policy_blockÚhitrX   rL   s   &&&           r   Ú_extract_safe_urlsr¹   Ç   s
  é € õ 9ÝHØ24°b¸" €NÜ" 9Ö-‰ˆð	!Ù'¨Ó,ˆMð P]ÒOdÑ ¸X¿]¹]ÕKÐjnˆØŠ?Ø'*˜8Ó$à×Ñ˜cÔ"Ø×"Ñ" 8Ö,ñ .÷ Ü+0´°Y³Ô+@ÓAÑ+@ a˜q×!Ð!Ñ+@ÑAÐAÜ
‡K�KÐ/°·±ÄÀJÃÔPÜ% h¸FÓC×C€GßØˆÜœ3˜y›>¨>ÈJÓ`Ð`øô ô 	!Ø ŠMð	!üò BáCùsL   ‚"D!¥D­AD!Á?D!ÂDÂ&<D!Ã"DÃ#D!Ã/D!ÄDÄD!ÄDÄ	D!)$Ú__doc__r›   r@   ÚloggingÚtypingr   r   r   r   Útools.tool_backend_helpersr   r   Útools.url_safetyr   Útools.web_tools_rescuer	   r
   Ú	getLoggerrŸ   rQ   r…   rz   rh   r   r%   r+   r2   r:   rC   rF   rY   rr   r}   rˆ   r­   r¹   r9   r   r   Ú<module>rÁ      s™   ðñó Û Û ß ,Ó ,ç HÝ 6ß Dà	×	Ò	Ð,Ó	-€àDÐ Ø"Ð ØIÐ àjð õ
GõõZõ[õDõNõLõ
	-õCõ0õ0
*õ/÷dar   