
    Pmj                       N   d Z ddlZddlZddlZddlmZmZ ddlmZ ddlm	Z	m
Z
 da ej                    ZdZdZde	d	e
e         fd
Zdddddedededed	ef
dZde	d	efdZdedz  dedz  de	d	efdZeddededz  dedz  de	deded	eeedz  f         fdZefded	efdZdS )u   Retry utilities — jittered backoff for decorrelated retries.

Replaces fixed exponential backoff with jittered delays to prevent
thundering-herd retry spikes when multiple sessions hit the same
rate-limited provider concurrently.
    N)datetimetimezone)parsedate_to_datetime)AnyOptional)g      >@g      N@g     V@      ^@   value_or_headersreturnc                 t   | }|rt          |t          t          t          f          sPt	          |dd          }t          |          r.	  |d          }| |d          }n# t          $ r Y dS w xY w|}ndS |dS t          |t                    rdS t          |t          t          f          rt          dt          |                    S t          |          	                                }|sdS 	 t          dt          |                    S # t          t          f$ r Y nw xY w	 t          |          }n# t          t          f$ r Y dS w xY w|dS |j         |                    t          j                  }t          d|t#          j        t          j                  z
                                            S )a  Parse a ``Retry-After`` value into non-negative seconds.

    Accepts either a raw header value (numeric string / HTTP-date / number)
    or a headers mapping, in which case the ``Retry-After`` key is looked up
    case-insensitively (``.get`` on dict-like objects tries both common
    casings; real HTTP header containers like httpx/requests are already
    case-insensitive).

    Returns:
        Seconds as a ``float`` (negative deltas clamped to ``0.0``), or
        ``None`` when the header is absent or unparseable.
    NgetzRetry-Afterzretry-afterg        )tzinfo)
isinstancestrintfloatgetattrcallable	Exceptionboolmaxstrip	TypeError
ValueErrorr   r   replacer   utcr   nowtotal_seconds)r
   rawgettervaluetextwhens         7/home/thesage/.hermes/hermes-agent/agent/retry_utils.pyparse_retry_after_secondsr%   &   s    C
z#S%/@AAeT**F 		}--="F=11E   ttCC4
{t#t t#U|$$ $3c

###s88>>D t3d$$$z"   $T**z"   tt|t{||8<|00sTHL666EEGGHHHs6   A! !
A/.A/1D D"!D"&D6 6E
Eg      @r   g      ?
base_delay	max_delayjitter_ratioattemptr'   r(   r)   c                n   t           5  t          dz  at          }ddd           n# 1 swxY w Y   t          d| dz
            }|dk    s|dk    r|}nt          |d|z  z  |          }t	          j                    |dz  z  dz  }t          j        |          }|                    d||z            }	||	z   S )aC  Compute a jittered exponential backoff delay.

    Args:
        attempt: 1-based retry attempt number.
        base_delay: Base delay in seconds for attempt 1.
        max_delay: Maximum delay cap in seconds.
        jitter_ratio: Fraction of computed delay to use as random jitter
            range.  0.5 means jitter is uniform in [0, 0.5 * delay].

    Returns:
        Delay in seconds: min(base * 2^(attempt-1), max_delay) + jitter.

    The jitter decorrelates concurrent retries so multiple sessions
    hitting the same provider don't all retry at the same instant.
       Nr   ?      l   yn< l    )	_jitter_lock_jitter_counterr   mintimetime_nsrandomRandomuniform)
r*   r'   r(   r)   tickexponentdelayseedrngjitters
             r$   jittered_backoffr=   Z   s    . 
  1               1gk""H2~~qJ!x-0)<< LNNdZ/0J>D
-

C[[L5011F6>s   &**errorc                     | t          | dd          t          | dd          t          | dd          g}d                    d |D                                                       S )zCBest-effort flattened provider error text for retry classification.messageNbodyresponse c              3   8   K   | ]}|t          |          V  d S )N)r   ).0parts     r$   	<genexpr>z_error_text.<locals>.<genexpr>   s-      DD$43CCII3C3C3C3CDD    )r   joinlower)r>   partss     r$   _error_textrL      sj     	y$''vt$$z4((	E 88DD%DDDDDJJLLLrH   base_urlmodelc                     | pd                                 }|pd                                 }t          |dd          }t          |          }|dk    od|v od|v od|v pd|v S )	aL  Return True for Z.AI Coding Plan transient overload 429s.

    The coding-plan endpoint reports overload as HTTP 429 with body code 1305
    and message "The service may be temporarily overloaded...". Treat only
    that narrow shape specially so ordinary quota/billing 429s still fail fast
    through the existing classifier.
     status_codeNi  zapi.z.ai/api/coding/paas/v4zglm-5.21305ztemporarily overloaded)rJ   r   rL   )rM   rN   r>   base
model_namestatusr"   s          r$   is_zai_coding_overload_errorrV      s     N!!##D+2$$&&JUM400FuD# 	A)T1	A#	A t^?74?	rH   short_attemptsdefault_waitrX   c                    t          |||          s|dfS | |k    r|dfS t          | |z
  dz
  t          t                    dz
            }t          |         }t	          d||d          dfS )u7  Provider-aware rate-limit backoff.

    For most providers this returns ``default_wait`` unchanged. For Z.AI
    Coding Plan GLM-5.2 overloads, keep the first ``short_attempts`` retries on
    the normal short exponential schedule, then switch to progressively longer
    waits (30s → 60s → 90s → 120s, capped) plus light jitter.

    ``attempt`` is 1-based, matching the retry loop's logged attempt number.
    Returns ``(wait_seconds, reason_label)`` where ``reason_label`` is suitable
    for status/log decoration when a provider-specific policy fired.
    )rM   rN   r>   Nzai_coding_overload_shortr,   g?r&   zai_coding_overload_long)rV   r1   len!_ZAI_CODING_OVERLOAD_LONG_BACKOFFr=   )r*   rM   rN   r>   rY   rX   idxr'   s           r$   adaptive_rate_limit_backoffr`      s    ( (eTTT "T!!.  888
g&*C0Q,R,RUV,V
W
WC237J A*
Y\]]]_yyyrH   c                 6    | t          t                    z   dz   S )u  Retry-loop ceiling needed for the full Z.AI overload backoff schedule.

    The adaptive policy runs ``short_attempts`` short retries, then walks the
    long-backoff table one entry per subsequent attempt. The retry loop gives
    up as soon as ``retry_count >= ceiling`` — and that check runs *before* the
    attempt's backoff is computed — so the ceiling must sit one past the final
    long-backoff entry for every long tier to actually execute.

    With the default ``api_max_retries`` (3) equal to ``short_attempts`` (3),
    the loop always gave up before reaching the long tier, leaving the whole
    long-backoff schedule as dead code. Callers extend the ceiling to this
    value for Z.AI Coding overload 429s so the 30/60/90/120s waits run.
    r,   )r]   r^   rW   s    r$   !zai_coding_overload_retry_ceilingrb      s     C ABBBQFFrH   )__doc__r4   	threadingr2   r   r   email.utilsr   typingr   r   r0   Lockr/   r^   #_ZAI_CODING_OVERLOAD_SHORT_ATTEMPTSr   r%   r   r=   r   rL   r   rV   tupler`   rb    rH   r$   <module>rk      sL          ' ' ' ' ' ' ' ' - - - - - -                
 y~ %> ! '( #1I 1I 1I 1I 1I 1In & & && & 	&
 & & & & &RMs Ms M M M McDj t TW \`    6 >z z zz Djz :	z
 z z z 5#*z z z z@ =` G Gc Gdg G G G G G GrH   