Ë
    ãß²j€  ã                  ó¤  — U d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	m
Z
 ddlmZ ddlmZmZmZ ddlmZ dZd	Zd
ZdZdZi Zded<    ej4                  «       Zd d!d„Zd"d„Zd"d„Zd#d„Zd$d„Z d%d„Z!d&d„Z"d'd„Z#d'd„Z$ ejJ                  dejL                  «      Z' e
d e(«       ¬«      Z)ded<   d(d„Z*ejV                  d)d„«       Z,d*d„Z-y)+aò  History-reuse budgets for native vision embeds (config section ``vision``).

A native ``vision_analyze`` result bakes the image into conversation history, where it is
re-sent on every later API call. Two knobs bound that cost: ``vision.embed_target_bytes``
(how large one embed may be) and ``vision.max_calls_per_image`` (how often the same image
may be embedded per session). See #112095: a delegated subagent re-loaded five screenshots
158 times in 15 minutes because nothing refused the repeat.
é    )ÚannotationsN)Ú
ContextVar)ÚPath)ÚAnyÚIteratorÚOptional)Ú
tool_errori   i   i  @ é   i   zdict[tuple[str, str], int]Ú_repeat_countsc                óX   — 	 ddl m}m}  | |«       d| |¬«      S # t        $ r |cY S w xY w)zJ``vision.<key>`` from config.yaml; ``default`` when config is unavailable.r   )Úcfg_getÚload_configÚvision©Údefault)Úhermes_cli.configr   r   Ú	Exception)Úkeyr   r   r   s       úF/home/ubuntu/.hermes/hermes-agent/tools/vision_tools_history_budget.pyÚ_cfg_visionr   &   s1   € ðß:Ù‘{“} h°¸WÔEÐEøÜò ØŠðús   ‚ ›)¨)c                 óî   — t        dt        ¬«      } 	 t        | t        «      rt	        d«      ‚t        | «      }t        t        |t        «      t        «      S # t        t        t        f$ r	 t        cY S w xY w)zZ``vision.embed_target_bytes`` clamped to 64 KiB..4 MiB; the 256 KB default on a bad value.Úembed_target_bytesr   zboolean is not a byte budget)r   Ú_DEFAULT_EMBED_TARGET_BYTESÚ
isinstanceÚboolÚ
ValueErrorÚintÚ	TypeErrorÚOverflowErrorÚminÚmaxÚ_MIN_EMBED_TARGET_BYTESÚ_MAX_EMBED_TARGET_BYTES)ÚrawÚtargets     r   Úresolve_embed_target_bytesr&   /   sj   € ä
Ð*Ô4OÔ
P€Cð+Ü�cœ4Ô ÜÐ;Ó<Ð<Ü�S“ˆô Œs�6Ô2Ó3Ô5LÓMÐMøô ”z¤=Ð1ò +Ü*Ò*ð+ús   “&A ÁA4Á3A4c                 óÆ   — t        d«      } 	 | �*| dk7  r%t        | t        «      st        t	        | «      d«      S ddlm}  |«       rt        S dS # t
        t        f$ r Y Œ&w xY w)uö   Per-image embed cap for this session; 0 = unlimited.

    ``vision.max_calls_per_image`` unset (or unparseable) â†’ ``_SUBAGENT_REPEAT_CAP`` inside a
    delegated subagent, unlimited for the main agent. An explicit value applies everywhere.
    Úmax_calls_per_imageÚ r   )Ú"is_delegated_child_process_context)
r   r   r   r!   r   r   r   Úagent.delegation_contextr*   Ú_SUBAGENT_REPEAT_CAP)r$   r*   s     r   Úresolve_repeat_capr-   ;   si   € ô Ð+Ó
,€CðØˆ?˜s bšy´¸CÄÔ1FÜ”s˜3“x Ó#Ð#õ LÙ#EÔ#GÔÐNÈQÐNøô ”zÐ"ò Ùðús   �+A ÁA ÁA c                ót  — | j                  d«      r9dt        j                  | j                  dd«      «      j	                  «       dd z   S | j                  dd«      d   j                  d	«      }d
|v rd|z   S dt        t        t        j                  j                  |«      «      j                  «       «      z   S )u  Stable identity for an image source: local paths resolve (symlinks, ``~``, ``file://``),
    URLs drop their fragment, data URLs hash. Region crops are NOT part of the key â€” the incident
    loop alternated full loads and crops of the same files, and both re-embed the image.zdata:zutf-8ÚignoreNé    ú#é   r   zfile://z://zurl:zfile:)Ú
startswithÚhashlibÚsha256ÚencodeÚ	hexdigestÚsplitÚremoveprefixÚstrr   ÚosÚpathÚ
expanduserÚresolve)Ú	image_urlÚstrippeds     r   Ú
_image_keyrA   K   s¥   € ð ×Ñ˜GÔ$ØœŸ™¨	×(8Ñ(8¸À(Ó(KÓL×VÑVÓXÐY\ÐZ\Ð]Ñ]Ð]Ø�‰˜s AÓ& qÑ)×6Ñ6°yÓA€HØ�ÑØ˜Ñ Ð Ø”SœœbŸg™g×0Ñ0°Ó:Ó;×CÑCÓEÓFÑFÐFó    c                ó6   — ddl m}  |dd«      t        | «      fS )Nr   )Úget_session_envÚHERMES_SESSION_IDr)   )Úgateway.session_contextrD   rA   )r?   rD   s     r   Ú
_count_keyrG   W   s   € Ý7ÙÐ.°Ó3´ZÀ	Ó5JÐJÐJrB   c                óø   — t        «       }|dk  ryt        | «      }t        5  t        j	                  |d«      }||k  rt        |«       	 ddd«       y	 ddd«       t        d› d|› d�d¬«      S # 1 sw Y   ŒxY w)a|  Reserve one native embed of ``image_url`` for the current session; tool-error JSON when the
    per-session cap is already spent, else ``None``. Check and count are ONE lock section: a
    parallel tool batch on the same image (the incident's 4 concurrent calls) must not all pass a
    check taken before any of them recorded. Callers ``release_embed`` when the embed then fails.r   NzHvision_analyze refused: this image has already been loaded into context zß time(s) in this session (region crops of the same file count too), and every native load re-sends the full image on each later API call. Answer from what you can already see, or ask the user. (vision.max_calls_per_image = z; 0 = unlimited)F)Úsuccess)r-   rG   Ú_repeat_lockr   ÚgetÚ_record_embed_lockedr	   )r?   Úcapr   Úcounts       r   Úrepeat_refusalrO   \   sœ   € ô
 Ó
€CØ
ˆa‚xØÜ
�YÓ
€CÜ	ñ Ü×"Ñ" 3¨Ó*ˆØ�3Š;Ü  Ô%Ø÷	ð à÷ô
 Ø
RÐSXÐRYð Z/ð 03¨eÐ3Cð	Eð ôð ÷ð ús   ¢(A0Á0A9c                óö   — t         j                  | d«      dz   t         | <   t        t         «      t        kD  rCt         j	                  t        t        t         «      «      «       t        t         «      t        kD  rŒBy y )Nr   r2   )r   rK   ÚlenÚ_REPEAT_COUNTS_MAX_KEYSÚpopÚnextÚiter)r   s    r   rL   rL   s   sT   € Ü(×,Ñ,¨S°!Ó4°qÑ8„N�3Ñä
ŒnÓ
Ô 7Ò
7Ü×Ñœ4¤¤^Ó 4Ó5Ô6ô ŒnÓ
Ô 7Õ
7rB   c                ób   — t         5  t        t        | «      «       ddd«       y# 1 sw Y   yxY w)u±   Count one successful native embed of ``image_url`` for the current session (uncapped
    sessions only â€” capped ones are counted by the reservation in :func:`repeat_refusal`).N)rJ   rL   rG   )r?   s    r   Úrecord_embedrW   z   s)   € ô 
ñ 4ÜœZ¨	Ó2Ô3÷4÷ 4ñ 4ús   ‡%¥.c                óÌ   — t        | «      }t        5  t        j                  |d«      }|dkD  r|dz
  t        |<   nt        j	                  |d«       ddd«       y# 1 sw Y   yxY w)zRGive back a slot reserved by :func:`repeat_refusal` when the embed did not happen.r   r2   N)rG   rJ   r   rK   rS   )r?   r   rN   s      r   Úrelease_embedrY   �   s[   € ä
�YÓ
€CÜ	ñ *Ü×"Ñ" 3¨Ó*ˆØ�1Š9Ø"'¨!¡)ŒN˜3Òä×Ñ˜s DÔ)÷*÷ *ñ *ús   ’?AÁA#z&^\[Image attached(?: at)?: (.+?)\]\s*$Úvision_native_turn_imagesr   zContextVar[frozenset[str]]Ú_native_turn_imagesc                óÔ   — t        | t        «      rt        d„ | D «       «      s
t        «       S dj	                  d„ | D «       «      }t        d„ t
        j                  |«      D «       «      S )Nc              3  óh   K  — | ]*  }t        |t        «      xr |j                  d «      dk(  –— Œ, y­w)Útyper?   N©r   ÚdictrK   ©Ú.0Úps     r   ú	<genexpr>z$_native_turn_keys.<locals>.<genexpr>–   s1   è ø€ ò 5ØABŒ
�1”dÓÒ< §¡ f£°Ñ <Ó<ñ5ùs   ‚02Ú
c              3  óŒ   K  — | ]<  }t        |t        «      sŒ|j                  d «      dk(  sŒ)|j                  dd«      –— Œ> y­w)r^   Útextr)   Nr_   ra   s     r   rd   z$_native_turn_keys.<locals>.<genexpr>š   s9   è ø€ Òp¨1Ä
È1ÌdÕ@SÐXY×X]ÑX]Ð^dÓXeÐioÓXo�Q—U‘U˜6 2×&Ñpùs   ‚A˜A­Ac              3  óp   K  — | ].  }|j                  «       sŒt        |j                  «       «      –— Œ0 y ­w©N)ÚstriprA   )rb   Úms     r   rd   z$_native_turn_keys.<locals>.<genexpr>›   s'   è ø€ Òa¨qÐWX×W^ÑW^ÕW`”Z §¡£	×*Ñaùs   ‚6˜6)r   ÚlistÚanyÚ	frozensetÚjoinÚ_NATIVE_HANDLE_REÚfindall)Úuser_messagerg   s     r   Ú_native_turn_keysrs   •   s[   € Ü�l¤DÔ)´ñ 5ØFRô5ô 2ô ‹{ÐØ�9‰9Ñp°ÔpÓp€DÜÑaÔ4E×4MÑ4MÈdÓ4SÔaÓaÐarB   c              #  ó°   K  — t         j                  t        | «      «      }	 d–— t         j                  |«       y# t         j                  |«       w xY w­w)zKScope the images natively attached to ``user_message`` to the running turn.N)r[   Úsetrs   Úreset)rr   Útokens     r   Únative_turn_imagesrx   ž   sC   è ø€ ô  ×#Ñ#Ô$5°lÓ$CÓD€Eð)Ûä×!Ñ! %Õ(øÔ×!Ñ! %Õ(üs   ‚A¢< ¦A¼AÁAc                óx   — |€t        | «      t        j                  «       vryt        j                  ddddœ«      S )a  Text tool result when ``image_url`` already rides the active user turn natively, else
    ``None``. A native re-embed would put the identical pixels into the same request twice
    (the Telegram case in #76411); a ``region`` crop still embeds because it returns new detail.NTu¦   This image is already attached natively to the current user message â€” you can see it now. Answer with your built-in vision; pass a `region` to zoom into part of it.)rI   Úalready_in_contextÚmessage)rA   r[   rK   ÚjsonÚdumps)r?   Úregions     r   Únative_turn_duplicater   ¨   sE   € ð ÐœZ¨	Ó2Ô:M×:QÑ:QÓ:SÑSØÜ�:‰:ØØ"ð^ñ	ó ð rB   ri   )r   r:   )Úreturnr   )r?   r:   r€   r:   )r?   r:   r€   útuple[str, str])r?   r:   r€   úOptional[str])r   r�   r€   ÚNone)r?   r:   r€   rƒ   )rr   r   r€   zfrozenset[str])rr   r   r€   zIterator[None])r?   r:   r~   zOptional[list]r€   r‚   ).Ú__doc__Ú
__future__r   Ú
contextlibr4   r|   r;   ÚreÚ	threadingÚcontextvarsr   Úpathlibr   Útypingr   r   r   Útools.registryr	   r   r"   r#   r,   rR   r   Ú__annotations__ÚLockrJ   r   r&   r-   rA   rG   rO   rL   rW   rY   ÚcompileÚ	MULTILINErp   rn   r[   rs   Úcontextmanagerrx   r   © rB   r   ú<module>r“      só   ðòõ #ã Û Û Û 	Û 	Û Ý "Ý ß *Ñ *å %ð )Ð Ø#Ð Ø)Ð ð Ð ØÐ à-/€Ð*Ó /Øˆy�~‰~Ó€ôó	NóOó 	GóKó
ó.7ó4ó*ð  �B—J‘JÐHÈ"Ï,É,ÓWÐ Ù2<Ð=XÑbkÓbmÔ2nÐ Ð/Ó nóbð ×Ñò)ó ð)ôrB   