
    i
!                        d Z ddlmZ ddlZddlmZmZ ddlmZm	Z	m
Z
 e G d d                      Ze G d d	                      Zd*d+dZd,d-dZ	 d.d/dZd0dZd1dZd2d3d!Zd4d5d&Zd6d(Zd6d)ZdS )7ai  Rate limit tracking for inference API responses.

Captures x-ratelimit-* headers from provider responses and provides
formatted display for the /usage slash command.  Currently supports
the Nous Portal header format (also used by OpenRouter and OpenAI-compatible
APIs that follow the same convention).

Header schema (12 headers total):
    x-ratelimit-limit-requests          RPM cap
    x-ratelimit-limit-requests-1h       RPH cap
    x-ratelimit-limit-tokens            TPM cap
    x-ratelimit-limit-tokens-1h         TPH cap
    x-ratelimit-remaining-requests      requests left in minute window
    x-ratelimit-remaining-requests-1h   requests left in hour window
    x-ratelimit-remaining-tokens        tokens left in minute window
    x-ratelimit-remaining-tokens-1h     tokens left in hour window
    x-ratelimit-reset-requests          seconds until minute request window resets
    x-ratelimit-reset-requests-1h       seconds until hour request window resets
    x-ratelimit-reset-tokens            seconds until minute token window resets
    x-ratelimit-reset-tokens-1h         seconds until hour token window resets
    )annotationsN)	dataclassfield)AnyMappingOptionalc                      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	<   e	dd            Z
e	dd            Ze	dd            ZdS )RateLimitBucketz1One rate-limit window (e.g. requests per minute).r   intlimit	remaining        floatreset_secondscaptured_atreturnc                <    t          d| j        | j        z
            S Nr   )maxr   r   selfs    =/home/ubuntu/.hermes/hermes-agent/agent/rate_limit_tracker.pyusedzRateLimitBucket.used'   s    1dj4>1222    c                @    | j         dk    rdS | j        | j         z  dz  S )Nr   r         Y@)r   r   r   s    r   	usage_pctzRateLimitBucket.usage_pct+   s&    :??3	DJ&%//r   c                h    t          j                     | j        z
  }t          d| j        |z
            S )zCEstimated seconds remaining until reset, adjusted for elapsed time.r   )timer   r   r   )r   elapseds     r   remaining_seconds_nowz%RateLimitBucket.remaining_seconds_now1   s/     )++ 003*W4555r   N)r   r   r   r   )__name__
__module____qualname____doc__r   __annotations__r   r   r   propertyr   r   r!    r   r   r
   r
      s         ;;ENNNNIMK3 3 3 X3 0 0 0 X0
 6 6 6 X6 6 6r   r
   c                      e Zd ZU dZ ee          Zded<    ee          Zded<    ee          Z	ded<    ee          Z
ded<   dZd	ed
<   dZded<   edd            Zedd            ZdS )RateLimitStatez3Full rate-limit state parsed from response headers.)default_factoryr
   requests_minrequests_hour
tokens_mintokens_hourr   r   r    strproviderr   boolc                    | j         dk    S r   )r   r   s    r   has_datazRateLimitState.has_dataC   s    !##r   c                d    | j         st          d          S t          j                    | j        z
  S )Ninf)r6   r   r   r   r   s    r   age_secondszRateLimitState.age_secondsG   s,    } 	 <<y{{T---r   N)r   r4   r"   )r#   r$   r%   r&   r   r
   r-   r'   r.   r/   r0   r   r3   r(   r6   r9   r)   r   r   r+   r+   8   s         ==$)E/$J$J$JLJJJJ%*U?%K%K%KMKKKK"'%"H"H"HJHHHH#(5#I#I#IKIIIIKH$ $ $ X$ . . . X. . .r   r+   valuer   defaultr   r   c                n    	 t          t          |                     S # t          t          f$ r |cY S w xY wN)r   r   	TypeError
ValueErrorr:   r;   s     r   	_safe_intrA   N   sD    5<<   z"   s    44r   r   c                T    	 t          |           S # t          t          f$ r |cY S w xY wr=   )r   r>   r?   r@   s     r   _safe_floatrC   U   s<    U||z"   s    ''r1   headersMapping[str, str]r3   r2   Optional[RateLimitState]c           	     (   d |                                  D             t          d D                       }|sdS t          j                    ddfd
}t           |d           |dd           |d           |dd          |          S )zoParse x-ratelimit-* headers into a RateLimitState.

    Returns None if no rate limit headers are present.
    c                >    i | ]\  }}|                                 |S r)   )lower).0kvs      r   
<dictcomp>z,parse_rate_limit_headers.<locals>.<dictcomp>f   s&    8881qwwyy!888r   c              3  @   K   | ]}|                     d           V  dS )zx-ratelimit-N)
startswith)rJ   rK   s     r   	<genexpr>z+parse_rate_limit_headers.<locals>.<genexpr>i   s.      @@1!,,~..@@@@@@r   Nr1   resourcer2   suffixr   r
   c           
        |  | }t          t                              d|                     t                              d|                     t                              d|                               S )Nzx-ratelimit-limit-zx-ratelimit-remaining-zx-ratelimit-reset-)r   r   r   r   )r
   rA   getrC   )rQ   rR   taglowerednows      r   _bucketz)parse_rate_limit_headers.<locals>._bucketo   s     #6##GKK(BS(B(BCCDD,JS,J,J K KLL%gkk2Ls2L2L&M&MNN	
 
 
 	
r   requestsz-1htokens)r-   r.   r/   r0   r   r3   r1   )rQ   r2   rR   r2   r   r
   )itemsanyr   r+   )rD   r3   has_anyrX   rV   rW   s       @@r   parse_rate_limit_headersr_   \   s     98888G @@@@@@@G t
)++C	
 	
 	
 	
 	
 	
 	
 	
 WZ((gj%0078$$GHe,,   r   nc                z    | dk    r	| dz  ddS | dk    r	| dz  ddS | dk    r	| dz  ddS t          |           S )zIHuman-friendly number: 7999856 -> '8.0M', 33599 -> '33.6K', 799 -> '799'.i@B z.1fMi'  i  K)r2   )r`   s    r   
_fmt_countrd      sj    I~~i-&&&&&F{{e)"""""Ezze)"""""q66Mr   secondsc                    t          dt          |                     }|dk     r| dS |dk     r"t          |d          \  }}|r| d| dn| dS t          |d          \  }}|dz  }|r| d| dn| dS )	zHSeconds -> human-friendly duration: '58s', '2m 14s', '58m 57s', '1h 2m'.r   <   si  zm mzh h)r   r   divmod)re   rh   ri   secrj   	remainders         r   _fmt_secondsrn      s    As7||A2vvwww4xx23!$1!~~s~~~~Q'''1!T??LAyRA)a<<1<<<<Q''')r      pctwidthc                    t          | dz  |z            }t          dt          ||                    }||z
  }dd|z   d|z   dS )uW   ASCII progress bar: [████████░░░░░░░░░░░░] 40%.r   r   [u   █u   ░])r   r   min)rp   rq   filledemptys       r   _barrx      sX    uu$%%FCv&&''FFNE/uv~/uu}////r      labelbucketlabel_widthc                2   |j         dk    r
d| d| dS |j        }t          |j                  }t          |j                   }t          |j                  }t          |j                  }t          |          }d| d| d| d|dd| d| d	| d
| dS )z#Format one bucket as a single line.r   z  <z  (no data) z5.1fz%  /z used  (z left, resets in ))r   r   rd   r   r   rn   r!   rx   )	rz   r{   r|   rp   r   r   r   resetbars	            r   _bucket_liner      s    |q5E5K555555

Cfk""Dv|$$E6+,,I566E
s))CttttttsttSttt$ttttPYttlqttttr   statec           
        | j         sdS | j        }|dk     rd}n+|dk     rt          |           d}nt          |           d}| j        r| j                                        nd}| d| d	d
t          d| j                  t          d| j                  d
t          d| j	                  t          d| j
                  g}g }d| j        fd| j        fd| j	        fd| j
        ffD ]S\  }}|j        dk    rC|j        dk    r8t          |j                  }|                    d| d|j        dd|            T|r*|                    d
           |                    |           d                    |          S )z2Format rate limit state for terminal/chat display.u5   No rate limit data yet — make an API request first.   zjust nowrg   zs agoz agoProviderz Rate Limits (captured z):r1   zRequests/minzRequests/hrz
Tokens/minz	Tokens/hrzrequests/minzrequests/hrz
tokens/minz	tokens/hrr   P   u     ⚠ z at z.0fu   % — resets in 
)r6   r9   r   rn   r3   titler   r-   r.   r/   r0   r   r   r!   appendextendjoin)	r   age	freshnessprovider_labellineswarningsrz   r{   r   s	            r   format_rate_limit_displayr      s   > GFF

C
Qww			r3xx&&&		#C((...	/4~MU^))+++:N ??)???
^U%788]E$788
\5#344[%"344E H	+,	+,	u'(	e'(	 _ _v <! 0B 6 6 !=>>EOO]U]]0@]]]V[]]^^^ RX99Ur   c           
        | j         sdS | j        }| j        }| j        }| j        }g }|j        dk    r%|                    d|j         d|j                    |j        dk    rU|                    dt          |j                   dt          |j                   dt          |j
                   d           |j        dk    r?|                    dt          |j                   dt          |j                              |j        dk    rU|                    d	t          |j                   dt          |j                   dt          |j
                   d           d
                    |          S )z<One-line compact summary for status bars / gateway messages.zNo rate limit data.r   zRPM: r   zRPH: z	 (resets r   zTPM: zTPH: z | )r6   r-   r/   r.   r0   r   r   r   rd   rn   r!   r   )r   rmtmrhthpartss         r   format_rate_limit_compactr      s   > %$$		B		B		B		BE	x!||6R\66BH66777	x!||  AZ55  A  A
288L8L  A  AWcdfd|W}W}  A  A  A  	B  	B  	B	x!||NZ55NN
288L8LNNOOO	x!||  AZ55  A  A
288L8L  A  AWcdfd|W}W}  A  A  A  	B  	B  	B::er   )r   )r:   r   r;   r   r   r   )r   )r:   r   r;   r   r   r   r[   )rD   rE   r3   r2   r   rF   )r`   r   r   r2   )re   r   r   r2   )ro   )rp   r   rq   r   r   r2   )ry   )rz   r2   r{   r
   r|   r   r   r2   )r   r+   r   r2   )r&   
__future__r   r   dataclassesr   r   typingr   r   r   r
   r+   rA   rC   r_   rd   rn   rx   r   r   r   r)   r   r   <module>r      s   , # " " " " "  ( ( ( ( ( ( ( ( ) ) ) ) ) ) ) ) ) ) 6 6 6 6 6 6 6 62 . . . . . . . .*         % % % % %V   
* 
* 
* 
*0 0 0 0 0u u u u u) ) ) )X     r   