Ë
    áß²jX  ã                  óÄ   — d Z ddlmZ ddlZddlZddlZddlmZmZm	Z	m
Z
  ej                  e«      ZdZdZdd„Zdd„Zdd	„Zd
dœdd„Zdd„Zdd„Zddœdd„Zdd„Zdd„Zy)uc  Usage-anchored token accounting: the provider's real ``usage.prompt_tokens`` is the only
authoritative context size; the local ``bytes/4`` estimate covers ONLY messages appended since.

An anchor = provider usage at capture + a snapshot of the transcript position it priced:
``base_count`` (len(messages) at capture; the reply is not yet appended and is covered by
``completion_tokens``, so the delta walk skips an assistant row at that index), ``base_last_role``
and ``base_last_fp`` (content fingerprint of the last priced message; compaction, splices and
rewinds replace it â†’ anchor fails closed â†’ full estimation until the next real reading).

The fingerprint (not ``id()``) is the identity: the gateway re-reads the transcript from the DB
every turn and a resumed session runs in a fresh process, so object identity is never stable
across the surfaces where the estimate mattered most (#99421, #104462). The anchor also persists
on the session row (``model_config._usage_anchor``) so a restarted process can restore it; a
restored anchor is honored only while the durable transcript still matches its fingerprint.
é    )ÚannotationsN)ÚAnyÚDictÚListÚOptionalÚ_usage_anchor)ÚroleÚcontentÚapi_contentÚtool_call_idÚ
tool_callsc                óª  — t        | t        «      syt        D �ci c]&  }| j                  |«      €Œ|| j                  |«      “Œ( }}	 t	        j
                  |dt        dd¬«      }t        j                  |j                  dd«      «      j                  «       S c c}w # t        t        f$ r% t        t        |j                  «       «      «      }Y Œkw xY w)zTStable digest of one transcript message over its provider-visible, persisted fields.NT)ú,ú:)Ú	sort_keysÚdefaultÚensure_asciiÚ
separatorszutf-8Úreplace)Ú
isinstanceÚdictÚ_FINGERPRINT_KEYSÚgetÚjsonÚdumpsÚstrÚ	TypeErrorÚ
ValueErrorÚreprÚsortedÚitemsÚhashlibÚsha256ÚencodeÚ	hexdigest)ÚmsgÚkÚpayloadÚraws       ú7/home/ubuntu/.hermes/hermes-agent/agent/usage_anchor.pyÚmessage_fingerprintr+   "   s­   € ä�cœ4Ô ØÜ&7ÖR ¸3¿7¹7À1»:Ñ;Qˆq�#—'‘'˜!“*‰}ÐR€GÐRð,Ü�j‰j˜¨D¼#ÈDÐ]gÔhˆô �>‰>˜#Ÿ*™* W¨iÓ8Ó9×CÑCÓEÐEùò Søô ”zÐ"ò ,Ü”6˜'Ÿ-™-›/Ó*Ó+Šð,ús   šB±BÁB Â1CÃCc                ó.  — 	 t        | xs d«      }t        |xs d«      }|dk  st        |t        «      r|sy|d   }|t        d|«      t        |«      t        |t        «      r|j                  d«      ndt        |«      dœS # t        t        f$ r Y yw xY w)zRBuild a usage anchor from provider-reported usage, or None when usage is unusable.r   Néÿÿÿÿr	   ©Úprompt_tokensÚcompletion_tokensÚ
base_countÚbase_last_roleÚbase_last_fp)
Úintr   r   r   ÚlistÚmaxÚlenr   r   r+   )r/   r0   ÚmessagesÚptÚctÚlasts         r*   Úcapture_usage_anchorr<   .   s›   € ðÜ�Ò# !Ó$ˆÜÐ"Ò' aÓ(ˆð 
ˆQ‚w”j ¬4Ô0¹ØØ�B‰<€DàÜ   B›ZÜ˜(“mÜ.8¸¼tÔ.D˜$Ÿ(™( 6Ô*È$Ü+¨DÓ1ñð øô ”zÐ"ò Ùðús   ‚B ÂBÂBc                ó†  — 	 t        |j                  d«      xs d«      }|dk  st	        | «      |k  ry| |dz
     }t        |t        «      r#|j                  d«      |j                  d«      k7  ry|j                  d«      }t        |t        «      xr t        |«      xr t        |«      |k(  S # t        t        f$ r Y yw xY w)Nr1   r   Fé   r	   r2   r3   )
r4   r   r   r   r7   r   r   r   Úboolr+   )r8   Úanchorr1   Úbase_msgÚfps        r*   Ú_anchor_matchesrC   A   s¹   € ðÜ˜Ÿ™ LÓ1Ò6°QÓ7ˆ
ð �Q‚œ#˜h›-¨*Ò4ØØ˜
 Q™Ñ'€HÜ�h¤Ô%¨¯©°fÓ)=ÀÇÁÐL\ÓA]Ò)]ØØ	�‰�NÓ	#€BÜ�bœ#ÓÒS¤4¨£8ÒSÔ0CÀHÓ0MÐQSÑ0SÐSøô ”zÐ"ò Ùðús   ‚B. Â.C Â?C T©Úcharge_stale_thinkingc               óh  — t        |t        «      rt        | t        «      rt        | |«      syddlm} t        |d   «      t        |j                  d«      xs d«      z   }| t        |d   «      d }|r/t        |d   t        «      r|d   j                  d«      dk(  r|d	d }|r| |||¬
«      z  }|S )a  Anchored prompt+completion tokens plus a rough estimate of ONLY the messages appended since;
    None when the anchor is missing or stale. The anchored response's own reply is skipped (already
    in completion_tokens). ``charge_stale_thinking`` is forwarded to the delta estimate.Nr   )Úestimate_messages_tokens_roughr/   r0   r1   r	   Ú	assistantr>   rD   )r   r   r5   rC   Úagent.model_metadatarG   r4   r   )r8   r@   rE   rG   ÚtotalÚdeltas         r*   Úanchored_context_tokensrL   O   s²   € ô �fœdÔ#¬:°hÄÔ+EÌ_Ð]eÐgmÔMnØÝCä��Ñ'Ó(¬3¨v¯z©zÐ:MÓ/NÒ/SÐRSÓ+TÑT€EØ”S˜ Ñ-Ó.Ð/Ð0€EÙ”˜E !™H¤dÔ+°°a±·±¸VÓ0DÈÒ0SØ�a�b�	ˆÙØÑ/°ÐMbÔcÑcˆØ€Ló    c                ó<  ‡ — t        ‰ t        «      sy 	 ˆ fd„dD «       \  }}}‰ j	                  d«      ‰ j	                  d«      }}|dk  s|dk  st        |t
        «      r|sy |t        d|«      |t        |t
        «      r||dœS d |dœS # t        t        f$ r Y y w xY w)Nc              3  óZ   •K  — | ]"  }t        ‰j                  |«      xs d «      –— Œ$ y­w)r   N)r4   r   )Ú.0r'   r@   s     €r*   ú	<genexpr>z_serialize.<locals>.<genexpr>d   s#   øè ø€ Òt¸!œc &§*¡*¨Q£-Ò"4°1×5Ñtùs   ƒ(+)r/   r0   r1   r3   r2   r   r.   )r   r   r   r   r   r   r6   )r@   r9   r:   r1   rB   r	   s   `     r*   Ú
_serializerR   `   s´   ø€ Ü�fœdÔ#ØðÛtÐ?sÔtÑˆˆB�
ð �z‰z˜.Ó)¨6¯:©:Ð6FÓ+Gˆ€BØ	ˆQ‚w�* ’/¬°B¼Ô)<ÁBØØ´c¸!¸R³jÐPZÜ&0°´sÔ&;˜dÐWYñ[ð [ØAEÐWYñ[ð [øô ”zÐ"ò Ùðús   ”B	 Â	BÂBc                ó  — t        | dd«      ryt        | dd«      }t        t        | dd«      dd«      }|rt        |«      sy	  ||t        t        |«      i«       y# t        $ r t
        j                  dd¬	«       Y yw xY w)
zaWrite (or clear, ``None``) the session row's anchor blob. Best-effort: the row may not exist yet.Ú_persist_disabledFNÚ
session_idÚ_session_dbÚpatch_session_model_configzusage anchor persist failedT©Úexc_info)ÚgetattrÚcallableÚUSAGE_ANCHOR_MODEL_CONFIG_KEYrR   Ú	ExceptionÚloggerÚdebug)Úagentr@   rU   Úpatchers       r*   Úpersist_usage_anchorrb   n   sƒ   € äˆuÐ)¨5Ô1ØÜ˜ ¨dÓ3€JÜ”g˜e ]°DÓ9Ð;WÐY]Ó^€GÙœX gÔ.ØðCÙ�
Ô:¼JÀvÓ<NÐOÕPøÜò CÜ�‰Ð2¸TˆÖBðCús   ÁA Á A?Á>A?F)Ú	turn_basec               ó@   — || _         |s|€|| _        t        | |«       y)zSInstall ``anchor`` on the agent (``None`` clears) and mirror it to the session row.N)r   Ú_turn_base_usage_anchorrb   )r`   r@   rc   s      r*   Úset_usage_anchorrf   |   s#   € à €EÔÙ�F�NØ(.ˆÔ%Ü˜ Õ'rM   c                ó‚  — t        | dd«      €t        | dd«      ryt        | dd«      }t        t        | dd«      dd«      }|rt        |«      rt        |t        «      sy	 t	         ||t
        d«      «      }|€yt        ||«      r|| _
        yt        | d«       y# t        $ r t        j                  dd	¬
«       Y yw xY w)z¸On a resumed session, adopt the persisted anchor when ``conversation_history`` still carries
    the priced prefix; otherwise clear the stale blob so it can never suppress compression.r   NrT   FrU   rV   Úget_session_model_config_valueúusage anchor load failedTrX   )rZ   r[   r   r5   rR   r\   r]   r^   r_   rC   r   rb   )r`   Úconversation_historyrU   Úgetterr@   s        r*   Úrestore_usage_anchorrl   „   sÇ   € ô ˆu�o tÓ,Ð8¼GÀEÐK^Ð`eÔ<fØÜ˜ ¨dÓ3€JÜ”W˜U M°4Ó8Ð:ZÐ\`Óa€FÙœX fÔ-´ZÐ@TÔVZÔ5[ØðÜ™F :Ô/LÈdÓSÓTˆð €~ØÜÐ+¨VÔ4Ø$ˆÕä˜U DÕ)øô ò Ü�‰Ð/¸$ˆÔ?Ùðús   Á B Â B>Â=B>c                óô   — t        | dd«      }|rt        |«      rt        |t        «      sy	 t	         ||t
        d«      «      }|rt        ||«      S dS # t        $ r t        j                  dd¬«       Y yw xY w)z³Anchored token figure from the session row's persisted anchor, for callers without a live
    agent (gateway hygiene); None when absent, unreadable, or stale against ``messages``.rh   Nri   TrX   )
rZ   r[   r   r5   rR   r\   r]   r^   r_   rL   )Ú
session_dbrU   r8   rk   r@   s        r*   Úpersisted_anchor_tokensro   š   s~   € ô �ZÐ!AÀ4ÓH€FÙœX fÔ-´ZÀÌ$Ô5OØðÜ™F :Ô/LÈdÓSÓTˆñ 9?Ô" 8¨VÓ4ÐHÀDÐHøô ò Ü�‰Ð/¸$ˆÔ?Ùðús   ­A Á A7Á6A7)r&   r   ÚreturnzOptional[str])r/   r   r0   r   r8   úList[Dict[str, Any]]rp   úOptional[Dict[str, Any]])r8   rq   r@   zDict[str, Any]rp   r?   )r8   rq   r@   rr   rE   r?   rp   úOptional[int])r@   r   rp   rr   )r`   r   r@   rr   rp   ÚNone)r`   r   r@   rr   rc   r?   rp   rt   )r`   r   rj   zOptional[List[Dict[str, Any]]]rp   rt   )rn   r   rU   r   r8   r   rp   rs   )Ú__doc__Ú
__future__r   r"   r   ÚloggingÚtypingr   r   r   r   Ú	getLoggerÚ__name__r^   r\   r   r+   r<   rC   rL   rR   rb   rf   rl   ro   © rM   r*   ú<module>r|      s   ðñõ  #ã Û Û ß ,Ó ,à	ˆ×	Ñ	˜8Ó	$€à /Ð ð
 UÐ ó	Fóó&Tð @Dõ ó"[óCð Y^õ (ó*ô,IrM   