
    ,j`                       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d<d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dAd&Z dBd)Z!dCd*Z"dd+d,dDd.Z#dCd/Z$ ed           G d0 d1                      Z%dd+d,dEd2Z&dFd4Z'dCd5Z(dCd6Z)dGd8Z*ddd9dHd;Z+dS )I    )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     8/home/ubuntu/.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, ...]windowstuple[str, ...]detailsunavailable_reasonr   boolc                H    t          | j        p| j                  o| j         S r   )r2   r.   r0   r1   )selfs    r   	availablezAccountUsageSnapshot.available,   s$    DL0DL11Q$:Q6QQr   )r   r2   )r#   r$   r%   r&   r,   r-   r.   r0   r1   propertyr5   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,   )r7   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>   Nr9   )tzZz+00:00)tzinfo)
isinstanceintfloatr   fromtimestampr   r   r   r=   endswithfromisoformatrE   r>   
ValueError)r7   textdts      r   	_parse_dtrO   8   s    
t%#u&& E%eEllx|DDDD% 
{{}} 	4== 	(9x'D	'--BG22

(,
(G(GG 	 	 	44	4s    <C 
C+*C+rN   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   rG   total_secondsstrftimedivmod)	rN   local_dtdeltarY   hoursremminutesdaysrels	            r   _format_resetrc   K   s    y}}HOE++--..M@x(()<==@@@@t,,JE3RiG{{UB''e$D$$E$$$	'E''W'''G>>X&&':;;>>>>r   FmarkdownsnapshotOptional[AccountUsageSnapshot]re   r2   	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**r9   z
Provider: rW   rR   z: unavailabler   d   z: z% remaining (z% used)u    • resets     • zUnavailable: )r,   r-   appendr)   r.   r   r   maxroundrH   r    rc   r"   r0   r1   )	rf   re   headerlineswindowbase	remainingusedr"   s	            r   render_account_usage_linesru   _   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   drH   c                    d| dS )N$z,.2fr   )rv   s    r   _fmt_usdry   {   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.
    )rF   rG   rH   r2   mathisfinite)rz   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 ||                       |                    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_topup_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Top up: z(or run /credits)r-   nouszportal-accountNous credits)r)   r*   r+   r,   r-   r.   r0   )hermes_cli.nous_accountr   getattrr   rm   minrl   r   ry   r(   r   tupleAttributeError	TypeError)account_infor   accesssubr.   r0   r   sub_remainingrt   used_pctsub_credits	purchasedtotal_usablerollover
period_endpaidr-   s                    r   build_nous_credits_snapshotr      s   SAAAAAA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G"7"7"E"EGGHHH*+++-0_wsFD)))$##zz 'NN'NN
 
 
 	
 I&   tts   K? IK? 8BK? ?LL      $@)re   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_stateNrd   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_stateru   hermes_cli.authr   getrF   r   r=   concurrent.futuresr   r   futuresThreadPoolExecutorsubmitresultr   loggerdebug)re   r   r   fixturerf   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_usdrx    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.   r0   )r   rF   rG   rH   r}   r~   rl   r   rm   r   r(   r   r   r   r   )	stater.   r0   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c                  V    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S )CreditsViewuL  Surface-agnostic data for the ``/credits`` command.

    One portal fetch, one parse — consumed identically by the CLI panel, the
    gateway button, and any other money surface. Fail-open: when not logged in
    or the portal is unreachable, ``logged_in`` is False / ``topup_url`` is None
    and callers degrade gracefully.
    r2   r   r   r/   balance_linesNr!   identity_line	topup_urlFdepleted)	r#   r$   r%   __doc__r&   r   r   r   r   r   r   r   r   r   U  sk           OOO%'M''''#'M''''#I####Hr   r   c                   t          d          }	 ddlm}  |d          pi                     d          }t	          |t
                    r|                                s|S n# t          $ r |cY S w xY w	 ddl}ddl	m
}m} |j                            d	
          5 }|                    |d                              |          }	ddd           n# 1 swxY w Y   n.# t          $ r! t                               dd           |cY S w xY w|	t%          |	dd          s|S t'          |	          }
g }|
t)          |
|           }d |D             }t%          |	dd          }t%          |	dd          }g }|r"|                    t          |                     |r|                    d|            |rdd                    |          z   nd}t          dt/          |          | ||	          t%          |	dd          du           S )u  Build the /credits view: balance block + identity line + top-up URL.

    Reuses the same account fetch + snapshot + URL builder as the /usage credits
    block, so the numbers always match. The balance block is the rendered
    snapshot MINUS its trailing top-up/command-hint lines (the /credits surface
    supplies its own affordance). Fail-open → ``CreditsView(logged_in=False)``.
    F)r   r   r   r   r   N)r   r   r   r   Tr   r   u4   credits ▸ /credits portal fetch failed (fail-open)r   r   rd   c                    g | ]R}|                                                     d           )|                                                     d          P|SS )zTop up:z(or run)lstrip
startswith).0lines     r   
<listcomp>z&build_credits_view.<locals>.<listcomp>  sd     
 
 
;;==++I66
 KKMM,,Y77	

 
 
r   emailorg_namezorg zTopping up as  / r   )r   r   r   r   r   )r   r   r   r   rF   r   r=   r   r   r   r   r   r   r   r   r   r   r   r   r   ru   rl   joinr   )re   r   not_logged_inr   r   r   r   r   r   r   rf   r   renderedr   r   whor   s                    r   build_credits_viewr   f  s     %000M;;;;;;&&v..4"99.II3$$ 	! 	!  	!   !!!!	
 	
 	
 	
 	
 	
 	
 	

 22q2AA 	Tkk">DkQQXX Y  G	 	 	 	 	 	 	 	 	 	 	 	 	 	 	    KVZ[[[ gg{EBB*733H  "M-hJJJ
 
 
 
 
 GWd++Ew
D11HC 

3u:: &

$($$%%%<?I%

377TMM**#''00"7>>%G   sH   AA$ $A32A37'C" ,C
C" CC" CC" "(DDbase_urlc                    | pd                                                     d          }|sd}|                    d          r|d t          d                    }d|v r|dz   S |dz   S )Nr9   /z%https://chatgpt.com/backend-api/codexz/codexz/backend-apiz/wham/usagez/api/codex/usage)r=   rstriprJ   len)r   
normalizeds     r   _resolve_codex_usage_urlr     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_idr9   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.   r0   )r   r   r   r   r=   httpxClientr   raise_for_statusjsonrl   r   rH   rO   rF   rG   r(   r   r@   r   )creds
token_datar   r   r   clientresponsepayloadr   r.   keyr   rq   rt   r0   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 ) Nr9   	anthropicoauth_usage_apizMAnthropic account limits are only available for OAuth-backed Claude accounts.)r)   r*   r+   r1   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   rj   	resets_atr  extra_usage
is_enabledused_creditsmonthly_limitcurrencyUSDzExtra usage: r	  r   r;   r)   r*   r+   r.   r0   )r   r=   r
   r(   r   r  r  r   r  r  rH   rl   r   rO   rF   rG   r   )tokenr   r  r  r  r.   mappingr  r   rq   utilrt   r0   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   r9   r   r   z/creditsz/keyr   r   )r   r   r   r   r   datatotal_creditsr   total_usager  r	  limitlimit_remaininglimit_resetusager   rj   rx   r   z
 remainingzresets zAPI key quotark   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   rH   rm   rF   rG   rl   r   r   r(   r   r   )r   r   runtimer)  r   credits_urlkey_urlr   r  credits_respr  key_respkey_datar4  r5  r0   r.   r6  r7  r8  r9  limit_valueremaining_valuer   detail_partsusage_partsr7   r   s                               r   _fetch_openrouter_account_usagerI  $  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 )Nr9   >   r9   autocustomr  r  r/  )r   r=   lowerr  r-  rI  r   )r)   r   r   r   s       r   fetch_account_usagerN  l  s     X^$$**,,2244J+++t''-///$$1333%%28WEEE &   tt4s   A< A< %A< <
B
	B
)r   r   )r7   r!   r   r!   )r7   r   r   r   )rN   r   r   r   )rf   rg   re   r2   r   rh   )rv   rH   r   r   )rz   r   r   r{   )r   rg   )re   r2   r   rH   r   rh   )re   r2   r   rH   r   r   )r   r   r   r   )r   r!   r   r!   r   rg   )r)   r!   r   r!   r   r!   r   rg   ),
__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@   rO   rc   ru   ry   r   r   r   r   r   r   r   r  r-  rI  rN  r   r   r   <module>rV     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 $         ,14 C C C C C CL+ + + +- - - -`: : : :zE E E EV #!	       r   