
    PL
j&                        d Z ddlmZ ddlZddlZddlmZmZmZm	Z	  ej
        e          ZdZddZddZd dZddd!dZddd"dZg dZdS )#u  Stream diagnostics — per-attempt counters, exception chains, retry logging.

When a streaming chat-completions request dies mid-response, we want to
know why: which Cloudflare edge served the request, which OpenRouter
downstream provider answered, how many bytes/chunks we got before the
drop, the HTTP status, the underlying httpx error class.  These helpers
collect that info and emit it both to ``agent.log`` (full detail) and to
the user-facing status line (compact).

All helpers are extracted from :class:`AIAgent` for cleanliness.
``run_agent`` keeps thin forwarder methods so existing call sites and
tests that patch ``run_agent.<helper>`` keep working.
    )annotationsN)AnyDictListOptional)
zcf-rayzcf-cache-statuszx-openrouter-providerzx-openrouter-modelzx-openrouter-idzx-request-idzx-vercel-idviaserverzx-forwarded-forreturnDict[str, Any]c                 6    t          j                     dddi ddS )zReturn a fresh per-attempt diagnostic dict.

    Mutated in-place by the streaming functions and read from the retry
    block when a stream dies.  Lives on ``request_client_holder`` so it
    survives across the closure boundary.
    Nr   )
started_atfirst_chunk_atchunksbytesheadershttp_status)time     5/home/kuhnn/.hermes/hermes-agent/agent/stream_diag.pystream_diag_initr   )   s+     ikk  r   agentr   diaghttp_responseNonec                   |t          |t                    sdS 	 t          |dd          |d<   n# t          $ r Y nw xY w	 t          |dd          pi }i }t          | dt                    }|D ]D}	 |                    |          }|rt          |          dd         ||<   5# t          $ r Y Aw xY w||d<   dS # t          $ r Y dS w xY w)u  Snapshot interesting headers + HTTP status from the live stream.

    Called once at stream open (before iterating chunks) so the metadata
    survives even if the stream dies before any chunk arrives.  Failures
    are swallowed — diag is best-effort.
    Nstatus_coder   r   _STREAM_DIAG_HEADERSx   )
isinstancedictgetattr	ExceptionSTREAM_DIAG_HEADERSgetstr)r   r   r   r   capturedtarget_headersnamevals           r   stream_diag_capture_responser+   :   s#    JtT$:$:%m]DII]   -D99?R#% (>@STT" 	 	Dkk$'' 4%(XXdsd^HTN   "Y   sB   0 
==/B: 11B#"B: #
B0-B: /B00B: :
CCerrorBaseExceptionr&   c                |   g }| }|mt          |          dk     rZ||v rnU|                    |           t          |dd          pt          |dd          }|||u rn|}|t          |          dk     Zg }|D ]}t          |                                                              dd          }t          |          dk    r|dd         dz   }|                    |rt          |          j         d	| d
nt          |          j                   |rd                    |          nt          |           j        S )u  Return a compact ``Outer(msg) <- Inner(msg) <- ...`` rendering.

    OpenAI SDK wraps httpx errors as ``APIConnectionError`` /
    ``APIError`` and only the wrapper's class is visible at the catch
    site — but the underlying ``RemoteProtocolError`` /
    ``ConnectError`` / ``ReadError`` is what tells us WHY the stream
    died.  Walks ``__cause__`` then ``__context__`` (deduped, max 4
    deep) to surface the chain in one line.
    N   	__cause____context__
       …()z <- )	lenappendr"   r&   stripreplacetype__name__join)r,   seenlinknxtpartsemsgs          r   flatten_exception_chainrE   Y   sY    !#D$)D

s4yy1}}4<<DdK.. 
'-3
 3
 ;#++ 
s4yy1}} E P P!ffllnn$$T3//s88c>>dsd)e#CSNQ(1131111d1gg>NOOOO!&@6;;uDKK,@@r   )r   kindattemptintmax_attemptsmid_tool_callboolOptional[Dict[str, Any]]c                  	 	 |                      |          }n# t          $ r t          |          }Y nw xY w|r t          |          dk    r|dd         dz   }	 t	          |          }n$# t          $ r t          |          j        }Y nw xY wt          j                    }	d}
d}d}d}d}d}t          |t                    r^	 t          |                    d          pd          }
t          |                    d          pd          }t          |                    d	          p|	          }t          d|	|z
            }|                    d
          }| t          dt          |          |z
            }|                    d          pi }t          |t                    r3|r1d                    d |                                D                       }|                    d          "t          |                    d                    }n# t          $ r Y nw xY wt                               d|||t%          | dd          pdt%          | dd          | j        pd| j        pdt          |          j        ||||
||||ddnd|d|i           dS # t          $ r  t                               dd           Y dS w xY w)u  Record a transient stream-drop and retry to ``agent.log``.

    Always logs a structured WARNING so users have a breadcrumb regardless
    of UI verbosity.  Subagents in particular benefit because their
    retries no longer spam the parent's terminal — but the file log keeps
    full detail (provider, error class, attempt, base_url, subagent_id).

    When *diag* is provided (the per-attempt stream-diagnostic dict from
    :func:`stream_diag_init`), the WARNING also captures upstream headers
    (cf-ray, x-openrouter-provider, x-openrouter-id), HTTP status, bytes
    streamed before the drop, and elapsed time on the dying attempt.
    These are the breadcrumbs needed to answer "is one CF edge / one
    downstream provider responsible, or is it random across runs?"
       Nr5   r           -r   r   r   r   r   r3   c              3  *   K   | ]\  }}| d | V  dS )=Nr   ).0kvs      r   	<genexpr>z#log_stream_retry.<locals>.<genexpr>   sA       - -'+q!1

q

- - - - - -r   r   u   Stream %s on attempt %s/%s — retrying. subagent_id=%s depth=%s provider=%s base_url=%s error_type=%s error=%s chain=%s http_status=%s bytes=%d chunks=%d elapsed=%.2fs ttfb=%s upstream=[%s]_subagent_id_delegate_depthz.2fsrJ   )extrazstream-retry log emit failedT)exc_info)_summarize_api_errorr#   r&   r8   rE   r<   r=   r   r    r!   rH   r%   floatmaxr>   itemsloggerwarningr"   providerbase_urldebug)r   rF   r,   rG   rI   rJ   r   _summary_chain_now_bytes_chunks_elapsed_ttfb_headers_repr_http_status_started_firstr   s                      r   log_stream_retryrp   x   s:   0CD	"11%88HH 	" 	" 	"5zzHHH	" 	.H++~-H	*,U33FF 	* 	* 	*%[[)FFF	* y{{dD!! 	TXXg..3!44dhhx005A66 ,!7!7!?4@@sD8O44"233%U6]]X%=>>E((9--3gt,,  $'HH - -/6}}- - - % %M 88M**6#&txx'>'>#?#?L    	 E>4007CE,a00N!cN!cKK $0uOOOOOc"M2/ 	 	
 	
 	
 	
 	
2  D D D3dCCCCCCDsq    J 5J 5%J A+ *J +B	J B8J EH J 
HJ HA9J &KKc               2   |rdnd}t          | ||||||           | j        pd}d}t          |t                    r_	 |                    d          }	|	6dt          d	t          j                    t          |	          z
            d
d}n# t          $ r Y nw xY w	 | 	                    d| d| dt          |          j         d| d| d|            |                     d| d| dt          |          j                    dS # t          $ r Y dS w xY w)u  Emit a single user-visible line for a stream drop+retry.

    Both top-level agents and subagents announce drops in the UI — the
    parent prefixes subagent lines with ``[subagent-N]`` via ``log_prefix``
    so they're easy to attribute.  All cases also write a structured
    WARNING to ``agent.log`` via :func:`log_stream_retry` with the full
    diagnostic detail (subagent_id, provider, base_url, error_type,
    cf-ray, x-openrouter-provider, bytes/chunks, elapsed) for post-hoc
    analysis.

    The user-visible status line is intentionally compact: provider,
    error class, attempt N/M, plus ``after Xs`` when the stream dropped
    mid-flight.  Full diagnostic detail goes to ``agent.log`` only —
    ``hermes logs --level WARNING | grep "Stream drop"`` to inspect.
    zdrop mid tool-calldrop)rF   r,   rG   rI   rJ   r   rb    r   Nz after rO   z.1frY   u   ⚠️ z stream z (r7   u    — reconnecting, retry /zstream retry )rp   rb   r    r!   r%   r^   r   r]   r#   _emit_statusr<   r=   _touch_activity)
r   r,   rG   rI   rJ   r   rF   rb   _suffixstarteds
             r   emit_stream_dropry      s   0 $1<fD!#    ~+H G$ 	hh|,,G"QCTY[[5>>-I$J$JQQQQ 	 	 	D	
@h @ @ @ @U0D @ @w @ @'.@ @1=@ @	
 	
 	
 	,G , ,l , ,%[[), ,	
 	
 	
 	
 	
    s%   AB 
BBA)D 
DD)r$   r   r+   rE   rp   ry   )r
   r   )r   r   r   r   r   r   r
   r   )r,   r-   r
   r&   )r   r   rF   r&   r,   r-   rG   rH   rI   rH   rJ   rK   r   rL   r
   r   )r   r   r,   r-   rG   rH   rI   rH   rJ   rK   r   rL   r
   r   )__doc__
__future__r   loggingr   typingr   r   r   r   	getLoggerr=   r`   r$   r   r+   rE   rp   ry   __all__r   r   r   <module>r      s    # " " " " "   , , , , , , , , , , , ,		8	$	$    "   >A A A AN &*[D [D [D [D [D [DJ &*8 8 8 8 8 8v  r   