
    ip                       d Z ddlmZ ddlZddlZddlmZmZ ddlm	Z	m
Z
mZ  ej        e          Z G d dej                  Ze G d d	                      Zg d
Zg dZg dZg dZg dZg dZg dZg dZdgZ eh d          Zg dZddddddd5d"Zdd#d6d*Zd7d+Z dd#d8d,Z!d9d-Z"d:d/Z#d;d1Z$d<d2Z%d=d3Z&d>d4Z'dS )?a  API error classification for smart failover and recovery.

Provides a structured taxonomy of API errors and a priority-ordered
classification pipeline that determines the correct recovery action
(retry, rotate credential, fallback to another provider, compress
context, or abort).

Replaces scattered inline string-matching with a centralized classifier
that the main retry loop in run_agent.py consults for every API failure.
    )annotationsN)	dataclassfield)AnyDictOptionalc                  J    e Zd ZdZdZdZdZdZdZdZ	dZ
d	Zd
ZdZdZdZdZdZdS )FailoverReasonu8   Why an API call failed — determines recovery strategy.authauth_permanentbilling
rate_limit
overloadedserver_errortimeoutcontext_overflowpayload_too_largemodel_not_foundformat_errorthinking_signaturelong_context_tierunknownN)__name__
__module____qualname____doc__r   r   r   r   r   r   r   r   r   r   r   r   r   r        >/home/agentuser/.hermes/hermes-agent/agent/error_classifier.pyr
   r
      sp        BB D%N GJ J!L G *+ (O "L .+ GGGr   r
   c                      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<    e	e
          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 )ClassifiedErrorz>Structured classification of an API error with recovery hints.r
   reasonNOptional[int]status_codezOptional[str]providermodel strmessage)default_factoryzDict[str, Any]error_contextTbool	retryableFshould_compressshould_rotate_credentialshould_fallbackreturnc                @    | j         t          j        t          j        fv S N)r"   r
   r   r   )selfs    r   is_authzClassifiedError.is_authP   s    {~2N4QRRRr   )r1   r,   )r   r   r   r   __annotations__r$   r%   r&   r)   r   dictr+   r-   r.   r/   r0   propertyr5   r   r   r   r!   r!   >   s         HH!%K%%%%"H""""EG$)E$$?$?$?M???? I!O!!!!%*****!O!!!!S S S XS S Sr   r!   )
zinsufficient creditsinsufficient_quotazcredit balancezcredits have been exhaustedztop up your creditszpayment requiredzbilling hard limitzexceeded your current quotazaccount is deactivatedzplan does not include)z
rate limitr   ztoo many requests	throttledzrequests per minuteztokens per minutezrequests per dayztry again inzplease retry afterresource_exhaustedzrate increased too quicklythrottlingexceptionztoo many concurrent requestsservicequotaexceededexception)zusage limitquotazlimit exceededkey limit exceeded)z	try againretryz	resets atzreset inwaitzrequests remainingperiodicwindow)zrequest entity too largezpayload too largezerror code: 413)zcontext lengthzcontext sizezmaximum contextztoken limitztoo many tokenszreduce the lengthzexceeds the limitzcontext windowzprompt is too longzprompt exceeds max length
max_tokenszmaximum number of tokenszexceeds the max_model_lenmax_model_lenzprompt lengthinput is too longzmaximum model lengthzcontext length exceededztruncating inputzslot context
n_ctx_slotu   超过最大长度u   上下文长度rF   zmax input tokenzinput tokenz*exceeds the maximum number of input tokens)zis not a valid modelzinvalid modelzmodel not foundr   zdoes not existzno such modelzunknown modelzunsupported model)	zinvalid api keyinvalid_api_keyauthenticationunauthorized	forbiddenzinvalid tokenztoken expiredztoken revokedzaccess denied	signature>   	ReadErrorPoolTimeoutReadTimeoutConnectErrorTimeoutErrorConnectTimeoutAPITimeoutErrorBrokenPipeErrorConnectionErrorAPIConnectionErrorRemoteProtocolErrorConnectionResetErrorConnectionAbortedErrorServerDisconnectedError)zserver disconnectedzpeer closed connectionzconnection reset by peerzconnection was closedznetwork connection lostzunexpected eofzincomplete chunked readr'   i@ )r%   r&   approx_tokenscontext_lengthnum_messageserror	Exceptionr%   r(   r&   r[   intr\   r]   r1   c               $    t                     t                     j        }t                     t	                    }t                                                     }d}	d}
t          t                    r	                    di           }t          |t                    r/|	                    d          pd                                }	|	                    di           }t          |t                    r|	                    d          pd}t          |t
                    r|
                                r	 ddl}|                    |          }t          |t                    rT|	                    di           }t          |t                    r)|	                    d          pd                                }
n# |j        t          f$ r Y nw xY w|	s)	                    d          pd                                }	|g}|	r|	|vr|                    |	           |
r|
|vr|
|	vr|                    |
           d                    |          pd
                                                                }pd
                                                                }d fd}dk    r dv rdv r |t"          j        dd          S dk    r dv rdv r |t"          j        dd          S t)          |||||||
  
        }||S |rt+          ||          }||S t-          ||||          }||S t/          fdt0          D                       }|rHsF||dz  k    p|dk    p|dk    }|r |t"          j        dd          S  |t"          j        d          S |t6          v s"t           t8          t:          t<          f          r |t"          j        d          S  |t"          j        d          S )u}  Classify an API error into a structured recovery recommendation.

    Priority-ordered pipeline:
      1. Special-case provider-specific patterns (thinking sigs, tier gates)
      2. HTTP status code + message-aware refinement
      3. Error code classification (from body)
      4. Message pattern matching (billing vs rate_limit vs context vs auth)
      5. Transport error heuristics
      6. Server disconnect + large session → context overflow
      7. Fallback: unknown (retryable with backoff)

    Args:
        error: The exception from the API call.
        provider: Current provider name (e.g. "openrouter", "anthropic").
        model: Current model slug.
        approx_tokens: Approximate token count of the current context.
        context_length: Maximum context length for the current model.

    Returns:
        ClassifiedError with reason and recovery action hints.
    r'   r^   r)   metadatarawr   N r"   r
   r1   r!   c                r    | t                    d}|                    |           t          di |S )N)r"   r$   r%   r&   r)   r   )_extract_messageupdater!   )r"   	overridesdefaultsbodyr^   r&   r%   r$   s      r   _resultz#classify_api_error.<locals>._result@  sP    & 't44
 
 		"""*****r     rL   thinkingTFr-   r.     zextra usagezlong contextr%   r&   r[   r\   r]   	result_fn)r[   r\   rq   c              3      K   | ]}|v V  	d S r3   r   .0p	error_msgs     r   	<genexpr>z%classify_api_error.<locals>.<genexpr>  s'      LL1YLLLLLLr   g333333?i    r-   )r"   r
   r1   r!   ) _extract_status_codetyper   _extract_error_body_extract_error_coder(   lower
isinstancer7   getstripjsonloadsJSONDecodeError	TypeErrorappendjoinr
   r   r   _classify_by_status_classify_by_error_code_classify_by_messageany_SERVER_DISCONNECT_PATTERNSr   r   _TRANSPORT_ERROR_TYPESrQ   rU   OSErrorr   )r^   r%   r&   r[   r\   r]   
error_type
error_code_raw_msg	_body_msg_metadata_msg_err_obj	_metadata	_raw_jsonr   _inner
_inner_errpartsprovider_lowermodel_lowerrk   
classifiedis_disconnectis_largerj   rv   r$   s   ```                     @@@r   classify_api_errorr      s   < 'u--Ke%Ju%%D$T**J 5zz!!HIM$ <88GR((h%% 	!i006B==??I Z44I)T** %MM%006B	i-- 	)//2C2C 	#!%I!6!6%fd33 Z)/GR)@)@J)*d;; Z1;	1J1J1Pb0W0W0Y0Y 0)<    	<),,299;;IJE  Yh..Y $h66=PY;Y;Y]###In"++--3355N;B%%''--//K	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+ 	+$ 	s9$$)##w-!
 
 
 	
 	sY&&i''w, 
 
 
 	
 (J#;'%
 
 

 !  ,ZGLL
! &:#%	  J  LLLL0KLLLLLM ?[ ? >C#77g=6;QgUadgUg 	7/ $   
 w~->>>> +++z%,P_ahAi/j/j+w~->>>> 7>)T::::s   BG GG)r]   r$   rv   r   rj   r7   Optional[ClassifiedError]c               \   | dk    r |	t           j        ddd          S | dk    r9dv sdv r |	t           j        ddd          S  |	t           j        dd          S | d	k    rt          |	          S | d
k    rPt	          fdt
          D                       r |	t           j        dd          S  |	t           j        dd          S | dk    r |	t           j        dd          S | dk    r |	t           j        ddd          S | dk    rt          ||||||||		  	        S | dv r |	t           j
        d          S | dv r |	t           j        d          S d| cxk    rdk     rn n |	t           j        dd          S d| cxk    rdk     rn n |	t           j
        d          S dS )zAClassify based on HTTP status code with message-aware refinement.i  FTr-   r/   r0   i  r?   zspending limitr-   r0   i  i  c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z&_classify_by_status.<locals>.<genexpr>  s'      AA!qI~AAAAAAr   i  rn   ro   rl   rp   )  i  ry   )i  i  r   X  N)r
   r   r   _classify_402r   _MODEL_NOT_FOUND_PATTERNSr   r   r   _classify_400r   r   r   )
r$   rv   r   rj   r%   r&   r[   r\   r]   rq   s
    `        r   r   r     s    c y%) 	
 
 
 	
 c9,,0@I0M0M9&)- $	    y 
 
 
 	
 cY	222cAAAA'@AAAAA 	9. $    y* 
 
 
 	
 cy, 
 
 
 	
 cy%%) 	
 
 
 	
 cz4U')%
 
 
 	
 j  y4EEEEj  y2dCCCC kCy' 
 
 
 	
 kCy4EEEE4r   c                     t           fdt          D                       }t           fdt          D                       }|r|r |t          j        ddd          S  |t          j        ddd          S )u  Disambiguate 402: billing exhaustion vs transient usage limit.

    The key insight from OpenClaw: some 402s are transient rate limits
    disguised as payment errors.  "Usage limit, try again in 5 minutes"
    is NOT a billing problem — it's a periodic quota that resets.
    c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z _classify_402.<locals>.<genexpr>  '      HHQ!y.HHHHHHr   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z _classify_402.<locals>.<genexpr>  s'      VV!qI~VVVVVVr   Tr   F)r   _USAGE_LIMIT_PATTERNS_USAGE_LIMIT_TRANSIENT_SIGNALSr
   r   r   )rv   rq   has_usage_limithas_transient_signals   `   r   r   r     s     HHHH2GHHHHHOVVVV7UVVVVV 
/ 
y%%) 	
 
 
 	
 9!%	   r   c                   t           fdt          D                       r |t          j        dd          S t           fdt          D                       r |t          j        dd          S t           fdt          D                       r |t          j        ddd          S t           fd	t          D                       r |t          j	        ddd          S d
}	t          |t                    r|                    di           }
t          |
t                    r;|
                    d          pd
                                                                }	|	s;|                    d          pd
                                                                }	t          |	          dk     p|	dv }||dz  k    p|dk    p|dk    }|r|r |t          j        dd          S  |t          j        dd          S )uH   Classify 400 Bad Request — context overflow, format error, or generic.c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z _classify_400.<locals>.<genexpr>;  '      
>
>a1	>
>
>
>
>
>
>r   Trn   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z _classify_400.<locals>.<genexpr>C  '      
=
=a1	>
=
=
=
=
=
=r   Fr   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z _classify_400.<locals>.<genexpr>L  '      
8
8a1	>
8
8
8
8
8
8r   r   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z _classify_400.<locals>.<genexpr>S  '      
5
5a1	>
5
5
5
5
5
5r   r'   r^   r)      )r^   r'   g?i8 P   )r   _CONTEXT_OVERFLOW_PATTERNSr
   r   r   r   _RATE_LIMIT_PATTERNSr   _BILLING_PATTERNSr   r   r7   r   r   r~   lenr   )rv   r   rj   r%   r&   r[   r\   r]   rq   err_body_msgerr_obj
is_genericr   s   `            r   r   r   ,  s~    
>
>
>
>#=
>
>
>>> 
y+ 
 
 
 	
 
=
=
=
=#<
=
=
=== 
y* 
 
 
 	
 
8
8
8
8#7
8
8
888 
y%%) 	
 
 
 	
 
5
5
5
5#4
5
5
555 
y"%) 	
 
 
 	
 L$ G((7B''gt$$ 	J#KK	228b??AAGGIIL 	G HHY//52<<>>DDFFL\""R'H<=+HJ~33a}u7LaP\_aPaH 
h 
y+ 
 
 
 	
 9#   r   c                   |                                  }|dv r |t          j        dd          S |dv r |t          j        ddd          S |dv r |t          j        dd          S |d	v r |t          j        dd
          S dS )z:Classify by structured error codes from the response body.)r;   r:   rate_limit_exceededT)r-   r/   )r9   billing_not_activepayment_requiredFr   )r   model_not_availableinvalid_modelr   )context_length_exceededmax_tokens_exceededrn   N)r~   r
   r   r   r   r   )r   rv   rq   
code_lowers       r   r   r   y  s     !!##JOOOy%%)
 
 
 	
 UUUy"%) 	
 
 
 	
 PPPy* 
 
 
 	
 GGGy+ 
 
 
 	
 4r   r   c                   t           fdt          D                       r |t          j        dd          S t           fdt          D                       }|rTt           fdt
          D                       }|r |t          j        ddd          S  |t          j        ddd          S t           fdt          D                       r |t          j        ddd          S t           fd	t          D                       r |t          j        ddd          S t           fd
t          D                       r |t          j        dd          S t           fdt          D                       r |t          j        ddd          S t           fdt          D                       r |t          j        dd          S dS )zJClassify based on error message patterns when no status code is available.c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  s'      
?
?a1	>
?
?
?
?
?
?r   Trn   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  r   r   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  s'      "Z"Za1	>"Z"Z"Z"Z"Z"Zr   r   Fc              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  r   r   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  r   r   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  r   r   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  s'      
2
2a1	>
2
2
2
2
2
2r   c              3      K   | ]}|v V  	d S r3   r   rs   s     r   rw   z'_classify_by_message.<locals>.<genexpr>  r   r   r   N)r   _PAYLOAD_TOO_LARGE_PATTERNSr
   r   r   r   r   r   r   r   r   r   _AUTH_PATTERNSr   r   r   )rv   r   r[   r\   rq   r   r   s   `      r   r   r     s    
?
?
?
?#>
?
?
??? 
y, 
 
 
 	
 HHHH2GHHHHHO 
""Z"Z"Z"Z;Y"Z"Z"ZZZ 	9))- $	    y"%) 	
 
 
 	
 
5
5
5
5#4
5
5
555 
y"%) 	
 
 
 	
 
8
8
8
8#7
8
8
888 
y%%) 	
 
 
 	
 
>
>
>
>#=
>
>
>>> 
y+ 
 
 
 	
 
2
2
2
2>
2
2
222 
y%) 	
 
 
 	
 
=
=
=
=#<
=
=
=== 
y* 
 
 
 	
 4r   r#   c                N   | }t          d          D ]}t          |dd          }t          |t                    r|c S t          |dd          }t          |t                    rd|cxk    rdk     rn n|c S t          |dd          pt          |dd          }|||u r n|}dS )	z?Walk the error and its cause chain to find an HTTP status code.   r$   Nstatusd   r   	__cause____context__)rangegetattrr   r`   )r^   current_codecauses        r   rz   rz     s    G1XX  wt44dC   	KKKw$//dC   	SD%6%6%6%63%6%6%6%6%6KKKd33\wwW[7\7\=EW,,E4r   c                    t          | dd          }t          |t                    r|S t          | dd          }|=	 |                                }t          |t                    r|S n# t          $ r Y nw xY wi S )z8Extract the structured error body from an SDK exception.rj   Nresponse)r   r   r7   r   r_   )r^   rj   r   	json_bodys       r   r|   r|     s    5&$''D$ uj$//H	 I)T** !  ! 	 	 	D	Is   *A) )
A65A6c                   | sdS |                      di           }t          |t                    ri|                     d          p|                     d          pd}t          |t                    r(|                                r|                                S |                      d          p|                      d          pd}t          |t          t
          f          r!t          |                                          S dS )z4Extract an error code string from the response body.r'   r^   r   r{   r   )r   r   r7   r(   r   r`   )rj   	error_objr   s      r   r}   r}      s     r"%%I)T""  }}V$$C	f(=(=CdC   	 TZZ\\ 	 ::<<88F;txx55;D$c
## !4yy   2r   c                   |r|                     di           }t          |t                    r[|                     dd          }t          |t                    r0|                                r|                                dd         S |                     dd          }t          |t                    r0|                                r|                                dd         S t          |           dd         S )z+Extract the most informative error message.r^   r)   r'   Nr   )r   r   r7   r(   r   )r^   rj   r   msgs       r   rf   rf   0  s      %HHWb))	i&& 	)--	2..C#s## )		 )yy{{4C4((hhy"%%c3 	%CIIKK 	%99;;tt$$u::dsdr   )r^   r_   r%   r(   r&   r(   r[   r`   r\   r`   r]   r`   r1   r!   )r$   r`   rv   r(   r   r(   rj   r7   r%   r(   r&   r(   r[   r`   r\   r`   r]   r`   r1   r   )rv   r(   r1   r!   )rv   r(   r   r(   rj   r7   r%   r(   r&   r(   r[   r`   r\   r`   r]   r`   r1   r!   )r   r(   rv   r(   r1   r   )
rv   r(   r   r(   r[   r`   r\   r`   r1   r   )r^   r_   r1   r#   )r^   r_   r1   r7   )rj   r7   r1   r(   )r^   r_   rj   r7   r1   r(   )(r   
__future__r   enumloggingdataclassesr   r   typingr   r   r   	getLoggerr   loggerEnumr
   r!   r   r   r   r   r   r   r   r   _THINKING_SIG_PATTERNS	frozensetr   r   r   r   r   r   r   r   rz   r|   r}   rf   r   r   r   <module>r      s  	 	 # " " " " "   ( ( ( ( ( ( ( ( & & & & & & & & & &		8	$	$
! ! ! ! !TY ! ! !L S S S S S S S S4      &   	" 	" 	"    ! ! ! H	 	 	 
 
 
  
 # 
$ 
$ 
$ 
 
       m; m; m; m; m; m;x h h h h h hV   L H H H H H HZ# # # #PV V V Vv   &   "         r   