
    `gj&                        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y)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-forc                 8    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     D/root/.hermes/venv/lib/python3.12/site-packages/agent/stream_diag.pystream_diag_initr   )   s&     iik r   c                R   |t        |t              sy	 t        |dd      |d<   	 t        |dd      xs i }i }t        | dt              }|D ]'  }	 |j                  |      }|rt        |      dd ||<   ) ||d<   y# t        $ r Y bw xY w# t        $ r Y Jw xY w# t        $ r Y y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)agentdiaghttp_responser   capturedtarget_headersnamevals           r   stream_diag_capture_responser(   :   s     JtT$:%m]DI]-D9?R#% (>@ST" 	Dkk$'%(Xds^HTN	 #Y      sF   A< )B $B4B <	BB	BB BB 	B&%B&c                $   g }| }|Zt        |      dk  rL||v rnG|j                  |       t        |dd      xs t        |dd      }|||u rn|}|t        |      dk  rLg }|D ]  }t        |      j	                         j                  dd      }t        |      dkD  r|dd dz   }|j                  |rt        |      j                   d	| d
nt        |      j                          |rdj                  |      S 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)errorseenlinknxtpartsemsgs          r   flatten_exception_chainrA   Y   s    !#D$)D

s4y1}4<DdK. 
'-3
 ;#+ 
s4y1} E P!flln$$T3/s8c>ds)e#CSQ(()3%q1d1g>N>NO	P
 "'6;;u@DK,@,@@r   )r"   c                  	 	 | j                  |      }|rt        |      dkD  r|dd dz   }	 t	        |      }t        j                         }	d}
d}d}d}d}d}t        |t              r	 t        |j                  d      xs d      }
t        |j                  d      xs d      }t        |j                  d	      xs |	      }t        d|	|z
        }|j                  d
      }|t        dt        |      |z
        }|j                  d      xs i }t        |t              r(|r&dj                  d |j                         D              }|j                  d      t        |j                  d            }t         j#                  d|||t%        | dd      xs dt%        | dd      | j&                  xs d| j(                  xs dt        |      j                  ||||
||||ddnd|d|i       y# t        $ r t        |      }Y w xY w# t        $ r t        |      j                  }Y w xY w# t        $ r Y w xY w# t        $ r t         j+                  dd       Y y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?"
       Nr0   r           -r   r   r   r   r   r.   c              3  0   K   | ]  \  }}| d |   yw)=Nr   ).0kvs      r   	<genexpr>z#log_stream_retry.<locals>.<genexpr>   s#      -'+q!1#Qqc
-s   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smid_tool_call)extrazstream-retry log emit failedT)exc_info)_summarize_api_errorr   r    r3   rA   r7   r8   r   r   r   intr   floatmaxr9   itemsloggerwarningr   providerbase_urldebug)r!   kindr:   attemptmax_attemptsrO   r"   _summary_chain_now_bytes_chunks_elapsed_ttfb_headers_repr_http_status_started_firstr   s                      r   log_stream_retryrj   x   sr   0CD	"11%8H H+~-H	*,U3F
 yy{dD!TXXg.3!4dhhx05A6 ,!7!?4@sD8O4"23%U6]X%=>E((9-3gt,$'HH -/6}}- %M 88M*6#&txx'>#?L 	 E>407CE,a0NN!cNN!cK  $0uSkOc"M2/ 	 	
M  	"5zH	"  	*%[))F	*6  8  D3dCDsv   G1 I  H 1I  *DH1 6A:I  1H	I  H		I  H.*I  -H..I  1	H=:I  <H==I    I#"I#c                  |rdnd}t        | ||||||       | j                  xs d}d}t        |t              rC	 |j	                  d      }	|	/dt        d	t        j                         t        |	      z
        d
d}	 | j                  d| d| dt        |      j                   d| d| d|        | j                  d| d| dt        |      j                          y# t        $ r Y pw xY w# t        $ r Y y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)r\   r:   r]   r^   rO   r"   rY    r   Nz after rD   z.1frN   u   ⚠️ z stream z (r2   u    — reconnecting, retry /zstream retry )rj   rY   r   r   r   rU   r   rT   r   _buffer_statusr7   r8   _touch_activity)
r!   r:   r]   r^   rO   r"   r\   rY   _suffixstarteds
             r   emit_stream_droprs      s8   0 $1fD!# ~~+H G$	hh|,G"#CTYY[5>-I$J3#OqQ
hZxvRU0D0D/EQwi P''.iq@	
 	G9Al^ 4%[))*,	
  		  s%   AC# ?A#C2 #	C/.C/2	C>=C>)r   r   r(   rA   rj   rs   )returnDict[str, Any])r!   r   r"   ru   r#   r   rt   None)r:   BaseExceptionrt   r    )r!   r   r\   r    r:   rw   r]   rS   r^   rS   rO   boolr"   Optional[Dict[str, Any]]rt   rv   )r!   r   r:   rw   r]   rS   r^   rS   rO   rx   r"   ry   rt   rv   )__doc__
__future__r   loggingr   typingr   r   r   r   	getLoggerr8   rW   r   r   r(   rA   rj   rs   __all__r   r   r   <module>r      s   #   , ,			8	$ ">AN &*[D[D [D 	[D
 [D [D [D #[D 
[DJ &*88 8 	8
 8 8 #8 
8vr   