
    )jAS                       d dl mZ d dlZd dlZd dlmZ d dlmZmZ d dlm	Z	m
Z
mZ d dlZd dlmZmZ d dlmZmZ d dlmZ e	rd d	lmZ  ej        e          Zd9dZ ed           G d d                      Z ed           G d d                      Zd:dZd;dZd<dZddd=d#Zd>d&Z d?d)Z!d@d*Z"dd+d,dAd.Z#d@d/Z$dBd1Z%d@d2Z&d@d3Z'dCd5Z(ddd6dDd8Z)dS )E    )annotationsN)	dataclass)datetimetimezone)TYPE_CHECKINGAnyOptional)_is_oauth_tokenresolve_anthropic_token)_read_codex_tokens!resolve_codex_runtime_credentials)resolve_runtime_provider)	TypeGuardreturnr   c                 >    t          j        t          j                  S N)r   nowr   utc     :/home/wildlama/.hermes/hermes-agent/agent/account_usage.py_utc_nowr      s    <%%%r   T)frozenc                  D    e Zd ZU ded<   dZded<   dZded<   dZded	<   dS )
AccountUsageWindowstrlabelNzOptional[float]used_percentOptional[datetime]reset_atOptional[str]detail)__name__
__module____qualname____annotations__r   r    r"   r   r   r   r   r      sN         JJJ$(L((((#'H'''' F      r   r   c                      e Zd ZU ded<   ded<   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
dS )AccountUsageSnapshotr   providersourcer   
fetched_atzAccount limitstitleNr!   planr   ztuple[AccountUsageWindow, ...]windowsztuple[str, ...]detailsunavailable_reasonr   boolc                H    t          | j        p| j                  o| j         S r   )r1   r.   r/   r0   )selfs    r   	availablezAccountUsageSnapshot.available,   s$    DL0DL11Q$:Q6QQr   )r   r1   )r#   r$   r%   r&   r,   r-   r.   r/   r0   propertyr4   r   r   r   r(   r(   !   s         MMMKKK!E!!!!D.0G0000!G!!!!(,,,,,R R R XR R Rr   r(   valuer!   c                    t          | pd                                          }|sd S |                    dd                              dd                                          S )N _ -)r   stripreplacer,   )r6   cleaneds     r   _title_case_slugr?   1   s\    %+2$$&&G t??3$$,,S#66<<>>>r   r   r   c                   | dv rd S t          | t          t          f          r-t          j        t          |           t
          j                  S t          | t                    r|                                 }|sd S |	                    d          r|d d         dz   }	 t          j
        |          }|j        r|n|                    t
          j                  S # t          $ r Y d S w xY wd S )N>   Nr8   )tzZz+00:00)tzinfo)
isinstanceintfloatr   fromtimestampr   r   r   r<   endswithfromisoformatrD   r=   
ValueError)r6   textdts      r   	_parse_dtrN   8   s    
t%#u&& E%eEllx|DDDD% 
{{}} 	4== 	(9x'D	'--BG22

(,
(G(GG 	 	 	44	4s    <C 
C+*C+rM   r   c                   | sdS |                                  }| t                      z
  }t          |                                          }|dk    rd|                    d           dS t          |d          \  }}|dz  }|dk    rt          |d          \  }}d	| d
| d}n|dk    r
d	| d| d}nd	| d}| d|                    d           dS )Nunknownr   znow (z%Y-%m-%d %H:%M %Z)i  <      zin zd hzh m ()
astimezoner   rF   total_secondsstrftimedivmod)	rM   local_dtdeltarX   hoursremminutesdaysrels	            r   _format_resetrb   K   s    y}}HOE++--..M@x(()<==@@@@t,,JE3RiG{{UB''e$D$$E$$$	'E''W'''G>>X&&':;;>>>>r   FmarkdownsnapshotOptional[AccountUsageSnapshot]rd   r1   	list[str]c          
        | sg S d|rdnd | j          |rdnd }|g}| j        r'|                    d| j         d| j         d           n|                    d| j                    | j        D ]}|j        |j         d}nqt          dt          d	t          |j                  z
                      }t          dt          t          |j                                      }|j         d
| d| d}|j
        r|dt          |j
                   z  }n|j        r|d|j         z  }|                    |           | j        D ]}|                    |           | j        r|                    d| j                    |S )Nu   📈 z**r8   z
Provider: rV   rQ   z: unavailabler   d   z: z% remaining (z% used)u    • resets     • zUnavailable: )r,   r-   appendr)   r.   r   r   maxroundrG   r    rb   r"   r/   r0   )	re   rd   headerlineswindowbase	remainingusedr"   s	            r   render_account_usage_linesrt   _   s    	YX-TT2Yx~Yx?WttUWYYFHE} 7G("3GGx}GGGHHHH5("355666"  &l111DDAuS51D+E+E%EFFGGIq%f&9 : :;;<<DlKKiKKdKKKD? 	,C=#A#ACCCDD] 	,+FM+++DT"  V" DBX%@BBCCCLr   drG   c                    d| dS )N$z,.2fr   )ru   s    r   _fmt_usdrx   {   s    q<<<r   vTypeGuard[float]c                    t          | t          t          f          o)t          | t                     ot	          j        |           S )u"  True iff v is a real numeric value (int or float, not bool, not NaN/Inf).

    Typed as a ``TypeGuard[float]`` so the type checker narrows ``v`` to a real
    number in the positive branch — callers can then do arithmetic / pass it to
    ``_fmt_usd`` without a None-operand warning.
    )rE   rF   rG   r1   mathisfinite)ry   s    r   _is_finite_numr~      s:     a#u&&Wz!T/B/B+BWt}UVGWGWWr   c                   	 ddl m} | t          | dd          sdS t          | dd          }t          | dd          }g }g }|t          |dd          }t          |d	d          }t          |          r|dk    rt          |          rt||k    rn||z
  }t	          d
t          d||z  dz                      }	|                    t          d|	t          |           dt          |           d                     |t          |dd          }
t          |
          r%|                    dt          |
                      t          |dd          }t          |          r%|                    dt          |                      t          |dd          }t          |          r%|                    dt          |                      |vt          |dd          }t          |          r+|dk    r%|                    dt          |                      t          |dd          }|r|                    d|            t          | dd          }|du r|                    d           |s|sdS |                    d ||                       |t          |dd          nd}t          ddt                      d |t          |          t          |          !          S # t          t          f$ r Y dS w xY w)"a  Map a NousPortalAccountInfo into an AccountUsageSnapshot for /usage.

    Shows dollar magnitudes (subscription / top-up / total) + renewal date + a
    portal CTA. When the portal supplies a subscription denominator
    (``monthly_credits``), also emits a subscription-usage window so the renderer
    shows a real ``% used`` gauge; when it's absent (older portals) the view
    gracefully degrades to magnitudes-only. Returns None when there's no usable
    account info to show (fail-open: caller just shows nothing).
    r   )nous_portal_billing_urlN	logged_inFpaid_service_access_infosubscriptionmonthly_creditscredits_remaining              Y@Subscriptionz of  leftr   r   r"   subscription_credits_remainingzSubscription credits: purchased_credits_remainingzTop-up credits: total_usable_creditszTotal usable: rollover_creditsz
Rollover: current_period_endzRenews: paid_service_access-   Status: access depleted — top up to restorezManage / top up: r-   nouszportal-accountNous credits)r)   r*   r+   r,   r-   r.   r/   )hermes_cli.nous_accountr   getattrr~   rl   minrk   r   rx   r(   r   tupleAttributeError	TypeError)account_infor   accesssubr.   r/   r   sub_remainingrs   used_pctsub_credits	purchasedtotal_usablerollover
period_endpaidr-   s                    r   build_nous_credits_snapshotr      s   RCCCCCCw|[%'P'P4'A4HHlND99,. ?%c+<dCCO#C)<dCCM//#a''"=11 (!_44&6sCto/E/M$N$NOO&,%-"*="9"9__x?X?X___     !&*JDQQKk** QO8M8MOOPPP(EtLLIi(( IG(92E2EGGHHH"6+A4HHLl++ JH0F0FHHIII?s$6==Hh'' BHqLL@HX,>,>@@AAA &:DAAJ 86*66777|%:DAA5==NNJKKK 	w 	4R+B+B<+P+PRRSSS-0_wsFD)))$##zz 'NN'NN
 
 
 	
 I&   tts   K* IK* 8A1K* *K?>K?      $@)rd   timeoutr   c                   	 ddl m}  |            }n# t          $ r d}Y nw xY w| t          |          }t	          ||           S 	 ddlm}  |d          pi                     d          }t          |t                    r|
                                sg S n# t          $ r g cY S w xY w	 ddl}ddlm} |j                            d	
          5 }	|	                    |d                              |          }
ddd           n# 1 swxY w Y   t%          |
          }t	          ||           S # t          $ r! t&                              dd           g cY S w xY w)a  Return rendered Nous-credits /usage lines, or [] when there's nothing to show.

    Account-independent of any live agent: gated on "a Nous account is logged in"
    (a cheap local auth-state check), then a wall-clock-bounded portal fetch. Shared
    by the CLI ``_show_usage`` and the TUI ``session.usage`` RPC so both surfaces show
    the same block regardless of session API-call count or resume state. Fail-open:
    any auth/portal hiccup or timeout returns [] (the caller shows nothing).

    Dev override: when HERMES_DEV_CREDITS_FIXTURE selects a fixture state, /usage
    renders from that fixture instead of the real portal (so the block + gauge are
    testable without a live account). Throwaway scaffolding.
    r   )dev_fixture_credits_stateNrc   )get_provider_auth_stater   access_token)get_nous_portal_account_info   )max_workersT)force_freshr   u9   credits ▸ /usage portal fetch/render failed (fail-open))exc_info)agent.credits_trackerr   	Exception_snapshot_from_credits_statert   hermes_cli.authr   getrE   r   r<   concurrent.futuresr   r   futuresThreadPoolExecutorsubmitresultr   loggerdebug)rd   r   r   fixturere   r   tok
concurrentr   poolaccounts              r   nous_credits_linesr      s   CCCCCC++--   /88)(XFFFF;;;;;;&&v..4"99.II3$$ 	 	I	   			!!!!HHHHHH22q2AA 	&Tkk,$ "  fWf%% 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& 	& /w77)(XFFFF    	P[_```				sU    ""AB B)(B)-%D5 ,D
>D5 
DD5 D"D5 5(E E c                   	 | dS g }g }t          | dd          }t          |t          t          f          rt	          j        |          rut          | dd          }t          | dd          }d}|r|r	d| d| d}|                    t          dt          d	t          d
|d
z                      |                     t          | dd          }|r|                    d|            t          | dd          }|r|                    d|            t          | dd          }|r|                    d|            t          | dd          du r|                    d           |s|sdS |                    d           t          ddt                      dt          |          t          |                    S # t          t          f$ r Y dS w xY w)u  Map a header-shaped CreditsState (e.g. a dev fixture) to the /usage snapshot.

    Renders the same magnitudes + monthly-grant % window the portal path produces,
    so HERMES_DEV_CREDITS_FIXTURE can exercise /usage without a live account. The
    *_usd strings are mock display values here (not server balance to compute on);
    the % comes from CreditsState.used_fraction (micros math). Fail-open → None.
    Nused_fractionsubscription_limit_usdsubscription_usdrw    of $r   r   r   r   r   zSubscription credits: $purchased_usdzTop-up credits: $remaining_usdzTotal usable: $paid_accessTFr   u,   (dev fixture — HERMES_DEV_CREDITS_FIXTURE)r   zdev-fixturer   )r)   r*   r+   r,   r.   r/   )r   rE   rF   rG   r|   r}   rk   r   rl   r   r(   r   r   r   r   )	stater.   r/   ufcap_usdsub_usdr"   r   r   s	            r   r   r     s@   /=4,.UOT22b3,'' 	DM",=,= 	e%=tDDGe%7>>GF :7 :9W997999NN"(!$S#eR%Z*@*@!A!A!     %!3T:: 	@NN>W>>???== 	@NN>}>>???== 	>NN<]<<===5-..%77NNJKKK 	w 	4EFFF# zz 'NN'NN
 
 
 	
 I&   tts   F? E'F? /AF? ?GGbase_urlc                    | pd                                                     d          }|sd}|                    d          r|d t          d                    }d|v r|dz   S |dz   S )Nr8   /z%https://chatgpt.com/backend-api/codexz/codexz/backend-apiz/wham/usagez/api/codex/usage)r<   rstriprI   len)r   
normalizeds     r   _resolve_codex_usage_urlr   T  s    .b''))0055J =<
8$$ 2 03x==. 01
##M))***r   c                    t          d          } t                      }|                    d          pi }t          |                    dd          pd                                          pd }d| d          dd	d
}|r||d<   t          j        d          5 }|                    t          |                     dd                    |          }|                                 d d d            n# 1 swxY w Y   |	                                pi }|                    d          pi }g }	dD ]\  }
}|                    |
          pi }|                    d          }|4|	
                    t          |t          |          t          |                    d                                         g }|                    d          pi }|                    d          r|                    d          }t          |t          t          f          r'|
                    dt          |          d           n*|                    d          r|
                    d           t!          ddt#                      t%          |                    d                    t'          |	          t'          |                    S ) NT)refresh_if_expiringtokens
account_idr8   Bearer api_keyapplication/jsonz	codex-cli)AuthorizationAccept
User-AgentzChatGPT-Account-Id      .@r   r   headers
rate_limit))primary_windowSession)secondary_windowWeeklyr   r    r   r   r    creditshas_creditsbalanceCredits balance: $.2f	unlimitedzCredits balance: unlimitedopenai-codex	usage_api	plan_type)r)   r*   r+   r-   r.   r/   )r   r   r   r   r<   httpxClientr   raise_for_statusjsonrk   r   rG   rN   rE   rF   r(   r   r?   r   )creds
token_datar   r   r   clientresponsepayloadr   r.   keyr   rp   rs   r/   r   r   s                    r   _fetch_codex_account_usager   _  s   -$GGGE#%%J^^H%%+FVZZb117R88>>@@HDJ55#355$! G
  3(2$%	d	#	#	# $v::6uyyR7P7PQQ[b:cc!!###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ mmoo#G\**0bJ(*GU 
 

U$$*zz.))<"4[["6::j#9#9::  	
 	
 	
 	
 Gkk)$$*G{{=!! 9++i((gU|,, 	9NNDgDDDEEEE[[%% 	9NN7888::gkk+6677gg   s   AC33C7:C7c                    t                      pd                                } | sd S t          |           st          ddt	                      d          S d|  dddd	d
}t          j        d          5 }|                    d|          }|                                 d d d            n# 1 swxY w Y   |	                                pi }g }d}|D ]\  }}|                    |          pi }	|	                    d          }
|
4t          |
          dk    rt          |
          dz  nt          |
          }|                    t          ||t          |	                    d                                         g }|                    d          pi }|                    d          r|                    d          }|                    d          }|                    d          pd}t          |t          t          f          r<t          |t          t          f          r |                    d|dd|dd|            t          ddt	                      t!          |          t!          |                    S ) Nr8   	anthropicoauth_usage_apizMAnthropic account limits are only available for OAuth-backed Claude accounts.)r)   r*   r+   r0   r   r   zoauth-2025-04-20zclaude-code/2.1.0)r   r   zContent-Typezanthropic-betar   r   r   z)https://api.anthropic.com/api/oauth/usager   ))	five_hourzCurrent session)	seven_dayzCurrent week)seven_day_opusz	Opus week)seven_day_sonnetzSonnet weekutilizationr   ri   	resets_atr   extra_usage
is_enabledused_creditsmonthly_limitcurrencyUSDzExtra usage: r   z / r:   r)   r*   r+   r.   r/   )r   r<   r
   r(   r   r   r   r   r   r   rG   rk   r   rN   rE   rF   r   )tokenr   r   r   r   r.   mappingr   r   rp   utilrs   r/   extrar	  r
  r  s                    r   _fetch_anthropic_account_usager    s   $&&,"3355E t5!! 
# $zzn	
 
 
 	
 +5**$*,) G 
d	#	#	# $v::ISZ:[[!!###$ $ $ $ $ $ $ $ $ $ $ $ $ $ $ mmoo#G(*GG  
 

US!!'Rzz-((<$)$KK1$4$4uT{{S  %++!"6::k#:#:;;  	
 	
 	
 	
 GKK&&,"Eyy yy00		/2299Z((1ElS%L11 	jQTV[P\6]6] 	NNSSSS]SSSSS     ::gg   s   5,B--B14B1r   c           	     	   t          d| |          }t          |                    dd          pd                                          }|sd S t          |                    dd          pd                              d          }| d}| d}d	| d
d}t          j        d          5 }|                    ||          }	|	                                 |	                                pi                     d          pi }
	 |                    ||          }|                                 |                                pi                     d          pi }n# t          $ r i }Y nw xY wd d d            n# 1 swxY w Y   t          |
                    d          pd          }t          |
                    d          pd          }dt          d||z
            dg}g }|                    d          }|                    d          }t          |                    d          pd                                          }|                    d          }t          |t          t          f          rt          |          dk    rt          |t          t          f          rdt          |          cxk    rt          |          k    rn nt          |          }t          |          }||z
  |z  dz  }d|dd|ddg}|r|                    d|            |                    t          d|d                     |          !                     t          |t          t          f          rd"t          |          dd#g}|                    d$          d%f|                    d&          d'f|                    d(          d)ffD ]]\  }}t          |t          t          f          r<t          |          dk    r)|                    dt          |          dd*|            ^|                    d                     |                     t#          dd+t%                      t'          |          t'          |          ,          S )-N
openrouter)	requestedexplicit_base_urlexplicit_api_keyr   r8   r   r   z/creditsz/keyr   r   )r   r   r   r   r   datatotal_creditsr   total_usager   r   limitlimit_remaininglimit_resetusager   ri   rw   r   z
 remainingzresets zAPI key quotarj   r   zAPI key usage: $z totalusage_dailytodayusage_weeklyz	this weekusage_monthlyz
this monthr:   credits_apir  )r   r   r   r<   r   r   r   r   r   r   rG   rl   rE   rF   rk   r   joinr(   r   r   )r   r   runtimer  r   credits_urlkey_urlr   r   credits_respr   key_respkey_datar  r  r/   r.   r  r  r  r  limit_valueremaining_valuer   detail_partsusage_partsr6   r   s                               r   _fetch_openrouter_account_usager/    s   &"   G
 Ir**0b117799E tW[[R006B77>>sCCJ)))K!!!G*5**$ G 
d	#	#	# 	vzz+wz??%%'''$$&&,"11&99?R	zz'7z;;H%%''' -2226::@bHH 	 	 	HHH		 	 	 	 	 	 	 	 	 	 	 	 	 	 	 '++o66=#>>MM229c::KOC][-H$I$IOOOPG(*GLL!!Ell#455Ohll=117R88>>@@KLL!!E53,''
%LL1e55 ''77775<<77777Ell00$6+ELQOQQQkQQQQR 	9 7+ 7 7888%)||L11  	
 	
 	
 %#u&& 	2B%,,BBBBC\\-(('2\\.));7\\/**L9
 	C 	CLE5
 %#u.. C5<<!3C3C""#AuU||#A#A#A%#A#ABBBw||K00111::gg   s8   -AE:AEE:E+(E:*E++E::E>E>)r   r   r)   c                  t          | pd                                                                          }|dv rd S 	 |dk    rt                      S |dk    rt	                      S |dk    rt          ||          S n# t          $ r Y d S w xY wd S )Nr8   >   r8   autocustomr   r   r  )r   r<   lowerr   r  r/  r   )r)   r   r   r   s       r   fetch_account_usager4    s     X^$$**,,2244J+++t''-///$$1333%%28WEEE &   tt4s   A< A< %A< <
B
	B
)r   r   )r6   r!   r   r!   )r6   r   r   r   )rM   r   r   r   )re   rf   rd   r1   r   rg   )ru   rG   r   r   )ry   r   r   rz   )r   rf   )rd   r1   r   rG   r   rg   )r   r   r   r   )r   r!   r   r!   r   rf   )r)   r!   r   r!   r   r!   r   rf   )*
__future__r   loggingr|   dataclassesr   r   r   typingr   r   r	   r   agent.anthropic_adapterr
   r   r   r   r   hermes_cli.runtime_providerr   r   	getLoggerr#   r   r   r   r(   r?   rN   rb   rt   rx   r~   r   r   r   r   r   r  r/  r4  r   r   r   <module>r<     s   " " " " " "   ! ! ! ! ! ! ' ' ' ' ' ' ' ' / / / / / / / / / /  L L L L L L L L Q Q Q Q Q Q Q Q @ @ @ @ @ @ !      		8	$	$& & & & $! ! ! ! ! ! ! ! $R R R R R R R R? ? ? ?   &? ? ? ?( ^c      8   X X X X\ \ \ \~ ,14 / / / / / /d7 7 7 7t+ + + +- - - -`: : : :zE E E EV #!	       r   