§
    ÷žyjƒŸ  ã                  óÈ  — 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mZ ddl	m
Z
mZmZ ddlmZ  ej        e¦  «        Zdaded	<    ed
dh¦  «        Z ej        d¦  «        Z e¦   «         ZdOd„ZdPd„Ze G d„ d¦  «        ¦   «         ZdZdZdZded<   dZ dZ!dQd„Z"e G d„ d¦  «        ¦   «         Z#dRdSd$„Z$dd%œdTd*„Z%	 dRdUd/„Z& e'd0d1d2d3d4d5d6d7d
d8¬9¦
  «
         e'd:d;d:d;d4d5d
d8¬<¦  «         e'd=d>d=d>d4d5d
d8¬<¦  «         e'd?d@d?d@d4d5d
d8¬<¦  «         e'd6d7ddAd4d5d6d7d
d8¬9¦
  «
         e'ddAddAddAddB¬C¦  «         e'ddAdDdEd4d5ddAd
ddB¬F¦  «        dGœZ(dHedI<   dVdJ„Z)dVdK„Z*dWdM„Z+dXdN„Z,dS )YuO  Credits tracking for Nous inference API responses.

Parses x-nous-credits-* (and optional x-nous-tool-pool-*) headers from
inference responses into a validated CreditsState dataclass.  Provides
depletion detection (paid_access), subscription-cap used_fraction, and
warn-once schema-version gating.  This is the hardened parser used by all
live consumers (run_agent, tui_gateway) â€” not a dev-only shim.

Header schema (x-nous-credits-* family):
    x-nous-credits-version                    contract/schema version
    x-nous-credits-remaining-micros           total remaining balance (micros)
    x-nous-credits-remaining-usd              same, formatted USD string
    x-nous-credits-subscription-micros        subscription balance (SIGNED; may be negative/debt)
    x-nous-credits-subscription-usd           same, formatted USD string
    x-nous-credits-subscription-limit-micros  subscription cap (PAIRED/optional)
    x-nous-credits-subscription-limit-usd     same, formatted USD string (PAIRED/optional)
    x-nous-credits-rollover-micros            rolled-over balance (micros)
    x-nous-credits-purchased-micros           purchased balance (micros)
    x-nous-credits-purchased-usd              same, formatted USD string
    x-nous-credits-denominator-kind           "subscription_cap" | "none"
    x-nous-credits-paid-access                "true" | "false" (STRING!)
    x-nous-credits-disabled-reason            reason string (header omitted when null)
    x-nous-credits-as-of-ms                   server-side timestamp (ms epoch)

Tool-pool headers use a SEPARATE prefix:
    x-nous-tool-pool-micros                   tool-pool balance (micros)
    x-nous-tool-pool-gated-off                "true" | "false" (STRING!)

Money is handled as micros ints only; *_usd values are preserved verbatim as
the raw strings the server sent (never re-parsed to float).
é    )ÚannotationsN)Ú	dataclass)ÚAnyÚMappingÚOptional)Úis_truthy_valueFÚboolÚ_version_warning_emittedÚsubscription_capÚnonez^-?\d+\.\d{2}$Úvaluer   Úreturnc                óŠ   — | €t           S 	 t          t          | ¦  «        ¦  «        S # t          t          f$ r
 t           cY S w xY w)u;  Parse a header value to an exact int (money-safe).

    The contract guarantees every ``*_micros`` field is an integer string â€”
    we parse with ``int()`` directly, NOT ``int(float(...))``, to avoid float-
    precision loss above 2**53 that would silently corrupt large money values.

    Returns the parsed int, or ``_SENTINEL`` if the value is not a valid integer
    string (including float-shaped strings like "1.5").  The sentinel lets callers
    detect the failure and return None from the overall parse (fail-hard-on-bad-
    input, not silently coerce).
    )Ú	_SENTINELÚintÚstrÚ	TypeErrorÚ
ValueError©r   s    ú;/home/ragecks/.hermes/hermes-agent/agent/credits_tracker.pyÚ	_safe_intr   >   sR   € ð €}ÝÐðÝ•3�u‘:”:‰ŒÐøÝ•zÐ"ð ð ð ÝÐÐÐðøøøs   ‹' §AÁAúOptional[str]c                óX   — | €dS t          t                               | ¦  «        ¦  «        S )zCReturn True iff value is a non-None string matching ^-?\d+\.\d{2}$.NF)r	   Ú_USD_REÚmatchr   s    r   Ú_validate_usdr   S   s&   € à€}ØˆuÝ•—’˜eÑ$Ô$Ñ%Ô%Ð%ó    c                  óp  — e Zd ZU dZdZded<   dZded<   dZded<   dZded	<   dZ	ded
<   dZ
ded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded<   dZded <   ed'd"„¦   «         Zed(d#„¦   «         Zed'd$„¦   «         Zed)d&„¦   «         ZdS )*ÚCreditsStatezAFull credits state parsed from x-nous-credits-* response headers.r   r   ÚversionÚremaining_microsÚ r   Úremaining_usdÚsubscription_microsÚsubscription_usdNúOptional[int]Úsubscription_limit_microsr   Úsubscription_limit_usdÚrollover_microsÚpurchased_microsÚpurchased_usdÚtool_pool_microsFr	   Útool_pool_gated_offr   Údenominator_kindTÚpaid_accessÚdisabled_reasonÚas_of_msç        ÚfloatÚcaptured_atÚfrom_headerr   c                ó   — | j         dk    S ©Nr   )r4   ©Úselfs    r   Úhas_datazCreditsState.has_datat   s   € àÔ !Ò#Ð#r   c                ód   — | j         st          d¦  «        S t          j        ¦   «         | j        z
  S )NÚinf)r:   r3   Útimer4   r8   s    r   Úage_secondszCreditsState.age_secondsx   s,   € àŒ}ð 	 Ý˜‘<”<ÐÝŒy‰{Œ{˜TÔ-Ñ-Ð-r   c                ó   — | j          S )u  True when the account has lost paid access.

        Keyed off ``paid_access == False`` ONLY â€” never ``remaining_micros == 0``,
        which would give a false positive whenever the balance is zero but access
        is still live (e.g. subscription renewal pending).
        )r/   r8   s    r   ÚdepletedzCreditsState.depleted~   s   € ð Ô#Ð#Ð#r   úOptional[float]c                ó¾   — t          | j        t          ¦  «        sdS | j        dk    rdS | j        | j        z
  }t	          dt          d|| j        z  ¦  «        ¦  «        S )u�  Fraction of the subscription cap consumed, in [0.0, 1.0].

        Computable only when ``subscription_limit_micros`` is a truthy (non-zero,
        non-None) int.  Guarded on the LIMIT FIELD, not ``denominator_kind`` â€”
        the limit field is the real denominator; ``denominator_kind`` is metadata.
        Returns None when there is no computable denominator (no limit, or limit==0).
        Nr   r2   ç      ð?)Ú
isinstancer'   r   r$   ÚmaxÚmin)r9   Úuseds     r   Úused_fractionzCreditsState.used_fractionˆ   sc   € õ ˜$Ô8½#Ñ>Ô>ð 	Ø�4ØÔ)¨QÒ.Ð.Ø�4ØÔ-°Ô0HÑHˆÝ�3�˜C ¨Ô(FÑ!FÑGÔGÑHÔHÐHr   ©r   r	   )r   r3   )r   rA   )Ú__name__Ú
__module__Ú__qualname__Ú__doc__r    Ú__annotations__r!   r#   r$   r%   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r4   r5   Úpropertyr:   r>   r@   rH   © r   r   r   r   ]   sÛ  € € € € € € àKÐKà€GÐÐÐÑØÐÐÐÐÑØ€MÐÐÐÑØ ÐÐ Ð Ð Ñ ØÐÐÐÐÑØ/3ÐÐ3Ð3Ð3Ñ3Ø,0ÐÐ0Ð0Ð0Ñ0Ø€OÐÐÐÑØÐÐÐÐÑØ€MÐÐÐÑØÐÐÐÐÑØ %ÐÐ%Ð%Ð%Ñ%Ø"ÐÐ"Ð"Ð"Ñ"Ø€KÐÐÐÑØ%)€OÐ)Ð)Ð)Ñ)Ø€HÐÐÐÑØ€KÐÐÐÑØ€KÐÐÐÑàð$ð $ð $ñ „Xð$ð ð.ð .ð .ñ „Xð.ð
 ð$ð $ð $ñ „Xð$ð ðIð Ið Iñ „XðIð Ið Ir   r   Ústickyi@  ))g      à?Úinfoé2   )g      è?ÚwarnéK   )gÍÌÌÌÌÌì?rT   éZ   z"tuple[tuple[float, str, int], ...]ÚCREDITS_USAGE_BANDSzcredits.usagei'  Údictc                 ó(   — t          ¦   «         ddddœS )u&  Fresh notice latch in the shape :func:`evaluate_credits_notices` expects.

    The policy owns this schema â€” every producer (agent build, lazy re-init,
    tests) must build the latch through here so a new gate key lands everywhere
    at once instead of drifting across hand-rolled literals.FN)ÚactiveÚseen_below_90Ú
usage_bandÚseen_grant_unspent)ÚsetrP   r   r   Únew_credits_latchr_   µ   s#   € õ ‘%”%ØØØ#ð	ð ð r   c                  ód   — e Zd ZU dZded<   dZded<   dZded<   dZd	ed
<   dZded<   dZ	ded<   dS )ÚAgentNoticeuó  A structured, driver-agnostic out-of-band notice.

    The agent fires these via ``AIAgent.notice_callback`` (and clears them via
    ``notice_clear_callback``); each driver renders it its own way â€” the TUI as a
    status-bar override, the CLI as a console line, etc. v1 credits notices are all
    ``kind="sticky"``; ``kind``/``ttl_ms`` are kept fully expressive so a future
    config/slash-command can switch them to TTL without touching the policy (a
    single default seam â€” see L4).
    r   ÚtextrR   ÚlevelrQ   ÚkindNr&   Úttl_msr   ÚkeyÚid)
rJ   rK   rL   rM   rN   rc   rd   re   rf   rg   rP   r   r   ra   ra   Æ   s|   € € € € € € ðð ð €I€I�IØ€EÐÐÐÑØ€DÐÐÐÑØ €FÐ Ð Ð Ñ Ø€CÐÐÐÑØ€BÐÐÐÑÐÐr   ra   r"   Úmodelr   Úbase_urlc                óL  — | sdS |                       d¦  «        rdS |sdS 	 ddlm}m} |                     d¦  «        }|                      d¦  «        r|dd	…                              d¦  «        }|                     |¦  «        }|sdS  || |¦  «        S # t          $ r Y dS w xY w)
u­  Return True when *model* is a Nous free-tier model, using ONLY local data.

    Two signals, both zero-network:

    1. The ``:free`` suffix â€” the canonical Nous free SKU marker (e.g.
       ``nvidia/nemotron-3-ultra:free``). Free by construction on the API side
       (spend is forced to 0 for ``:free`` ids).
    2. A peek into the in-process pricing cache in ``hermes_cli.models``
       (populated when the model picker fetched ``/v1/models`` pricing for
       *base_url*). PEEK ONLY â€” a cache miss never triggers a fetch. This is
       CLI/TUI-session best-effort: gateway sessions never run the picker's
       pricing fetch, so suppression there rests entirely on the ``:free``
       suffix (which all Nous free SKUs carry).

    Fail-open to False (the depleted notice still shows) on any error: wrongly
    showing the warning is recoverable noise; wrongly hiding it on a paid model
    would mask a real billing block.
    Fz:freeTr   )Ú_is_model_freeÚ_pricing_cacheú/z/v1Néýÿÿÿ)ÚendswithÚhermes_cli.modelsrk   rl   ÚrstripÚgetÚ	Exception)rh   ri   rk   rl   rf   Úpricings         r   Úis_free_tier_modelru   Ý   sè   € ð& ð ØˆuØ‡~‚~�gÑÔð ØˆtØð ØˆuðØDÐDÐDÐDÐDÐDÐDÐDð
 �oŠo˜cÑ"Ô"ˆØ�<Š<˜ÑÔð 	'Ø�c�r�c”(—/’/ #Ñ&Ô&ˆCØ ×$Ò$ SÑ)Ô)ˆØð 	Ø�5Øˆ~˜e WÑ-Ô-Ð-øÝð ð ð Øˆuˆuðøøøs   ¡A&B Â	B Â
B#Â"B#)Úmodel_is_freeÚstateÚlatchrv   ú#tuple[list[AgentNotice], list[str]]c          
     ó  — g }g }| j         }t          d         d         }|�||k     rd|d<   |�|dk     r| j        t          k    rd|d<   |d         }d}|�t          D ]}	||	d         k    r|	}Œ| j        dk    rd}| j        dk    o|duo|dk    o
| j        dk    }
| j         }|                     d	¦  «        }|r|d         r|d
         nd}||k    ròt          |v r4| 	                    t          ¦  «         | 
                    t          ¦  «         |�®| j        pd}|d         }| j        pd}t          dt          ||| j        z
  ¦  «        ¦  «        }|r|dz  d›nd}|dk    rdnd}| 	                    t          |› d|› d|› d�|t           t          t          ¬¦  «        ¦  «         |                     t          ¦  «         ||d	<   |
rjd|vrf|                     dd¦  «        rP| 	                    t          d| j        › d�dt           dd¬¦  «        ¦  «         |                     d¦  «         d|d<   n0d|v r,|
s*| 	                    d¦  «         | 
                    d¦  «         |o| }|rFd|vrB| 	                    t          ddt           dd¬¦  «        ¦  «         |                     d¦  «         n_d|v r[|sY| 	                    d¦  «         | 
                    d¦  «         |s-| 	                    t          ddd t&          d!d!¬"¦  «        ¦  «         ||fS )#u3  Reconcile credits notices against the latch. Mutates ``latch`` IN PLACE.

    latch = {"active": set[str], "seen_below_90": bool, "usage_band": Optional[int],
    "seen_grant_unspent": bool}.

    ``model_is_free``: True when the session's active model is a Nous free-tier
    model (see :func:`is_free_tier_model`). Suppresses the ``credits.depleted``
    notice â€” a depleted account on a free model can keep inferencing, so the
    error banner is noise (and confuses free-tier users who never had credits).
    Suppression does NOT emit the "restored" success notice; that fires only on
    a genuine ``paid_access`` flip back to True.

    Returns ``(to_show: list[AgentNotice], to_clear: list[str])``.
    Caller emits to_clear FIRST, then to_show.

    Pure function â€” no I/O, no agent/run_agent imports.
    r   NTr[   rC   r]   rZ   r   r\   é   ú?é   é@B ú.2frT   u   âš u   â€¢z You've used $z
 of your $z cap)rb   rc   rd   rf   rg   zcredits.grant_spentFu   â€¢ Grant spent Â· $z top-up leftrR   zcredits.depletedu0   âœ• Credit access paused Â· run /topup to top upÚerroru   âœ“ Credit access restoredÚsuccessÚttlzcredits.restored)rb   rc   rd   re   rf   rg   )rH   rW   r$   ÚGRANT_UNSPENT_MIN_MICROSr*   r.   r/   rr   ÚCREDITS_USAGE_KEYÚappendÚdiscardr(   r'   rE   rF   ra   ÚCREDITS_NOTICE_KINDÚaddr+   ÚCREDITS_RESTORED_TTL_MS)rw   rx   rv   Úto_showÚto_clearÚufÚ_lowest_bandrZ   Úcurrent_bandÚbandÚ
grant_condÚdepleted_condÚ
shown_bandÚtarget_bandÚ_cap_usdÚ_levelÚ_limÚ_used_microsÚ	_used_usdÚ_glyphÚshow_depleteds                        r   Úevaluate_credits_noticesr›   
  s  € ð. "$€GØ€Hà	Ô	€Bõ ' qÔ)¨!Ô,€LØ	€~˜"˜|Ò+Ð+Ø!%ˆˆoÑð 	ˆØ�ŠHˆHØÔ%Õ)AÒAÐAà&*ˆÐ"Ñ#à�8Œ_€Fð 6:€LØ	€~Ý'ð 	$ð 	$ˆDØ�T˜!”WŠ}ˆ}Ø#�øð Ô Ò!Ð!ØˆàÔÐ"4Ò4ð 	'Ø�dˆNð	'à�#ŠIð	'ð Ô" QÒ&ð	 ð Ô)Ð)€Mð —’˜<Ñ(Ô(€JØ&2ÐX°u¸_Ô7MÐX�,˜q”/�/ÐTX€KØ�jÒ Ð Ý Ð&Ð&Ø�OŠOÕ-Ñ.Ô.Ð.Ø�NŠNÕ,Ñ-Ô-Ð-ØÐ"ð Ô3Ð:°sˆHØ! !”_ˆFð Ô2Ð7°aˆDÝ˜q¥# d¨D°5Ô3LÑ,LÑ"MÔ"MÑNÔNˆLØ=AÐJ˜<¨)Ñ3Ð9Ð9Ð9ÀsˆIØ$¨Ò.Ð.�U�U°EˆFØ�NŠNÝØ"ÐUÐU°)ÐUÐUÀxÐUÐUÐUØ Ý,Ý)Ý(ðñ ô ñô ð ð �JŠJÕ(Ñ)Ô)Ð)Ø)ˆˆlÑð 	ð.à!¨Ð/Ð/Ø�IŠIÐ*¨EÑ2Ô2ð 0ð 	�ŠÝØM¨EÔ,?ÐMÐMÐMØÝ(Ø)Ø(ðñ ô ñ	
ô 	
ð 	
ð 	�
Š
Ð(Ñ)Ô)Ð)Ø&+ˆÐ"Ñ#Ð#Ø	 &Ð	(Ð	(°Ð	(Ø�ŠÐ-Ñ.Ô.Ð.Ø�ŠÐ,Ñ-Ô-Ð-ð "Ð7¨-Ð&7€MØð Ð+°6Ð9Ð9Ø�ŠÝØGØÝ(Ø&Ø%ðñ ô ñ	
ô 	
ð 	
ð 	�
Š
Ð%Ñ&Ô&Ð&Ð&Ø	˜vÐ	%Ð	%¨mÐ	%Ø�ŠÐ*Ñ+Ô+Ð+Ø�ŠÐ)Ñ*Ô*Ð*Øð 	ð �NŠNÝØ5Ø#ØÝ2Ø*Ø)ðñ ô ñ	ô 	ð 	ð �XÐÐr   ÚheadersúMapping[str, str]ÚproviderúOptional[CreditsState]c                óÎ  ‡— 	 t          d„ | D ¦   «         ¦  «        sdS d„ |                      ¦   «         D ¦   «         Š‰                     d¦  «        }|€dS t          |¦  «        }|t          u rdS |dk    r,|dk    r$t
          sdat                               d|¦  «         dS d7ˆfd„}d7ˆfd„} |d¦  «        }|t          u rdS  |d¦  «        }|t          u rdS  |d¦  «        }|t          u rdS  |d¦  «        }	|	t          u rdS ‰                     d¦  «        }
|
€d}n"t          |
¦  «        }|t          u s|dk     rdS |} |d¦  «        }|t          u rdS ‰                     dd¦  «        }t          |¦  «        sdS ‰                     dd¦  «        }t          |¦  «        sdS ‰                     dd¦  «        }t          |¦  «        sdS ‰                     d¦  «        }‰                     d¦  «        }d}d}|�9|�7t          |¦  «        }|t          u rdS |dk     rdS t          |¦  «        sdS |}|}‰                     dd¦  «        }|t          vrdS d‰v r9‰d          
                    ¦   «                              ¦   «         }|dvrdS |dk    }nd}d ‰v r9‰d           
                    ¦   «                              ¦   «         }|dvrdS |dk    }nd!}‰                     d"¦  «        }t          d8i d#|“d$|“d%|“d&|“d'|“d(|“d)|“d*|“d+|	“d,|“d-|“d.|“d/|“d0|“d1|“d2|“d3t          j        ¦   «         “d4d“ŽS # t          $ r  t                               d5d¬6¦  «         Y dS w xY w)9a-  Parse x-nous-credits-* (and x-nous-tool-pool-*) headers into a CreditsState.

    Returns None (miss) on ANY of:
    - No ``x-nous-credits-version`` header present.
    - Version != 1 (> 1 also emits a one-time logger.warning).
    - Any ``*_micros`` field is non-integer, or negative for a non-subscription field.
    - Any ``*_usd`` field doesn't match ``^-?\d+\.\d{2}$``.
    - ``denominator_kind`` is not in {"subscription_cap", "none"}.
    - ``paid_access`` / ``tool_pool_gated_off`` is not exactly "true"/"false".
    - ``as_of_ms`` is not a valid integer.
    - Any unexpected exception.

    Fail-open on the subscription_limit pair: a half-pair (only -micros or only
    -usd present) is treated as both-absent; the overall parse STILL SUCCEEDS
    but with subscription_limit_micros/usd both None.
    c              3  óF   K  — | ]}|                      ¦   «         d k    V — ŒdS )úx-nous-credits-versionN©Úlower)Ú.0Úks     r   ú	<genexpr>z(parse_credits_headers.<locals>.<genexpr>Ø  s0   è è € ÐJÐJ¸Q�1—7’7‘9”9Ð 8Ò8ÐJÐJÐJÐJÐJÐJr   Nc                ó>   — i | ]\  }}|                      ¦   «         |“ŒS rP   r£   )r¥   r¦   Úvs      r   ú
<dictcomp>z)parse_credits_headers.<locals>.<dictcomp>Ü  s&   € Ð<Ð<Ð<¡D A q�1—7’7‘9”9˜aÐ<Ð<Ð<r   r¢   r}   TuA   credits header version %d unsupported, ignoring â€” update Hermesrf   r   r   r   c                óŠ   •— ‰                      | ¦  «        }t          |¦  «        }|t          u rt          S |dk     rt          S |S r7   ©rr   r   r   ©rf   ÚrawÚvalÚlowereds      €r   Ú_req_nonnegz*parse_credits_headers.<locals>._req_nonnegð  sD   ø€ Ø—+’+˜cÑ"Ô"ˆCÝ˜C‘.”.ˆCØ•iÐÐÝ Ð Ø�QŠwˆwÝ Ð ØˆJr   c                óp   •— ‰                      | ¦  «        }t          |¦  «        }|t          u rt          S |S )Nr¬   r­   s      €r   Ú_req_intz'parse_credits_headers.<locals>._req_intú  s5   ø€ Ø—+’+˜cÑ"Ô"ˆCÝ˜C‘.”.ˆCØ•iÐÐÝ Ð ØˆJr   zx-nous-credits-remaining-microsz"x-nous-credits-subscription-microszx-nous-credits-rollover-microszx-nous-credits-purchased-microszx-nous-tool-pool-microsr   zx-nous-credits-as-of-mszx-nous-credits-remaining-usdr"   zx-nous-credits-subscription-usdzx-nous-credits-purchased-usdz(x-nous-credits-subscription-limit-microsz%x-nous-credits-subscription-limit-usdzx-nous-credits-denominator-kindr   zx-nous-credits-paid-access)ÚtrueÚfalser´   zx-nous-tool-pool-gated-offFzx-nous-credits-disabled-reasonr    r!   r#   r$   r%   r'   r(   r)   r*   r+   r,   r-   r.   r/   r0   r1   r4   r5   u9   credits â–¸ parse_credits_headers raised (fail-open miss)©Úexc_info)rf   r   r   r   rP   )ÚanyÚitemsrr   r   r   r
   ÚloggerÚwarningr   Ú_VALID_DENOMINATOR_KINDSÚstripr¤   r   r=   rs   Údebug)rœ   rž   Úversion_rawÚversion_valr±   r³   r!   r$   r)   r*   Ú_tp_rawr,   Ú_tp_valr1   r#   r%   r+   Úsub_limit_micros_rawÚsub_limit_usd_rawr'   r(   Úlmr.   Úpa_rawr/   Útpgo_rawr-   r0   r°   s                               @r   Úparse_credits_headersrÈ   ¼  s  ø€ ð,gõ ÐJÐJÀ'ÐJÑJÔJÑJÔJð 	Ø�4ð =Ð<¨G¯MªM©O¬OÐ<Ñ<Ô<ˆð —k’kÐ":Ñ;Ô;ˆØÐØ�4Ý Ñ,Ô,ˆØ�)Ð#Ð#Ø�4Ø˜!ÒÐØ˜QŠˆÕ'?ˆØ+/Ð(Ý—’ØWØñô ð ð �4ð	ð 	ð 	ð 	ð 	ð 	ð	ð 	ð 	ð 	ð 	ð 	ð '˜;Ð'HÑIÔIÐØ�yÐ(Ð(Ø�4à&˜hÐ'KÑLÔLÐØ¥)Ð+Ð+Ø�4à%˜+Ð&FÑGÔGˆØ�iÐ'Ð'Ø�4à&˜;Ð'HÑIÔIÐØ�yÐ(Ð(Ø�4ð —+’+Ð7Ñ8Ô8ˆØˆ?Ø ÐÐå Ñ(Ô(ˆGØ�)Ð#Ð# w°¢{ {Ø�tØ&Ðà�;Ð8Ñ9Ô9ˆØ•yÐ Ð Ø�4ð  ŸšÐ$BÀBÑGÔGˆÝ˜]Ñ+Ô+ð 	Ø�4à"Ÿ;š;Ð'HÈ"ÑMÔMÐÝÐ-Ñ.Ô.ð 	Ø�4àŸšÐ$BÀBÑGÔGˆÝ˜]Ñ+Ô+ð 	Ø�4ð
  'Ÿ{š{Ð+UÑVÔVÐØ#ŸKšKÐ(OÑPÔPÐà37Ð!Ø04ÐàÐ+Ð0AÐ0MåÐ/Ñ0Ô0ˆBØ•YˆˆØ�tØ�AŠvˆvØ�tÝ Ð!2Ñ3Ô3ð Ø�tØ(*Ð%Ø%6Ð"ð #Ÿ;š;Ð'HÈ&ÑQÔQÐØÕ#;Ð;Ð;Ø�4ð (¨7Ð2Ð2ØÐ9Ô:×@Ò@ÑBÔB×HÒHÑJÔJˆFØÐ.Ð.Ð.Ø�tØ  FÒ*ˆKˆKàˆKà'¨7Ð2Ð2ØÐ;Ô<×BÒBÑDÔD×JÒJÑLÔLˆHØÐ0Ð0Ð0Ø�tØ"*¨fÒ"4ÐÐà"'Ðð "Ÿ+š+Ð&FÑGÔGˆåð 
ð 
ð 
Ø�Kð
à-Ð-ð
ð (˜-ð
ð !4Ð 3ð	
ð
 .Ð-ð
ð '@Ð&?ð
ð $:Ð#9ð
ð ,˜Oð
ð .Ð-ð
ð (˜-ð
ð .Ð-ð
ð !4Ð 3ð
ð .Ð-ð
ð $˜ð
ð ,˜Oð
ð  �Xð!
õ" œ	™œ˜ð#
ð$ ˜ð%
ð 	
øõ* ð ð ð õ 	�ŠÐPÐ[_ˆÑ`Ô`Ð`Øˆtˆtðøøøs„   ƒL: ž5L: ÁL: Á/0L: Â! L: ÃL: ÃL: Ã/L: Ä8L: Ä?L: Å%L: Å>%L: Æ%%L: ÇA
L: ÈL: È L: È1#L: É4L: Ê=L: ËA.L: Ì:&M$Í#M$i óÎz30.34i€¨z18.00i -1z20.00i K¼ z12.34T)
r!   r#   r$   r%   r'   r(   r*   r+   r.   r/   i€–˜ z10.00)r!   r#   r$   r%   r'   r(   r.   r/   i@KL z5.00i€„ z2.00ú0.00Úout_of_credits)r!   r#   r$   r%   r*   r+   r/   r0   iÀ´³ÿz-5.00)r!   r#   r$   r%   r'   r(   r*   r+   r.   r/   r0   )ÚhealthyÚ	sub_50pctÚ	sub_75pctÚ	sub_90pctÚgrant_exhaustedr@   Údebtzdict[str, dict]Ú_DEV_FIXTURESc                 ót  — t          t          j                             d¦  «        ¦  «        sdS t          j                             dd¦  «                             ¦   «         } | sdS | }t          j        j        | v sd| v rc	 t          | dd¬¦  «        5 }|                     ¦   «                              ¦   «         }ddd¦  «         n# 1 swxY w Y   n# t          $ r Y dS w xY wt                               |                     ¦   «         ¦  «        }|sdS d	d
dœ|¥}t          di |¤dt          j        ¦   «         dœ¤ŽS )ub  Return a fixture CreditsState for HERMES_DEV_CREDITS_FIXTURE, or None.

    The env value is a state name, OR a path to a file whose contents are a state
    name (re-read each call â†’ flip states live without a restart). Unknown name /
    "clear" / "none" / unset â†’ None (normal behaviour). Throwaway test scaffolding.

    Hard prod-leak guard: a fixture applies ONLY when the dev flag HERMES_DEV_CREDITS
    is also on, so a stray HERMES_DEV_CREDITS_FIXTURE (leaked into a shell profile, a
    container env, a launch plist, â€¦) can never surface fabricated balances/notices
    on a real account.
    ÚHERMES_DEV_CREDITSNÚHERMES_DEV_CREDITS_FIXTUREr"   rm   Úrzutf-8)Úencodingr}   rÉ   )r    r+   T)r5   r4   rP   )r   ÚosÚenvironrr   r½   ÚpathÚsepÚopenÚreadÚOSErrorrÑ   r¤   r   r=   )r®   ÚnameÚfhÚspecÚmergeds        r   Údev_fixture_credits_staterâ   ¼  s|  € õ �2œ:Ÿ>š>Ð*>Ñ?Ô?Ñ@Ô@ð ØˆtÝ
Œ*�.Š.Ð5°rÑ
:Ô
:×
@Ò
@Ñ
BÔ
B€CØð ØˆtØ€DÝ	„w„{�cÐÐ˜S C˜Z˜Zð	Ý�c˜3¨Ð1Ñ1Ô1ð )°RØ—w’w‘y”y—’Ñ(Ô(�ð)ð )ð )ñ )ô )ð )ð )ð )ð )ð )ð )øøøð )ð )ð )ð )øøåð 	ð 	ð 	Ø�4�4ð	øøøå×Ò˜TŸZšZ™\œ\Ñ*Ô*€DØð Øˆtð ¨VÐ<Ð<°tÐ<€FÝÐLÐL˜&ÐL¨dÅÄ	ÁÄÐLÐLÐLÐLÐLs6   Á?C Â'CÂ8C ÃCÃC ÃCÃC Ã
CÃCc                óJ  — 	 t          | dd¦  «        }t          | dd¦  «        }d„ }d„ }t          |dd¦  «        }t          |t          t          f¦  «        o|dk    }t          | dd¦  «        }t	           |t          |d	d¦  «        ¦  «         |t          |d	d¦  «        ¦  «         |t          |d
d¦  «        ¦  «         |t          |d
d¦  «        ¦  «        |r ||¦  «        nd|r ||¦  «        nd |t          |dd¦  «        ¦  «         |t          |dd¦  «        ¦  «         |t          |dd¦  «        ¦  «        |rdndt          |t
          ¦  «        r|nddt          j        ¦   «         ¬¦  «        S # t          $ r  t           	                    dd¬¦  «         Y dS w xY w)u4  Map a NousPortalAccountInfo into a header-shaped CreditsState for the seed.

    Float account dollars â†’ micros (plus a DISPLAY *_usd string â€” allowed, since
    we're formatting account floats, NOT parsing a server-provided *_usd). Returns
    None if the account can't yield a usable state (fail-open).Úpaid_service_access_infoNÚsubscriptionc                ó|   — t          | t          t          f¦  «        rt          t          | dz  ¦  «        ¦  «        ndS )Nr~   r   )rD   r   r3   Úround©Údollarss    r   Ú
_to_microsz/_credits_state_from_account.<locals>._to_microsê  s7   € Ý6@ÀÍ3ÕPUÈ,Ñ6WÔ6WÐ^•3•u˜W yÑ0Ñ1Ô1Ñ2Ô2Ð2Ð]^Ð^r   c                óF   — t          | t          t          f¦  «        r| d›ndS )Nr   r"   )rD   r   r3   rè   s    r   Ú_to_usdz,_credits_state_from_account.<locals>._to_usdí  s(   € õ (2°'½CÅ¸<Ñ'HÔ'HÐP�gÐ#Ð#Ð#ÈbÐPr   Úmonthly_creditsr   Úpaid_service_accessÚtotal_usable_creditsÚsubscription_credits_remainingÚpurchased_credits_remainingÚrollover_creditsr   r   TF)r!   r#   r$   r%   r'   r(   r*   r+   r)   r.   r/   r5   r4   u/   credits â–¸ seed accountâ†’state mapping failedr¶   )
ÚgetattrrD   r   r3   r   r	   r=   rs   rº   r¾   )rR   Ú_accÚ_subrê   rì   Ú_monthlyÚ_has_capÚ_paids           r   Ú_credits_state_from_accountrù   à  sú  € ð Ý�tÐ7¸Ñ>Ô>ˆÝ�t˜^¨TÑ2Ô2ˆð	_ð 	_ð 	_ð	Qð 	Qð 	Qõ
 ˜4Ð!2°DÑ9Ô9ˆÝ˜h­­e¨Ñ5Ô5ÐF¸(ÀQº,ˆÝ˜Ð3°TÑ:Ô:ˆÝØ'˜Z­°Ð6LÈdÑ(SÔ(SÑTÔTØ!˜'¥'¨$Ð0FÈÑ"MÔ"MÑNÔNØ * 
­7°4Ð9YÐ[_Ñ+`Ô+`Ñ aÔ aØ$˜W¥W¨TÐ3SÐUYÑ%ZÔ%ZÑ[Ô[Ø>FÐ&P j j°Ñ&:Ô&:Ð&:ÈDØ8@Ð#J 7 7¨8Ñ#4Ô#4Ð#4ÀdØ'˜Z­°Ð6SÐUYÑ(ZÔ(ZÑ[Ô[Ø!˜'¥'¨$Ð0MÈtÑ"TÔ"TÑUÔUØ&˜J¥w¨tÐ5GÈÑ'NÔ'NÑOÔOØ3;ÐGÐ/Ð/ÀÝ!+¨Eµ4Ñ!8Ô!8ÐB˜˜¸dØÝœ	™œð
ñ 
ô 
ð 	
øõ ð ð ð Ý�ŠÐFÐQUˆÑVÔVÐVØˆtˆtðøøøs   ‚E5E8 Å8&F"Æ!F"ÚNonec                ó
  — || _         t          | dd¦  «        €|j        | _        t          | dd¦  «        }t	          |t
          ¦  «        r|j        �d|d<   t          | dd¦  «        }t          |¦  «        r |¦   «          dS dS )u¼  Install a seed CreditsState on the agent and fire the notice policy once.

    Sets _credits_state, latches session-start remaining, and primes the crossing
    gate (the cold-start snapshot IS the first observation, so a session that opens
    already in a band warns immediately â€” the live header path keeps true crossing
    semantics), then emits. Safe to call from a worker thread: emit already runs
    off-thread in the TUI build path.Ú_credits_session_start_microsNÚ_credits_latchTr[   Ú_emit_credits_notices)Ú_credits_stateró   r!   rü   rD   rX   rH   Úcallable)Úagentrw   Ú_latchÚemits       r   Ú_hydrate_seed_stater  	  s›   € ð !€EÔÝˆuÐ5°tÑ<Ô<ÐDØ.3Ô.DˆÔ+Ý�UÐ,¨dÑ3Ô3€FÝ�&�$ÑÔð ' EÔ$7Ð$Cð #'ˆˆÑÝ�5Ð1°4Ñ8Ô8€DÝ��~„~ð Øˆ‰Œˆˆˆðð r   c                ó�  ‡ — 	 t          ‰ dd¦  «        dk    rdS t          ‰ dd¦  «        �dS d}	 t          ¦   «         }n# t          $ r d}Y nw xY w|�t          ‰ |¦  «         dS ddl}dˆ fd„}|                     |dd¬¦  «                             ¦   «          dS # t          $ r  t                               dd¬¦  «         Y dS w xY w)u}  Hydrate agent._credits_state from /api/oauth/account (or a dev fixture) and
    fire the notice policy, so depletion / usage-band warnings show at session OPEN.

    Shared by (a) the TUI/desktop agent build (fires at "ready", before any message)
    and (b) the first-turn conversation setup (fallback for plain CLI / when the
    build path didn't seed). Idempotent: a second call is a no-op once a seed or a
    real header has already populated _credits_state.

    Returns True if it seeded this call, False otherwise (not nous / already seeded /
    fail-open error). Never raises â€” credits must never block session startup.
    rž   r"   ÚnousFrÿ   NTr   r   rú   c                 óô   •— 	 ddl m}   | d¬¦  «        }t          ‰dd ¦  «        �d S t          |¦  «        }|�t	          ‰|¦  «         d S d S # t
          $ r  t                               dd¬¦  «         Y d S w xY w)Nr   )Úget_nous_portal_account_infoT)Úforce_freshrÿ   u2   credits â–¸ session-start seed (background) failedr¶   )Úhermes_cli.nous_accountr  ró   rù   r  rs   rº   r¾   )r  rR   rw   r  s      €r   Ú_bg_seedz/seed_credits_at_session_start.<locals>._bg_seedA  s·   ø€ ð	bØPÐPÐPÐPÐPÐPØ3Ð3ÀÐEÑEÔE�Ý˜5Ð"2°DÑ9Ô9ÐEØ�FÝ3°DÑ9Ô9�ØÐ$Ý'¨¨uÑ5Ô5Ð5Ð5Ð5ð %Ð$øåð bð bð bÝ—’ÐQÐ\`�ÑaÔaÐaÐaÐaÐaðbøøøs   ƒ#A ¨!A Á&A7Á6A7zcredits-seed)ÚtargetrÞ   Údaemonu1   credits â–¸ session-start seed failed (fail-open)r¶   ©r   rú   )	ró   râ   rs   r  Ú	threadingÚThreadÚstartrº   r¾   )r  Úfixturer  r  s   `   r   Úseed_credits_at_session_startr    s0  ø€ ð)Ý�5˜* bÑ)Ô)¨VÒ3Ð3Ø�5å�5Ð*¨DÑ1Ô1Ð=Ø�5Øˆð	Ý/Ñ1Ô1ˆGˆGøÝð 	ð 	ð 	ØˆGˆGˆGð	øøøàÐõ    wÑ/Ô/Ð/Ø�4ð
 	ÐÐÐð
	bð 
	bð 
	bð 
	bð 
	bð 
	bð 	×Ò ¨~ÀdÐÑKÔK×QÒQÑSÔSÐSØˆtøÝð ð ð õ 	�ŠÐHÐSWˆÑXÔXÐXØˆuˆuðøøøsA   ƒB šB ­B °? ¾B ¿AÁB ÁAÁB Á%4B Â&CÃC)r   r   r   r   )r   r   r   r	   )r   rX   )r"   )rh   r   ri   r   r   r	   )rw   r   rx   rX   rv   r	   r   ry   )rœ   r�   rž   r   r   rŸ   )r   rŸ   r  rI   )-rM   Ú
__future__r   Úloggingr×   Úrer=   Údataclassesr   Útypingr   r   r   Úutilsr   Ú	getLoggerrJ   rº   r
   rN   Ú	frozensetr¼   Úcompiler   Úobjectr   r   r   r   r‡   r‰   rW   r„   rƒ   r_   ra   ru   r›   rÈ   rX   rÑ   râ   rù   r  r  rP   r   r   ú<module>r     sø  ððð ð ð@ #Ð "Ð "Ð "Ð "Ð "à €€€Ø 	€	€	€	Ø 	€	€	€	Ø €€€Ø !Ð !Ð !Ð !Ð !Ð !Ø )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ð )Ð )à !Ð !Ð !Ð !Ð !Ð !à	ˆÔ	˜8Ñ	$Ô	$€ð "'Ð Ð &Ð &Ð &Ñ &ð %˜9Ð&8¸&Ð%AÑBÔBÐ ð ˆ"Œ*Ð&Ñ
'Ô
'€ð ˆF‰HŒH€	ðð ð ð ð*&ð &ð &ð &ð ð8Ið 8Ið 8Ið 8Ið 8Iñ 8Iô 8Iñ „ð8Ið@ Ð ØÐ ð;Ð ð ð ð ñ ð
 $Ð ð "Ð ðð ð ð ð" ðð ð ð ð ñ ô ñ „ðð,'ð 'ð 'ð 'ð 'ðb  ð	lð lð lð lð lð lðh ð}ð }ð }ð }ð }ð\ ˆtØ#°7Ø&¸Ø",ÀWØ#°7Ø+¸ðñ ô ð �Ø#°7Ø&¸Ø",ÀWØ+¸ð	ñ ô ð �Ø"°&Ø%¸Ø",ÀWØ+¸ð	ñ ô ð �Ø"°&Ø%¸Ø",ÀWØ+¸ð	ñ ô ð �tà#°7Ø°Ø",ÀWØ#°7Ø+¸ðñ ô ð �Ø¨&Ø°Ø¨&ØÐ+;ð	ñ ô ð ˆDØ¨&Ø&¸Ø",ÀWØ¨&Ø+¸Ø(ðñ ô ðQ0"ð 0"€ð 0ð 0ð 0ñ 0ðf!Mð !Mð !Mð !MðH&ð &ð &ð &ðRð ð ð ð,5ð 5ð 5ð 5ð 5ð 5r   