+
    lHàjŸ&  ã                   óà   € R t ^ RIt^ RIt^ RIHtHtHt ]P                  ! R4      tRt	Rt
RRut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tR R ltR R ltR R ltR# )a�  Truncate-and-store pipeline for web_extract (no LLM).

Pages at or under the char budget are returned whole; larger pages become a head+tail window plus a
footer that says how much is shown, where the full text is stored (cache/web) and the exact read_file
call that pages the omitted middle. Inline base64 images become ``[IMAGE: alt]`` placeholders. Logs under the
origin (tools.web_tools) logger.
N)ÚAnyÚListÚOptionalztools.web_toolsi˜:  i€„ iÐ  i ¡ c                ó0   € V ^8„  d   QhR\         R\        /# ©é   ÚvalueÚreturn©r   Úint)Úformats   "Ú=/home/ubuntu/.hermes/hermes-agent/tools/web_tools_truncate.pyÚ__annotate__r      s   € ÷ Hñ HœSð H¤Sñ Hó    c                óP   € \        \        \        \        V 4      \        4      4      # )zšClamp to [2k, 500k] (below 2k the truncation footer dominates; a config typo must not blow up context);
raises TypeError/ValueError for non-numeric input.)ÚmaxÚ_CHAR_LIMIT_FLOORÚminr   Ú_CHAR_LIMIT_CEILING©r   s   &r   Ú_clamp_char_limitr      s   € ô Ô ¤#¤c¨%£jÔ2EÓ"FÓGÐGr   c                ó0   € V ^8„  d   QhR\         R\        /# r   r
   )r   s   "r   r   r   !   s   € ÷ *ñ *œSð *¤Sñ *r   c                óh   €  V f   \         # \        V 4      #   \        \        3 d
    \         u # i ; i)zV``_clamp_char_limit(value)``; ``None`` or non-numeric input falls back to the default.)ÚDEFAULT_EXTRACT_CHAR_LIMITr   Ú	TypeErrorÚ
ValueErrorr   s   &r   Ú_clamp_or_defaultr   !   s8   € ð*Ø-2ª]Ô)ÐXÔ@QÐRWÓ@XÐXøÜ”zÐ"ô *Ü)Ò)ð*ús   ‚	 Œ
 —1°1c                ó$   € V ^8„  d   QhR\         /# )r   r	   )r   )r   s   "r   r   r   )   s   € ÷ Kñ K¤ñ Kr   c                 óL   € ^ RI Hp  \        V ! 4       P                  R4      4      # )zE``web.extract_char_limit`` clamped to a sane range, else the default.©Ú_load_web_configÚextract_char_limit)Útools.web_toolsr    r   Úgetr   s    r   Ú_get_extract_char_limitr$   )   s    € å0ÜÑ-Ó/×3Ñ3Ð4HÓIÓJÐJr   c                ó0   € V ^8„  d   QhR\         R\         /# ©r   Útextr	   ©Ústr)r   s   "r   r   r   /   s   € ÷ 	Nñ 	N¬ð 	N´ñ 	Nr   c                óœ   € R R lp\         P                  ! RW4      p\         P                  ! RRV4      p\         P                  ! RRV4      # )a#  Replace inline base64 image blobs (token bombs) with ``[IMAGE: alt]`` placeholders: markdown images
(alt kept), parenthesised blobs, and bare ``data:image/...;base64,`` payloads. Real http(s) markdown
image links are left untouched so the agent can ``web_extract`` / ``vision_analyze`` them.c                óZ   € V ^8„  d   QhR\         P                  \        ,          R\        /# )r   Úmr	   )ÚreÚMatchr)   )r   s   "r   r   Ú4convert_base64_images_to_links.<locals>.__annotate__3   s&   € ÷ [ñ [”B—H‘HœS•Mð [¤cñ [r   c                 ót   € V P                  R 4      ;'       g    RP                  4       ;p'       d   RV R2# R# )ÚaltÚ z[IMAGE: Ú]ú[IMAGE])ÚgroupÚstrip)r,   r1   s   & r   Ú_md_replÚ0convert_base64_images_to_links.<locals>._md_repl3   s:   € Ø-.¯W©W°U«^×-AÐ-A¸r×,HÑ,HÓ,JÐ%J SÖ%J�˜#˜˜aÐ ÐZÐQZÐZr   zD!\[(?P<alt>[^\]]*)\]\(\s*data:image/[^;]+;base64,[A-Za-z0-9+/=\s]+\)z0\(\s*data:image/[^;]+;base64,[A-Za-z0-9+/=\s]+\)r4   z'data:image/[^;]+;base64,[A-Za-z0-9+/=]+)r-   Úsub)r'   r7   Úouts   &  r   Úconvert_base64_images_to_linksr;   /   sD   € õ[ô �&Š&ÐXÐZbÓ
i€CÜ
�&Š&ÐDÀiÐQTÓ
U€CÜ�6Š6Ð<¸iÈÓMÐMr   c                óR   € V ^8„  d   QhR\         R\         R\        \         ,          /# )r   ÚurlÚcontentr	   )r)   r   )r   s   "r   r   r   ;   s%   € ÷ ñ œ#ð ¬ð ´¼µñ r   c                óô  €  ^ RI p^ RIHp ^ RIHp V! RR4      pVP                  RRR7       WT! V 4       RVP                  V P                  R	4      4      P                  4       R
,           R2,          p\        V4      \        8”  d'   VR\         R\        R R\        V4      R R2,           p^ RIHp V! WaRRR7       \        V4      #   \         d"   p\        P!                  RY4        Rp?R# Rp?ii ; i)zúWrite the full page to cache/web; absolute path or None (best-effort: the truncated content is still
returned). cache/web is mounted read-only into remote backends (credential_files _CACHE_DIRS) so
read_file can page the complete text on any backend.N)Úget_hermes_dir)Ú
_host_slugz	cache/webÚ	web_cacheT)ÚparentsÚexist_okÚ-zutf-8:Né
   Nz.mdz 

[... stored copy truncated at Ú,z
 chars of z2; re-extract a more specific URL for the rest ...])Úwrite_text_exclusiveF)ÚprivateÚ	overwritez0Failed to store full web_extract text for %s: %s)ÚhashlibÚhermes_constantsr@   Útools.web_result_cacherA   ÚmkdirÚsha256ÚencodeÚ	hexdigestÚlenÚMAX_STORED_TEXT_CHARSÚtools.spill_safetyrH   r)   Ú	ExceptionÚloggerÚdebug)	r=   r>   rK   r@   rA   Ú	cache_dirÚpathrH   Úexcs	   &&       r   Ú_store_full_textr[   ;   sð   € ðÛÝ3Ý5Ù" ;°Ó<ˆ	Ø�‰ ¨tˆÔ4Ø˜j¨›oÐ.¨a°·±¸s¿z¹zÈ'Ó?RÓ0S×0]Ñ0]Ó0_Ð`cÕ0dÐ/eÐehÐiÕiˆÜˆw‹<Ô/Ô/ØÐ4Ô4Ð5Ø4Ô5JÈ1Ð4Mð NÜ˜'“l 1Ð%Ð%WðYõˆGõ 	<ñ 	˜T°EÀTÕJÜ�4‹yÐøÜô Ü�‰ÐGÈÔRÝûðús   ‚CC ÃC7ÃC2Ã2C7c          
      ój   € V ^8„  d   QhR\         R\         R\        R\        \         \        3,          /# )r   r>   r=   Ú
char_limitr	   )r)   r   ÚtupleÚbool)r   s   "r   r   r   V   s2   € ÷ *=ñ *=¤3ð *=¬Sð *=¼cð *=ÄeÌCÔQUÈIÕFVñ *=r   c           
     óÎ  € \        V 4      V8:  d   V R3# \        VR,          4      pW#,
          pV RV W) R reVP                  R4      ;qsR,          8”  d   VRV p^ VP                  R4      ;pu;8:  d   VR,          8  d   M MWg^,           R p\	        W4      pV'       d   ^ RIHp	 V	! V4      pRRR\        V4      R	 R
\        V4      R	 R\        V 4      R	 R2.p
V'       d/   VP                  R4      ^,           pV
RV 2RV RV R2.,          p
MV
P                  R4       V
P                  R4       VR,           V,           pVR,           RP                  V
4      ,           R3# )a  Return (model_text, was_truncated). Pages over ``char_limit`` become a ~75% head / ~25% tail window cut
on line boundaries, plus a footer saying how much is shown, where the full text is stored, and the
read_file call that pages the omitted middle. Deterministic.Fg      è?NÚ
g      à?)Úto_agent_visible_cache_pathr2   zShowing rG   z chars (head) + z chars (tail) of z total clean characters.zFull text saved to: z,To read the omitted middle: read_file path="z	" offset=zS limit=200  (the file is the complete page; raise/lower offset to page through it).zwFull text could not be stored; re-run web_extract on a more specific URL or use browser_navigate for the complete page.u+   

[... middle omitted â€” see footer ...]

Tu=   â”€â”€â”€â”€â”€â”€â”€â”€ [TRUNCATED] â”€â”€â”€â”€â”€â”€â”€â”€uW   â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€)
rR   r   ÚrfindÚfindr[   Útools.credential_filesrb   ÚcountÚappendÚjoin)r>   r=   r]   Úhead_budgetÚtail_budgetÚheadÚtailÚnlÚstored_pathrb   Úfooter_linesÚmiddle_start_lineÚ
model_texts   &&&          r   Ú_truncate_with_footerrr   V   s–  € ô ˆ7ƒ|�zÔ!Ø˜ˆ~ÐÜ�j 4Õ'Ó(€KØÕ*€KØ˜˜+Ð&¨°°Ð(>ˆ$à�j‰j˜ÓÐˆ°Õ"3Ô3Ø�C�RˆyˆØ�4—9‘9˜T“?Ð"ˆRÖ7 k°CÕ&7×7Ø˜•F�Gˆ}ˆä" 3Ó0€Kßõ 	GÙ1°+Ó>ˆà
Ð3Ø
”3�t“9˜Q�-Ð/´°D³	¸!¨}ð =Ü�'‹l˜1ÐÐ5ð	7ð€L÷
 à ŸJ™J tÓ,¨qÕ0ÐØØ" ; -Ð0Ø:¸;¸-ð HØ'Ð(ð )6ð7ð
õ 	
‰ð 	×ÑðJô	
ð ×Ñ˜
Ô#ØÐIÕIÈDÕP€JØ˜Õ˜tŸy™y¨Ó6Õ6¸Ð<Ð<r   c                óF   € V ^8„  d   QhR\         \        ,          R\        /# )r   r]   r	   )r   r   )r   s   "r   r   r   ƒ   s"   € ÷ bñ b¤h¬s¥mð b¼ñ br   c                ó4   € V e   \        V 4      # \        4       # )z[Caller's ``char_limit`` (else config) clamped; non-numeric input falls back to the default.)r   r$   )r]   s   &r   Ú_effective_char_limitru   ƒ   s   € à,6Ò,BÔ˜ZÓ(ÐaÔH_ÓHaÐar   c                ó0   € V ^8„  d   QhR\         R\         /# r&   r(   )r   s   "r   r   r   ‹   s   € ÷ ñ œsð ¤sñ r   c                ó"  € ^ RI Hp V R,          P                  RR4      P                  RR4      pV FY  w  r4VP                  RR4      pV'       g   K!  VP	                  \
        4      '       g   K>  VP	                  V4      '       g   KW  Vu # 	  R# )a  Magic-byte type name when a fetched body is a raw binary file, else ``""``. Backends return
the body as text with NUL bytes dropped, so signatures are compared NUL-stripped on both sides.
Only the file tools' own signature table; HTML/markdown/JSON never start with one.)Ú_MAGIC_SIGNATURES:Né    Nzlatin-1Úignoreó    r   r2   )Útools.file_operationsrx   rP   ÚreplaceÚ
startswithÚ_UNAMBIGUOUS_BINARY_KINDS)r'   rx   rk   ÚprefixÚnameÚsigs   &     r   Ú_binary_payload_kindrƒ   ‹   ss   € õ 8à��9×Ñ˜I xÓ0×8Ñ8¸À#ÓF€DÛ)‰ˆØ�n‰n˜W cÓ*ˆß‰3�4—?‘?Ô#<×=Ô=À$Ç/Á/ÐRU×BVÔBVØŠKñ *ñ r   c                óV   € V ^8„  d   QhR\         \        ,          R\        R\        RR/# )r   Úresultsr]   Údebug_call_datar	   N)ÚlistÚdictr   )r   s   "r   r   r   ™   s3   € ÷ Añ Aœt¤D�zð A´sð AÌTð AÐVZñ Ar   c                óÎ  € V  EF]  pVP                  RR4      pVP                  RR4      ;'       g    VP                  RR4      pVP                  R4      '       g	   V'       g   Kc  \        V4      pV'       d1   RVR&   RV R2VR&   \        P                  RWF\	        V4      4       K¦  \        V4      p\        WtV4      w  r‰WƒR&   V	'       ds   VR	;;,          ^,          uu&   VR
,          P                  RVR\	        V4      R\	        V4      /4       \        P                  RV\	        V4      \	        V4      4       EK=  \        P                  RV\	        V4      4       EK`  	  R# )z’In place: replace each successful entry's content with its base64-cleaned, budgeted text;
per-page truncation metrics go into ``debug_call_data``.r=   r2   Úraw_contentr>   ÚerrorzURL returned binary content (z�), not a page. Download it with the terminal (curl -L -o) and use read_file (SQLite/Office/PDF auto-extract) or terminal utilities on the file.z)%s (binary payload: %s, %d chars dropped)Úpages_truncatedÚtruncation_metricsÚoriginal_sizeÚ	sent_sizez%s (truncated %d -> %d chars)z%s (%d chars, whole)N)r#   rƒ   rV   ÚinforR   r;   rr   rg   )
r…   r]   r†   Úresultr=   rŠ   Úbinary_kindÚcleanrq   Ú	truncateds
   &&&       r   Ú_truncate_resultsr•   ™   s:  € ô ˆØ�j‰j˜ Ó#ˆØ—j‘j °Ó3×PÐP°v·z±zÀ)ÈRÓ7PˆØ�:‰:�g×Ò§kÙÜ*¨;Ó7ˆßð !#ˆF�9Ñà/°¨}ð =uð uð �7‰Oô �K‰KÐCÀSÔWZÐ[fÓWgÔhÙÜ.¨{Ó;ˆÜ 5°eÀ*Ó MÑˆ
Ø&ˆyÑßØÐ-×.°!Õ3Ó.ØÐ0Õ1×8Ñ8Ø˜˜_¬c°%«j¸+ÄsÈ:ÃÐWôô �K‰KÐ7¸¼cÀ%»jÌ#ÈjË/×Zä�K‰KÐ.°´S¸³Z×@ó3 r   c                ó\   € V ^8„  d   QhR\         \        ,          R\         \        ,          /# )r   r…   r	   )r‡   rˆ   )r   s   "r   r   r   ¸   s"   € ÷ 	ñ 	œ4¤�:ð 	¬$¬t­*ñ 	r   c                óæ   € V  Uu. uF`  pRVP                  RR4      RVP                  RR4      RVP                  RR4      RVP                  R4      /RV9   d   RVR,          /M/ CNKb  	  up# u upi )zOKeep only url/title/content/error per entry (+ blocked_by_policy when present).r=   r2   Útitler>   r‹   Úblocked_by_policy)r#   )r…   Úrs   & r   Ú_trim_resultsr›   ¸   s�   € ñ óñ ˆAð	 �1—5‘5˜ Ó# W¨a¯e©e°G¸RÓ.@À)ÈQÏUÉUÐS\Ð^`ÓMaØ�Q—U‘U˜7“^ð	
ð ATÐWXÔ@XÐ# QÐ':Õ%;Ñ<Ð^`ó	
ñ
 ñð ùò s   …A&A.)ÚSQLiteÚZIPÚgzipÚbzip2Úxzz7-ZipÚELFzMach-OÚPNGÚJPEGÚGIFÚTIFFÚFLACÚOgg)Ú__doc__Úloggingr-   Útypingr   r   r   Ú	getLoggerrV   r   rS   r   r   r   r   r$   r;   r[   rr   ru   r   rƒ   r•   r›   © r   r   Ú<module>r­      sˆ   ðñó Û 	ß &Ñ &à	×	Ò	Ð,Ó	-€ð #Ð ð "Ð à)-¨wÐ &Ð Ð&õHõ*õKõ	Nõõ6*=õZbð
 LÐ õõA÷>	r   