Ë
    áß²jÍ$  ã                   óˆ   — d Z ddlZddlZddlmZ ddlmZ  ej                  d«      Zdefd„Z	de
fd	„Zdd
„Z G d„ d«      Zy)a  Rate-limit / credits header capture and low-credit notices for ``AIAgent``.

Parses provider response headers into ``_rate_limit_state`` / ``_credits_state`` and emits sticky notices.
Extracted from ``run_agent.py``; every method resolves through ``AIAgent``'s MRO unchanged.
é    N)ÚAny)Úis_truthy_valueÚ	run_agentÚhttp_responsec                 ó$   — | �t        | dd«      S dS )z>Headers of a response, or None when there is nothing to parse.NÚheaders)Úgetattr)r   s    ú=/home/ubuntu/.hermes/hermes-agent/agent/rate_limit_credits.pyÚ_response_headersr      s   € à6CÐ6OŒ7�= )¨TÓ2ÐYÐUYÐYó    Úreturnc                 ó   — | �d| dz  z  S dS )Nz%.0f%%éd   ún/a© )Úfractions    r
   Ú_pctr      s   € Ø,4Ð,@ˆH˜ 3™Ñ'ÐKÀeÐKr   c                 óN   — || _         | j                  €|j                  | _        yy)zcRetain-last-known: overwrite state and latch session-start remaining on the first header ever seen.N)Ú_credits_stateÚ_credits_session_start_microsÚremaining_micros)ÚagentÚstates     r
   Ú_adopt_credits_stater      s)   € à €EÔØ×*Ñ*Ð2Ø.3×.DÑ.DˆÕ+ð 3r   c                   ó†   — e Zd ZdZdeddfd„Zd„ Zdeddfd„Zdeddfd„Zdeddfd	„Z	dd
„Z
defd„Zd„ Zd„ Zdeddfd„Zy)ÚRateLimitCreditsMixinzGRate-limit + credits header capture and notices (see module docstring).r   r   Nc                 óˆ   — t        |«      }|sy	 ddlm}  ||| j                  ¬«      }|�|| _        yy# t
        $ r Y yw xY w)zUParse x-ratelimit-* headers from an HTTP response and cache the state (never raises).Nr   )Úparse_rate_limit_headers©Úprovider)r   Úagent.rate_limit_trackerr   r    Ú_rate_limit_stateÚ	Exception)Úselfr   r   r   r   s        r
   Ú_capture_rate_limitsz*RateLimitCreditsMixin._capture_rate_limits#   sO   € ä# MÓ2ˆÙØð	ÝIÙ,¨W¸t¿}¹}ÔMˆEØÐ Ø).�Õ&ð !øäò 	Ùð	ús   �#5 µ	AÁ Ac                 ó   — | j                   S )z1Return the last captured RateLimitState, or None.)r"   ©r$   s    r
   Úget_rate_limit_statez*RateLimitCreditsMixin.get_rate_limit_state0   s   € à×%Ñ%Ð%r   c                 ó^   — t        |«      }|sy	 ddlm}  || |«       y# t        $ r Y yw xY w)zÿRecord the Nous gateway's ``x-nous-model-switch`` header (a named account asked for the
        free tier's model; the gateway served its backing model and named it). Applied between
        calls by ``hermes_cli.anon_auth.apply_model_switch``. Fail-open.Nr   )Únote_model_switch)r   Úhermes_cli.anon_authr*   r#   )r$   r   r   r*   s       r
   Ú_capture_nous_model_switchz0RateLimitCreditsMixin._capture_nous_model_switch4   s8   € ô $ MÓ2ˆÙØð	Ý>Ù˜d GÕ,øÜò 	Ùð	ús   �   	,«,c                 óH   — | j                  |«       | j                  |«       y)z�Capture rate-limit + credits state from Anthropic Messages response headers (the SDK's
        aggregated ``Message`` drops them). Fail-open.N)r%   Ú_capture_credits)r$   r   s     r
   Ú#_capture_anthropic_response_headersz9RateLimitCreditsMixin._capture_anthropic_response_headersA   s    € ð 	×!Ñ! -Ô0Ø×Ñ˜mÕ,r   c                 ó  — 	 ddl m}  |«       }|�˜t        | |«       t	        | dd«      }t        |t        «      rd|d<   t        j                  d|j                  |j                  xs d|j                  |j                  t        |j                  «      «       | j                  «        yt!        |«      }|syt#        t$        j&                  j)                  d	«      «      }	 dd
l m}  ||| j,                  ¬«      }|€|rt        j                  d«       yt        | |«       |r¸| j/                  «       }	t        j                  d|j                  |j                  xs d|j                  |j                  t        |j                  «      |	�d|	dz  z  nd|j0                  t3        d«      k7  rd|j0                  z  nd|j4                  rd|j4                  z  nd«	       | j                  «        y# t        $ r d}Y �Œêw xY w# t        $ r Y yw xY w)u(  Parse x-nous-credits-* headers, cache CreditsState, fire threshold notices.

        The PARSE is swallowed (miss â†’ keep last-known); notice EVALUATION WARNS on failure so a
        depletion-notice bug cannot vanish silently. HERMES_DEV_CREDITS_FIXTURE injects a chosen state instead.
        r   )Údev_fixture_credits_stateNÚ_credits_latchTÚseen_below_90u    credits â–¸ [FIXTURE] remaining=%d (%s) Â· paid=%s Â· denom=%s Â· used=%s (real headers bypassed â€” `echo clear` / unset HERMES_DEV_CREDITS_FIXTURE to restore)ú?ÚHERMES_DEV_CREDITS)Úparse_credits_headersr   uo   credits â–¸ response had no valid x-nous-credits-* headers (miss â€” producer off / non-Nous path / >TTL stale)uY   credits â–¸ remaining=%d (%s) Â· paid=%s Â· denom=%s Â· used=%s Â· Î”spent=%s Â· age=%s%su   %.1fÂ¢i'  r   Úinfz%.0fsu    Â· disabled=%sÚ )Úagent.credits_trackerr1   r#   r   r	   Ú
isinstanceÚdictÚloggerÚinfor   Úremaining_usdÚpaid_accessÚdenominator_kindr   Úused_fractionÚ_emit_credits_noticesr   r   ÚosÚenvironÚgetr6   r    Úget_credits_spent_microsÚage_secondsÚfloatÚdisabled_reason)
r$   r   r1   ÚfixtureÚlatchr   Údevr6   r   Úspents
             r
   r.   z&RateLimitCreditsMixin._capture_creditsG   sæ  € ð	ÝGÙ/Ó1ˆGð ÐÜ   wÔ/Ü˜DÐ"2°DÓ9ˆEÜ˜%¤Ô&à)-��oÑ&Ü�K‰Kðià×(Ñ(¨'×*?Ñ*?Ò*FÀ3È×H[ÑH[Ð]d×]uÑ]uÜ�W×*Ñ*Ó+ô	-ð
 ×&Ñ&Ô(ØÜ# MÓ2ˆÙØÜœbŸj™jŸn™nÐ-AÓBÓCˆð	ÝCÙ)¨'¸D¿M¹MÔJˆEð ˆ=ÙÜ—‘ð Sô Tàä˜T 5Ô)Ùà×1Ñ1Ó3ˆEÜ�K‰KØkØ×&Ñ&¨×(;Ñ(;Ò(B¸sÀE×DUÑDUÐW\×WmÑWmÜ�U×(Ñ(Ó)È5ÐK\¨H¸À¹Ò,FÐbgØ16×1BÑ1BÄeÈEÃlÒ1R�˜5×,Ñ,Ò,ÐX]Ø?D×?TÒ?TÐ" U×%:Ñ%:Ò:ÐZ\ô^ð 	×"Ñ"Õ$øôU ò 	Ø‹Gð	ûô0 ò 	Ùð	ús#   ‚G+ Ã G= Ç+G:Ç9G:Ç=	H	ÈH	c                 ó  — t        | dd«      €t        | dd«      €yt        | dd«      }| j                  «       r|€y	 ddlm}m}m}m} t        | dd«      }|€ |«       x}| _         |t        | dd	«      xs d	t        | d
d	«      xs d	«      }|j                  r|s	 || «      ry ||||¬«      \  }}	|	D ]  }
| j                  |
«       Œ |D ]  }| j                  |«       Œ y# t        $ r t        j                  dd¬«       Y yw xY w)zÐRun the threshold policy and emit notices (shared by the warm path and the cold-start seed).

        Runs only when a notice consumer is bound; WARNS on failure; clears FIRST so depleted lands last.
        Únotice_callbackNÚnotice_clear_callbackr   r   )Úevaluate_credits_noticesÚis_free_tier_modelÚnew_credits_latchÚ%rewarm_pricing_before_depleted_noticer2   Úmodelr8   Úbase_url)Úmodel_is_freez%credits notice evaluation/emit failedT)Úexc_info)r	   Ú_credits_notices_enabledr9   rQ   rR   rS   rT   r2   ÚdepletedÚ_emit_notice_clearÚ_emit_noticer#   r<   Úwarning)r$   r   rQ   rR   rS   rT   rK   rW   Úto_showÚto_clearÚkeyÚnotices               r
   rB   z+RateLimitCreditsMixin._emit_credits_notices|   s1  € ô
 �4Ð*¨DÓ1Ð9¼gÀdÐLcÐeiÓ>jÐ>rØÜ˜Ð.°Ó5ˆØ×,Ñ,Ô.°%°-Øð	S÷ó ô ˜DÐ"2°DÓ9ˆEØˆ}Ù.?Ó.AÐA�˜Ô+á.¬w°t¸WÀbÓ/IÒ/OÈRÔQXÐY]Ð_iÐkmÓQnÒQtÐrtÓuˆMØ�~Š~¡mÑ8]Ð^bÔ8cØÙ 8¸ÀÐUbÔ cÑˆG�XØò -�Ø×'Ñ'¨Õ,ð-à!ò *�Ø×!Ñ! &Õ)ñ*øäò 	SÜ�N‰NÐBÈTˆNÖRð	Sús   ½A&C# Â$>C# Ã# DÄDc                 óÞ   — t        | dd«      }|�|S d}	 ddlm}  |«       xs i j                  d«      }t	        |t
        «      rd|v rt        |d   «      }|| _        |S # t        $ r Y Œw xY w)zh``display.credits_notices``, read once per agent and cached (UI noise, not correctness); fail-open True.Ú_credits_notices_enabled_cacheNTr   )Úload_configÚdisplayÚcredits_notices)	r	   Úhermes_cli.configrd   rE   r:   r;   Úboolr#   rc   )r$   ÚcachedÚenabledrd   re   s        r
   rY   z.RateLimitCreditsMixin._credits_notices_enabled™   s…   € ä˜Ð?ÀÓFˆØÐØˆMØˆð	Ý5Ù"“}Ò*¨×/Ñ/°	Ó:ˆGÜ˜'¤4Ô(Ð->À'Ñ-IÜ˜wÐ'8Ñ9Ó:�ð /6ˆÔ+Øˆøô ò 	Ùð	ús   •AA  Á 	A,Á+A,c                 ó   — | j                   S )z/Return the last captured CreditsState, or None.)r   r'   s    r
   Úget_credits_statez'RateLimitCreditsMixin.get_credits_state©   s   € à×"Ñ"Ð"r   c                 óz   — | j                   �| j                  €y| j                   | j                  j                  z
  S )z\Session-cumulative micros spent = first_seen_remaining - current_remaining. None if no data.N)r   r   r   r'   s    r
   rF   z.RateLimitCreditsMixin.get_credits_spent_micros­   s9   € à×-Ñ-Ð5¸×9LÑ9LÐ9TØØ×1Ñ1°D×4GÑ4G×4XÑ4XÑXÐXr   c                 óB  — t        |«      }|sy	 |j                  d«      }|sy|j                  «       dk(  r6| xj                  dz  c_        t        j                  d| j                  «       yt        j                  d|j                  «       «       y# t        $ r Y yw xY w)zNLog X-OpenRouter-Cache-Status; HITs count in ``_or_cache_hits``. Never raises.Nzx-openrouter-cache-statusÚHITé   z)OpenRouter response cache HIT (total: %d)zOpenRouter response cache %s)r   rE   ÚupperÚ_or_cache_hitsr<   r=   Údebugr#   )r$   r   r   Ústatuss       r
   Ú_check_openrouter_cache_statusz4RateLimitCreditsMixin._check_openrouter_cache_status³   s‡   € ä# MÓ2ˆÙØð
	Ø—[‘[Ð!<Ó=ˆFÙØØ�|‰|‹~ Ò&Ø×#Ò# qÑ(Õ#Ü—‘ÐGÈ×I\ÑI\Õ]ä—‘Ð;¸V¿\¹\»^ÕLøÜò 	Ùð	ús   �B ¤AB Á-$B Â	BÂB©r   N)Ú__name__Ú
__module__Ú__qualname__Ú__doc__r   r%   r(   r,   r/   r.   rB   rh   rY   rl   rF   ru   r   r   r
   r   r       s‡   „ ÙQð°#ð ¸$ó ò&ð¸ð Àó ð-Àð -Èó -ð3%¨cð 3%°dó 3%ójSð:¨$ó ò #òYð¸Cð ÀDô r   r   rv   )rz   ÚloggingrC   Útypingr   Úutilsr   Ú	getLoggerr<   r   Ústrr   r   r   r   r   r
   ú<module>r€      sZ   ðñó
 Û 	Ý å !ð 
ˆ×	Ñ	˜;Ó	'€ðZ Só Zð
L�có LóE÷bò br   