
    .cjY                   z   d Z ddlmZ ddlZddlZddlZddlZddlZddlZddl	Z	ddl
mZ ddlmZmZmZ ddlmZ ddlmZ ddlmZ dd	lmZmZmZ dd
lmZmZ   ej!        d          Z" e#            Z$ e#            Z% e#            Z& e#            Z'ddZ(ddZ)e G d d                      Z* G d d          Z+dS )u~  Gateway streaming consumer — bridges sync agent callbacks to async platform delivery.

The agent fires stream_delta_callback(text) synchronously from its worker thread.
GatewayStreamConsumer:
  1. Receives deltas via on_delta() (thread-safe, sync)
  2. Queues them to an asyncio task via queue.Queue
  3. The async run() task buffers, rate-limits, and progressively edits
     a single message on the target platform

Design: Uses the edit transport (send initial message, then editMessageText).
This is universally supported across Telegram, Discord, and Slack.

Credit: jobless0x (#774, #1312), OutThisLife (#798), clicksingh (#697).
    )annotationsN)	dataclass)AnyCallableOptional)BasePlatformAdapter)_custom_unit_to_cp)MEDIA_TAG_CLEANUP_RE)DEFAULT_STREAMING_EDIT_INTERVAL"DEFAULT_STREAMING_BUFFER_THRESHOLDDEFAULT_STREAMING_CURSOR)is_intentional_silence_responseis_partial_silence_markerzgateway.stream_consumertextstrreturnc                d    t          | t                    rd| vr| S |                     dd          S )a  Escape triple-backtick markers so text can be safely wrapped
    inside an outer ``` code block without breaking the fence.

    When reasoning content contains ``` (e.g. the model quotes code
    in its thinking), wrapping it in an outer ``` for display causes
    the inner fence to break the outer block.  Solution: replace each
    `` ``` `` with `` \`\`\` `` before wrapping.

    Returns:
        The input text with each `` ``` `` replaced by `` \`\`\` ``,
        or the input unchanged if no triple-backticks are present.
    ```z\`\`\`)
isinstancer   replacer   s    9/home/ice/.hermes/hermes-agent/gateway/stream_consumer.pyescape_code_fences_for_displayr   :   s9     dC   E$5$5<<{+++    c                @   t          | t                    r| s| S |                     d          dz  dk    r|                     d          dz   } ddl} |j        dd	| |j        
          } |j        dd	|          }|                    d          dz  dk    r| dz   } | S )u  Append a closing `` ``` `` fence and/or `` ` `` if the text has
    orphaned code-block or inline-code markers.

    When model output is truncated mid-code-block (e.g. by token limits
    or a finish_reason="length"), the resulting message has an unclosed
    code fence.  On Discord, Slack, and other platforms this causes
    everything after the orphaned fence to render as a single code block.
    The same problem applies to inline-code spans closed by a single
    backtick: an orphaned `` ` `` makes the remainder of the message
    render as inline code.

    Triple-backtick: count `` ``` `` occurrences.  If odd, append a
    closing fence on its own line.  This is safe because nested
    triple-backtick fences (e.g. a literal `` ``` `` inside a code block)
    are exceedingly rare in model output and, when they do appear, the
    extra closing fence just creates a brief empty code block at the end
    of the message — far less harmful than the entire message being one
    giant code block.

    Single backtick: after balancing triple-backtick fences, strip all
    complete `` ```…``` `` regions and count remaining standalone `` ` ``.
    If odd, append a closing inline-code backtick.  Same trade-off: a
    stray closing backtick may produce a brief empty inline-code span,
    which is far less harmful than the rest of the message being rendered
    as inline code.

    Returns:
        The input text with closing markers appended if needed, or the
        input text unchanged.
    r         

```r   Nz	```.*?``` )flagsz	```[^`]*$`)r   r   countrstripresubDOTALL)r   r%   without_fencess      r   ensure_closed_code_fencesr)   L   s    > dC     zz%1!!{{4  7* IIIRVL"d")DDDNRVL"n==NC  1$))czKr   c                  v    e Zd ZU dZeZded<   eZded<   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<   dS )StreamConsumerConfigz5Runtime config for a single stream consumer instance.floatedit_intervalintbuffer_thresholdr   cursorFboolbuffer_only        fresh_final_after_secondsedit	transportr    	chat_typeN)__name__
__module____qualname____doc__ _DEFAULT_STREAMING_EDIT_INTERVALr-   __annotations__#_DEFAULT_STREAMING_BUFFER_THRESHOLDr/   _DEFAULT_STREAMING_CURSORr0   r2   r4   r6   r7    r   r   r+   r+      s         ??;M;;;;?????+F++++K (+**** I Ir   r+   c                  ~   e Zd ZU dZdZdZdZdZded<   	 	 	 	 	 	 djdkdZ	ddddld!Z
edmd"            Zedmd#            Zednd%            Zedmd&            Zdod(Zdd)dpd-Zdqd/Zdod0Zdrd1Zdsdtd5Zdod6Zedod7            Zdd8dud:Zdrd;Zdod<Zdrd=Zedvd>            Zdod?Zdod@Z e!Z"edvdA            Z#ddBdwdDZ$dxdEZ%dydGZ&edzdJ            Z'ee(fd{dO            Z)d{dPZ*drdQZ+dydRZ,ed|dT            Z-d}dWZ.dmdXZ/dmdYZ0dqdZZ1dod[Z2dod\Z3dqd]Z4dmd^Z5d~d_Z6dd`Z7ddaZ8dqdbZ9dcddddfZ:dodgZ;ddcdhddiZ<dS )GatewayStreamConsumera2  Async consumer that progressively edits a platform message with streamed tokens.

    Usage::

        consumer = GatewayStreamConsumer(adapter, chat_id, config, metadata=metadata)
        # Pass consumer.on_delta as stream_delta_callback to AIAgent
        agent = AIAgent(..., stream_delta_callback=consumer.on_delta)
        # Start the consumer as an asyncio task
        task = asyncio.create_task(consumer.run())
        # ... run agent in thread pool ...
        consumer.finish()  # signal completion
        await task         # wait for final edit
       )z<REASONING_SCRATCHPAD>z<think>z<reasoning>z
<THINKING>z
<thinking>z	<thought>)z</REASONING_SCRATCHPAD>z</think>z</reasoning>z</THINKING>z</thinking>z
</thought>r   r.   _draft_id_counterNadapterr   chat_idr   configOptional[StreamConsumerConfig]metadataOptional[dict]on_new_messageOptional[callable]on_before_finalizeOptional[Callable[[], Any]]initial_reply_to_idOptional[str]run_still_currentOptional[Callable[[], bool]]c	                   || _         || _        |pt                      | _        || _        || _        || _        || _        t          j	                    | _
        d| _        d | _        d | _        t                      | _        t                      | _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        d| _        | j        j        | _        d| _        d| _        g | _        g | _         tC          |dd          du | _"        |pd | _#        d| _$        d| _%        d| _&        d | _'        d| _(        d| _)        d S )	Nr    FTr3         @r   REQUIRES_EDIT_FINALIZEc                     dS )NTr@   r@   r   r   <lambda>z0GatewayStreamConsumer.__init__.<locals>.<lambda>  s     r   )*rE   rF   r+   cfgrI   _on_new_message_on_before_finalize_initial_reply_to_idqueueQueue_queue_accumulated_message_id_message_created_tsset_preview_message_ids_segment_preview_message_ids_already_sent_edit_supported_last_edit_time_last_sent_text_last_edit_overflowed_fallback_final_send_fallback_prefix#_fallback_preserve_partial_messages!_max_fallback_flood_retry_seconds_flood_strikesr-   _current_edit_interval_final_response_sent_final_content_delivered_delivered_commentary_texts_delivered_segment_textsgetattr_adapter_requires_finalize_run_still_current_in_think_block_think_buffer_use_draft_streaming	_draft_id_draft_failures_before_finalize_notified)	selfrE   rF   rG   rI   rK   rM   rO   rQ   s	            r   __init__zGatewayStreamConsumer.__init__   s}    3133   . $6 $7!#(;==*. 59  14! 9<)"#"! &+"$)! " 490 25.&*h&<#$)! ).%68( 46% G5u==E 	' #4"E  % %*!(,  !).&&&r   Ffinalexpect_editsr   r1   r   r   dict | Nonec                   | j         rt          | j                   ni }| j        r
| j        |d<   |rd|d<   |rd|d<   |pdS )a7  Return per-send metadata for stream-created messages.

        Mattermost treats notify-worthy sends as user-visible final content
        when deciding whether a broken thread root may fall back flat.  Preview
        and progress sends keep their original metadata and remain thread-strict.

        ``expect_edits`` preserves the upstream Telegram streaming contract:
        preview messages that may be edited later must stay on the editable
        legacy send path, while fresh/fallback final sends can still use richer
        final-message delivery.
        reply_to_message_idTr   notifyN)rI   dictr[   )r}   r   r   metas       r   _metadata_for_sendz(GatewayStreamConsumer._metadata_for_send2  sf    " '+m;tDM"""$ 	D*.*CD&' 	(#'D  	"!DN|tr   c                    | j         S )z?True if at least one message was sent or edited during the run.)re   r}   s    r   already_sentz"GatewayStreamConsumer.already_sentL  s     !!r   c                    | j         S )zBTrue when the stream consumer delivered the final assistant reply.)rp   r   s    r   final_response_sentz)GatewayStreamConsumer.final_response_sentQ  s     ((r   
str | Nonec                    | j         S )z?The Discord/chat message ID of the last-sent or edited message.)r`   r   s    r   
message_idz GatewayStreamConsumer.message_idV  s     r   c                    | j         S )z|True when the final response content reached the user, even if
        the subsequent cosmetic edit (cursor removal) failed.)rq   r   s    r   final_content_deliveredz-GatewayStreamConsumer.final_content_delivered[  s     ,,r   Nonec                   K   | j         rdS d| _         | j        dS 	 |                                 }t          j        |          r
| d{V  dS dS # t          $ r Y dS w xY w)z?Run the pre-finalize hook exactly once, swallowing hook errors.NT)r|   rZ   inspectisawaitable	Exception)r}   results     r   _notify_before_finalizez-GatewayStreamConsumer._notify_before_finalizea  s      ) 	F)-&#+F	--//F"6**   	 	 	DD	s   0A 
AAfinalizer   contentr   c               N  K   | j         ||d}||d<   | j        ru	 t          j        | j        j                  j        }d|v s+t          d |                                D                       r
| j        |d<   n# t          t          f$ r Y nw xY w | j        j        di | d{V S )z>Edit via the adapter, passing routing metadata when supported.)rF   r   r   r   rI   c              3  F   K   | ]}|j         t          j        j        u V  d S N)kindr   	ParameterVAR_KEYWORD).0params     r   	<genexpr>z6GatewayStreamConsumer._edit_message.<locals>.<genexpr>  sC       / / J'"3"??/ / / / / /r   Nr@   )rF   rI   r   	signaturerE   edit_message
parametersanyvalues	TypeError
ValueError)r}   r   r   r   kwargsparamss         r   _edit_messagez#GatewayStreamConsumer._edit_messageo  s       |$
 
 &z= 		 *4<+DEEP''3 / /!'/ / / , ,' *.F:&z*   .T\.88888888888s   AA8 8BBr   c                   |                      |pd                                          sdS |                                                                 }|k    rdS t          fdg | j        | j        R D                       S )zDReturn True if *text* was already delivered as visible chat content.r    FTc              3  H   K   | ]}|                                 k    V  d S r   )strip)r   senttargets     r   r   z;GatewayStreamConsumer.has_delivered_text.<locals>.<genexpr>  sD       
 
 JJLLF"
 
 
 
 
 
r   )_clean_for_displayr   _visible_prefixr   rr   rs   )r}   r   visible_prefixr   s      @r   has_delivered_textz(GatewayStreamConsumer.has_delivered_text  s    ((44::<< 	5--//5577V##4 
 
 
 
[$:[T=Z[[
 
 
 
 
 	
r   c                D    | j                             t                     dS )z>Finalize the current stream segment and start a fresh message.N)r^   put_NEW_SEGMENTr   s    r   on_segment_breakz&GatewayStreamConsumer.on_segment_break  s    %%%%%r   c                P    |r#| j                             t          |f           dS dS )z7Queue a completed interim assistant commentary message.N)r^   r   _COMMENTARYr}   r   s     r   on_commentaryz#GatewayStreamConsumer.on_commentary  s3     	1KOO[$/00000	1 	1r   rT   timeoutr,   c                    t          j                    }	 | j                            t          |f           n# t
          $ r Y dS w xY w|                    t          dt          |                              S )u  Block the calling (agent worker) thread until everything queued
        before this point has been finalized and delivered to the platform.

        Enqueues a ``(_FLUSH, Event)`` barrier behind any pending deltas /
        commentary / segment breaks.  The async ``run()`` task processes those
        first (FIFO), then handles the barrier — finalizing the current segment
        and setting the event.  Returns True if the flush completed within
        ``timeout``, False on timeout (so the caller continues rather than
        hanging if the consumer task is not running / already finished).

        This is the ordering barrier used before sending a blocking interactive
        prompt (clarify poll): without it, the poll — sent on a separate,
        agent-thread-blocking path — races ahead of buffered prose that is still
        sitting in this queue, so the question lands ABOVE its own explanation.
        Fr3   )r   )		threadingEventr^   r   _FLUSHr   waitmaxr,   )r}   r   evts      r   flush_pending_syncz(GatewayStreamConsumer.flush_pending_sync  sw      o	KOOVSM**** 	 	 	55	xxCw 8 8x999s   !7 
AAc                    | j         }|dS 	  |             dS # t          $ r  t                              dd           Y dS w xY w)z8Fire the on_new_message callback, swallowing any errors.Nzon_new_message callback errorTexc_info)rY   r   loggerdebug)r}   cbs     r   _notify_new_messagez)GatewayStreamConsumer._notify_new_message  sh    !:F	IBDDDDD 	I 	I 	ILL84LHHHHHH	Is   
 &AAc                Z    | dS 	 |                                   dS # t          $ r Y dS w xY w)u  Wake a thread blocked in flush_pending_sync(), swallowing errors.

        Centralised so every loop exit path that consumed a ``_FLUSH`` barrier
        (the normal bottom-of-iteration path AND early ``continue`` paths such
        as the oversized-prose overflow split) reliably sets the event. Missing
        a set is not a deadlock — the caller uses a bounded timeout — but it
        would make the caller stall the full timeout before its blocking send.
        N)rb   r   )flush_events    r   _signal_flushz#GatewayStreamConsumer._signal_flush  sL     F	OO 	 	 	DD	s    
**preserve_no_editr   c                  |r| j         dk    rd S | j        rH|                     | j                                                  }|r| j                            |           d | _         d | _        d| _        d| _        d| _        d| _	        d| _
        t                      | _        d| _        d| _        | j        r8t!          |           xj        dz  c_        t!          |           j        | _        d S d S )N__no_edit__r    Fr   )r`   rh   r   r   rs   appendra   r_   rj   rk   rl   rb   rd   rp   rq   ry   typerD   rz   )r}   r   	finalizeds      r   _reset_segment_statez*GatewayStreamConsumer._reset_segment_state  s    	 0M A AF  	@//0DEEKKMMI @-44Y???#' !$)! "380,/EE)
 %*!(-% $ 	:JJ((A-((!$ZZ9DNNN	: 	:r   c                r    |r| j                             |           dS ||                                  dS dS )u2  Thread-safe callback — called from the agent's worker thread.

        When *text* is ``None``, signals a tool boundary: the current message
        is finalized and subsequent text will be sent as a new message so it
        appears below any tool-progress messages the gateway sent in between.
        N)r^   r   r   r   s     r   on_deltazGatewayStreamConsumer.on_delta  sJ      	$KOOD!!!!!\!!##### \r   c                D    | j                             t                     dS )z#Signal that the stream is complete.N)r^   r   _DONEr   s    r   finishzGatewayStreamConsumer.finish  s    r   c                   | j         |z   }d| _         |r|                                }| j        rd}d}| j        D ]L}|                    |                                          }|dk    r|dk    s||k     r|}t          |          }M|rd| _        |||z   d         }n@t          d | j        D                       }t          |          |k    r|| d         n|| _         dS d}d}| j        D ]	}|                                }	d}
	 |                    |	|
          }|dk    rn|dk    r#| j         p| j        	                    d          }n|d|         }|
                    d          }|dk    r;| j         s| j        	                    d          o|                                dk    }n#||d	z   d                                         dk    }|r|dk    s||k     r|}t          |          }n|d	z   }
|r-| xj        |d|         z  c_        d| _        |||z   d         }nd}| j        D ][}|                                }	t          d	t          |                    D ]'}|	                    |	d|                   r||k    r|}(\|r*| xj        |d|          z  c_        || d         | _         n#| xj        |                     |          z  c_        dS |dS dS )
ab  Add a text delta to the accumulated buffer, suppressing think blocks.

        Uses a state machine that tracks whether we are inside a
        reasoning/thinking block.  Text inside such blocks is silently
        discarded.  Partial tags at buffer boundaries are held back in
        ``_think_buffer`` until enough characters arrive to decide.
        r    r   FNc              3  4   K   | ]}t          |          V  d S r   )len)r   ts     r   r   z?GatewayStreamConsumer._filter_and_accumulate.<locals>.<genexpr>1  s(      !I!IQ#a&&!I!I!I!I!I!Ir   Tr   r   )rx   lowerrw   _CLOSE_THINK_TAGSfindr   r   _OPEN_THINK_TAGSr_   endswithrfindr   range_strip_orphan_close_tags)r}   r   buf	lower_bufbest_idxbest_lentagidxmax_tag	tag_lowersearch_startis_boundary	precedinglast_nl	held_backis                   r   _filter_and_accumulatez,GatewayStreamConsumer._filter_and_accumulate  s     4' X	
 		I# R1 , ,C#..55Cbyyh"nnh#&#&s88 	+0D(h1223CC "!I!I$2H!I!I!IIIG;>s88g;M;MgXYYSVD&F 0 / /C #		I#$L/'nnYEE"99!!88$($5 5 !D#'#4#=#=d#C#C (K
 ),DSD	I&/ood&;&;G&"}})-):%: &I(,(9(B(B4(H(H%@(1(9(9R(? !, /8!.E.K.K.M.MQS.S& "HNNcHnn'*H'*3xxH!'*Qw5/8  %%YhY7%%+/D(h1223CC !"I#4 . .$'IIKK	!&q#c((!3!3 . .A(11)BQB-@@ .Q]],-	. ! 
P))S9*-==))-0)-=** ))T-J-J3-O-OO))Fq  X	 X	 X	 X	 X	r   c                .   d|vr|S |                                 }g }d}|t          |          k     rd}|||dz            dk    r| j        D ]}|                                 }t          |          }||||z            |k    rJ||z   }	|	t          |          k     r,||	         dv r"|	dz  }	|	t          |          k     r
||	         dv "|	}d} n|s |                    ||                    |dz  }|t          |          k     d                    |          S )	u  Remove any close tags from *text* that have no matching open.

        Mirrors ``agent/think_scrubber.py::StreamingThinkScrubber.
        _strip_orphan_close_tags`` so the progressive-display filter
        behaves the same as the post-stream final-response scrubber.
        An orphan close tag is always noise — stripped along with any
        trailing whitespace so surrounding prose flows naturally.
        z</r   Fr   z 	
r   Tr    )r   r   r   r   join)
clsr   
text_loweroutr   matchedr   r   tag_lenjs
             r   r   z.GatewayStreamConsumer._strip_orphan_close_tagst  sG    tKZZ\\
#d))mmG!AE'"d**0 	 	C #		I!)nnG!!AK-0I==K#d))mmQ90D0DFA  #d))mmQ90D0D"& >  

47###Q #d))mm  wws||r   c                    | j         r8| j        s3| xj        |                     | j                   z  c_        d| _         dS dS dS )zFlush any held-back partial-tag buffer into accumulated text.

        Called when the stream ends (got_done) so that partial text that
        was held back waiting for a possible opening tag is not lost.
        r    N)rx   rw   r_   r   r   s    r   _flush_think_bufferz)GatewayStreamConsumer._flush_think_buffer  sb      	$d&: 	$ !>!>t?Q!R!RR!#D		$ 	$ 	$ 	$r   c                  K   t          | j        t                    r| j        j        nt          }|                                 }t          d| || j        j                  z
  dz
            }| 	                                | _
        | j
        r\t          |           xj        dz  c_        t          |           j        | _        t                              d| j        | j                   	 	 |                                 s	 	 	 | j                                        }t          |t(                    r=t	          |          dk    r*|d         t*          u r|                     |d                    l# t.          j        $ r Y dS t2          $ r Y dS w xY wd	}d	}d	}d}d}		 	 | j                                        }|t4          u rd}n|t6          u rd}nt          |t(                    r+t	          |          dk    r|d         t8          u r	|d         }	npt          |t(                    r/t	          |          dk    r|d         t*          u rd}d}|d         }n,|                     |           n# t.          j        $ r Y nw xY w|r|                                  t?          |                      | j!                            r| "                                 d{V  	 	 	 | j                                        }t          |t(                    r=t	          |          dk    r*|d         t*          u r|                     |d                    l# t.          j        $ r Y dS t2          $ r Y dS w xY wtG          j$                    }
|
| j%        z
  }|p|p|	du}| j        j&        s6|p3|| j'        k    r| j!        p!t	          | j!                  | j        j(        k    }d	}|r/|s-|s+|	)tS          |                      | j!                            rd	}|rj| j!        rb || j!                  |k    r| j*        | +                    | j!        ||          }t	          |          dk    r| ,                    | j!        ||          }d	}| j-        }t	          |          dk    }|dd
         D ]2}| .                    |||           d{V }|||k    rd	}d	} nd}|}3|r#|d
         | _!        d| _*        d| _/        d| _0        nd| _*        d| _/        d| _0        tG          j$                    | _%        |rd}| j!        r"| 1                    | j!        d           d{V }|o|| _2        | j2        rd| _3        	 	 	 | j                                        }t          |t(                    r=t	          |          dk    r*|d         t*          u r|                     |d                    l# t.          j        $ r Y dS t2          $ r Y dS w xY w|rd| _*        d	| _4        d| _5        | j!        s(|r|                     |           A || j!                  |k    r| j*        | j6        rto          | j!        ||          }| j!        8                    dd|          }||dz  k     r|}| j!        d|         }| 1                    |dd	           d{V }| j4        s|snW| j!        |d         9                    d          | _!        d| _*        d| _0         || j!                  |k    r| j*        | j6        | j!        }|s|s|	|| j        j        z  }| 1                    ||p||           d{V }tG          j$                    | _%        |r| j!        s| j*        | j:        r| ;                                 d{V  | j!        r| j4        r!| <                    | j!                   d{V  n| j2        rd| _3        n|r| j=        r| j>        rd| _2        d| _3        n| j*        r^| 1                    | j!        d           d{V | _2        | j2        rd| _3        nb| j4        r | <                    | j!                   d{V  n:| j:        s3| 1                    | j!                   d{V | _2        | j2        rd| _3        	 	 	 | j                                        }t          |t(                    r=t	          |          dk    r*|d         t*          u r|                     |d                    l# t.          j        $ r Y dS t2          $ r Y dS w xY w|	[| ?                                 | @                    |	           d{V  tG          j$                    | _%        | ?                                 |rK| j!        r.|s,| j*        r%| j*        dk    r| A                                 d{V  | ?                    d           |r|                     |           t          jC        d           d{V  	# t          jD        $ rl d	}| j!        rI| j*        rB	 t          | 1                    | j!        dd	           d{V           }n# t2          $ r Y nw xY w|r| j2        sd| _2        d| _3        Y n1t2          $ r%}t          F                    d|           Y d}~nd}~ww xY w	 	 | j                                        }t          |t(                    r=t	          |          dk    r*|d         t*          u r|                     |d                    l# t.          j        $ r Y dS t2          $ r Y dS w xY w# 	 	 | j                                        }t          |t(                    r=t	          |          dk    r*|d         t*          u r|                     |d                    l# t.          j        $ r Y w t2          $ r Y w w xY wxY w)z@Async task that drains the queue and edits the platform message.  d   r   zBStream consumer using native-draft transport (chat=%s draft_id=%s)Tr   r   NFr   r   r    r   r   r   is_turn_finalr   r   g?zStream consumer error: %s)Gr   rE   _BasePlatformAdaptermessage_len_fnr   _raw_message_limitr   rX   r0   _resolve_draft_streamingry   r   rD   rz   r   r   rF   rv   r^   
get_nowaittupler   r   r\   Emptyr   r   r   r   r   r   _is_intentional_silence_responser   r_   _suppress_silence_markertime	monotonicrg   r2   ro   r/   _is_partial_silence_markerr`   _truncate_for_stream_split_text_chunksr[   _send_new_chunkra   rh   _send_or_editrp   rq   rj   rk   rf   r	   r   lstripre   r   _send_fallback_finalru   ri   r   _send_commentary#_flush_segment_tail_on_edit_failureasynciosleepCancelledErrorr1   error)r}   _len_fn
_raw_limit_safe_limititemgot_donegot_segment_break	got_flushr   commentary_textnowelapsedshould_editcurrent_update_visiblechunkschunks_deliveredreply_toall_heads_deliveredchunknew_idtail_delivered
_cp_budgetsplit_atokdisplay_text_best_effort_okes                              r   runzGatewayStreamConsumer.run  s      $,(<==DL'' 	 ,,..
#zGGDHO,D,DDsJKK %)$A$A$C$C!$ 	JJ((A-((!$ZZ9DNLLTdn  
O	[* ..00 z4;1133D"4//4IINN Gv--**473334 ;      K !$)!!	""&#{55775=='+H!<//04-!%dE22 "s4yyA~~$q'U`J`J`.21gO!%dE22 "s4yyA~~$q'U[J[J[ )-I04-*.q'K!33D9999 ;   +6  ,,... 8//0ABB   #;;=========R
4;1133D"4//4IINN Gv--**473334 ;      c
 n&& 44 3(3&d2 
 x+ 	"- # D$?? 2 $ 1O t011TX5NN   */&  	($	( .	( (/2//0ABB  0
 #(K N<4#4 N<   122[@@ ,4 "&!:!: -{G" " v;;!++ &*%<%< $ 1;& &F ,1(#'#<.1&kkAo+%+CRC[ . .E+/+?+? % (&. ,@ , , & & & & & &F
  &~81C1C 7< 338 0 %/3,'-HH. 606r
D- 04D,7;D435D00
 04D,7;D435D0/3~/?/?,# #-1N#0 "7;7I7I$($5 8J 8" 8" 2" 2" 2" 2" 2" 2" 9I8[^D5#8 E@D ="t4;1133D"4//4IINN Gv--**473334 ;      I - )/3D,8=D546D1#'#4 ) ( % < ..{;;;    122[@@ ,8 0 9 &8 -{G& &
 $(#4#:#:4J#O#O#jAo55'1H $ 1)8) < $(#5#5!D $6 $ $        4 "B " ",0,=hii,H,O,OPT,U,U)+/(/1,C   122[@@ ,8 0 9D $(#4L# 8,= 8/BY$7 483E3E$"*"?.? '/ 4F 4 4 . . . . . .* ,0>+;+;D( ;( =D,<,HDL^,H"::<<<<<<<<<
 ( 3E4 2E"&";";D<M"N"NNNNNNNNN!6 0E =AD992(E %)$C(E $(#=	(E" 9=D5<@D99!- E ?C>P>P $ 1D ?Q ? ? 9 9 9 9 9 9D5  $8 
S@D = =!%!: S '+&?&?@Q&R&R R R R R R R R!%!3 E>B>P>PQUQb>c>c8c8c8c8c8c8cD5#8 E@D =l4;1133D"4//4IINN Gv--**473334 ;       #.--/////@@@@@@@@@+/>+;+;D(--///  % E )I 6I !,I !,=="FFHHHHHHHHH--t-DDD  4&&{333mD)))))))))w
[*z
 % 	5 	5 	5 $O  T%5 &*"00 -E 1        ' 'OO
 !   D  5t'@ 5,0)04- 	9 	9 	9LL4a88888888	94;1133D"4//4IINN Gv--**473334 ;      4;1133D"4//4IINN Gv--**473334 ;      ss  *h A-E. .F 	FFh $I'  h I' h ?I' h AI' h I' &h 'I96h 8I99Ah A-M M"	M"!M"%G<h #A-W W/"	W/.W/2J(h A-d	 	d(	d('d(+Ch j0(0ij0
i&#j0%i&&j0 m 	j0j+&m +j00m 4A-l! !m 3	m ?m oA-n22oo	ooooc                *    t          j        |           S )u  Strip MEDIA: directives and internal markers from text before display.

        The streaming path delivers raw text chunks that may include
        ``MEDIA:<path>`` tags and ``[[audio_as_voice]]`` directives meant for
        the platform adapter's post-processing.  The actual media files are
        delivered separately via ``_deliver_media_from_response()`` after the
        stream finishes — we just need to hide the raw directives from the
        user.
        )r  "strip_media_directives_for_displayr   s    r   r   z(GatewayStreamConsumer._clean_for_displayZ  s     $FtLLLr   r
  reply_to_idc          	     8  K   |                      |          }|                                s|S 	 | j                            | j        |||                     |d                     d{V }|j        rk|j        rdt          |j                  | _	        | 
                    |           d| _        || _        |                                  t          |j                  S d| _        |S # t          $ r'}t                               d|           |cY d}~S d}~ww xY w)zSend a new message chunk, optionally threaded to a previous message.

        Returns the message_id so callers can thread subsequent chunks.
        Tr   rF   r   r3  rI   NFzStream send chunk error: %s)r   r   rE   sendrF   r   successr   r   r`   _track_preview_ids_from_resultre   rh   r   rf   r   r   r$  )r}   r   rA  r   r   r=  s         r   r  z%GatewayStreamConsumer._send_new_chunkg  sZ      &&t,,zz|| 		<,,$00u40PP	 -        F ~ #&"3 ##&v'8#9#9 33F;;;%)"'+$ ((***6,---',$"" 	 	 	LL6:::	s$   B/C( C( (
D2DDDc                    | j         pd}| j        j        rA|                    | j        j                  r"|dt	          | j        j                            }|                     |          S )z>Return the visible text already shown in the streamed message.r    N)rh   rX   r0   r   r   r   )r}   prefixs     r   r   z%GatewayStreamConsumer._visible_prefix  se    %+8? 	4vtx?? 	42c$(/22223F&&v...r   
final_textc                    | j         p|                                 }|r>|                    |          r)|t          |          d                                         S |S )zAReturn only the part of final_text the user has not already seen.N)rk   r   
startswithr   r  )r}   rI  rH  s      r   _continuation_textz(GatewayStreamConsumer._continuation_text  s_    &@$*>*>*@*@ 	5j++F33 	5c&kkll+22444r   r1  'list[str]'c                   t          |           dk    r| S g }d}| D ]}|d| dnd}|du}|pd}|                    d          D ]n}|                                }|                    d          rC|rd}d}2d}|dd                                         }	|	r|	                                d	         nd}o||z   }
|r|
d
z  }
|}nd}|                    |
           |S )a1  Close orphaned ``` fences at each chunk boundary and reopen on the next.

        When a split lands inside a triple-backtick code block, the head chunk
        would render everything after the orphaned fence as code, and the tail
        chunk's content would lose its code formatting.  Mirror
        ``BasePlatformAdapter.truncate_message``'s contract: close the fence at
        the end of the chunk and reopen it (with the original language tag) at
        the start of the next one, so EVERY delivered chunk is fence-balanced
        on its own.
        r   Nr   r   r    FTrC   r   r   )r   splitr   rK  r   )r1  r  
carry_langr5  rH  in_codelanglinestrippedr   bodys              r   _balance_fences_across_chunksz3GatewayStreamConsumer._balance_fences_across_chunks  s-    v;;!M&*
 	 	E-7-C):))))F ,G#DD)) 	= 	=::<<&&u-- = ="'!"&&qrrl002214<syy{{1~~"E>D "!

!
JJt
r   limitlen_fn'Callable[[str], int]'	list[str]c                    ||           |k    r| gS |}d| v rt          |dz
  |dz  d          }g }| } ||          |k    r|t          |||          }|                    dd|          }||dz  k     r|}|                    |d|                    ||d                             d          } ||          |k    ||r|                    |           t
                              |          S )a#  Split text into reasonably sized chunks for fallback sends.

        Chunks are fence-balanced: a split inside a ``` code block closes the
        fence on the head chunk and reopens it on the tail, so no chunk leaves
        the rest of a message rendering as one giant code block.
        r      r   r   r   r   N)r   r	   r   r   r  rB   rV  )r   rW  rX  split_limitr1  	remainingr8  r9  s           r   r  z(GatewayStreamConsumer._split_text_chunks  s    6$<<5  6M D==ebj%1*a88K	fY+--+I{FKKJ tQ
;;H*/))%MM)IXI.///!()),33D99I fY+--  	%MM)$$$$BB6JJJr   c                   t          | j        dd          }t          |          s|                     |||          S t	          | j        t
                    r ||||          }n |||          }t	          |t          t          f          rt          d |D                       s|                     |||          S t          |          S )ai  Use the adapter's canonical splitter for streaming overflow.

        Platform adapters may add word-boundary, code-fence, table, or
        platform-specific formatting rules.  The consumer must not replace
        those rules with newline-only slicing.  Non-base test doubles and
        legacy adapters retain the historical two-argument call shape.
        truncate_messageNrX  c              3  @   K   | ]}t          |t                    V  d S r   )r   r   )r   r5  s     r   r   z=GatewayStreamConsumer._truncate_for_stream.<locals>.<genexpr>  s=       <
 <
',Juc""<
 <
 <
 <
 <
 <
r   )	rt   rE   callabler  r   r  listr  all)r}   r   rW  rX  truncater1  s         r   r  z*GatewayStreamConsumer._truncate_for_stream  s     4<);TBB!! 	@**4???dl$899 	+XdE&999FFXdE**F&4-00 	@ <
 <
06<
 <
 <
 9
 9
 	@ **4???F||r   c           	     R  K   |                      |          }t          |          }|                     |          }d| _        |                                s|                                r||                                 k    rnt          | j        dd          du rV|                     |           d{V }|dk    rdS d| _	        d| _
        d| _        |dk    rd| _        nd| _        d| _        dS |                                r||                                 k    r|}n| j        r| j        r| j        j        r| j                            | j        j                  ri| j        dt'          | j        j                            }	 |                     | j        |           d{V }|j        r|| _        n# t,          $ r Y nw xY wd| _	        d| _        d| _        dS t          | j        d	d
          }t/          | j        t0                    r| j        j        nt&          }t5          d|dz
            }	|                     ||	|          }
| j        }d}d}d}|
D ]}d}t9          d          D ]}| j                            | j        ||                     d                     d{V }|j        r nT|                      |          }|dk    r8|6tB          "                    d|           tG          j$        |           d{V   |r|j        s@|rd| _	        || _        || _        d| _
         dS d| _	        d| _        d| _        d| _
         dS d}|}|j%        p|}| &                                 |rw||k    rq| j        sj||k    rdt          | j        dd          }|L	  || j        |           d{V  n3# t,          $ r&}tB          "                    d||           Y d}~nd}~ww xY w|| _        d| _	        d| _        d| _        |
d         | _        d| _
        d| _        dS )zSend the final continuation after streaming edits stop working.

        Retries each chunk once on flood-control failures with a short delay.
        F%RESEND_FINAL_ON_EMPTY_STREAM_FALLBACKTN	deliveredr    	ambiguousr   r   MAX_MESSAGE_LENGTH   r  r	  ra  r   r
  rF   r   rI   r   z1Flood control on fallback send, retrying in %.1fsdelete_messagez(Fallback partial cleanup failed (%s): %sr   )'r   r)   rL  rj   r   r   rt   rE   _send_empty_fallback_finalre   rk   rl   rq   rp   r`   rh   rX   r0   r   r   r   rE  r   r   r  r  r   r  r   rD  rF   r   _fallback_flood_retry_delayr   r   r!  r"  r   r   )r}   r   rI  continuationdelivery
clean_textr   	raw_limitr%  
safe_limitr1  stale_message_idlast_message_idlast_successful_chunksent_any_chunkr5  attemptretry_delay	delete_fnr=  s                       r   r  z*GatewayStreamConsumer._send_fallback_final  s     
 ,,T22
 /z::
..z::$)!!!## B	   ""$"6"6"8"888L;  	  "&!@!@!L!LLLLLLL{**F%)"(*%;@8{** 59D11 16D-49D1 !! jD4H4H4J4J&J&J) $,  ,55dhoFF	 "&!56LDHO8L8L7L6L!MJ'+'9'9'+'7$. (: ( ( " " " " " " "> >3=D0$   %)",0)04-DL*>EE	 $,(<==DL'' 	
 i#o..
((z'(RR+)- " .	' .	'EF 88  #|00 L!!4444@@  1          
 > E">>vFFa<<K$;LLK#   "-4444444444  !  *.D&'6D$+@D(,.D)FF &+"#' ')$(*%!N$)!$/B?O $$&&&& 	 O33< 4
**.>EEI$#)DL2BCCCCCCCCCC    LLB(!        +!$(!(,%%bz "38000s*   0F8 8
GG%N= =
O-O((O-c                  K   t          | j                  }| j        r2| j        dk    r'|                    t	          | j                             d}t          d          D ]}	 | j                            | j        || 	                    d                     d{V }nM# t          $ r@}t                              d|           |                     |          rdnd	cY d}~c S d}~ww xY wt          |d
d          r nn|                     |          }|dk    r8|6t                              d|           t!          j        |           d{V  |                     |          rdnd	c S t          |dd          }t          | j        dd          }|Y|D ]V}	|	r|	|k    r	  || j        |	           d{V  $# t          $ r&}t                              d|	|           Y d}~Od}~ww xY wt                      | _        |pd| _        d| _        d| _        d| _        || _        d| _        d| _        |                                  dS )a   Commit a completed answer after Telegram finalization fails.

        Returns ``delivered`` on confirmed success, ``failed`` when the
        gateway can safely retry, and ``ambiguous`` when a timeout may have
        reached the platform already.
        r   Nr   Tr
  rn  z$Empty fallback final send failed: %srj  failedrE  Fr   z=Flood control on empty fallback final send; retrying in %.1fsr   ro  z.Empty fallback preview cleanup failed (%s): %sr    ri  )rb   rd   r`   addr   r   rE   rD  rF   r   r   r   r    _send_failure_may_have_deliveredrt   rq  r!  r"  re   rp   rq   rh   rk   rl   r   )
r}   rI  	stale_idsr   r{  excr|  new_message_idr}  stale_ids
             r   rp  z0GatewayStreamConsumer._send_empty_fallback_final  s&      9::	 	1 0M A AMM#d.//000Qxx 	 	G#|00 L&!4444@@  1          
    CSIII <<SAA"KK!        vy%00 ::6BBK!|| 7S   mK000000000 88@@   !t<<DL*:DAA	 % 
 
 8~#=#=#)DL(;;;;;;;;;;    LLH         -0EE)):]!$(!(,%) "380  """{s6   %<B""
C,,3C'C,'C,'F??
G/	G**G/result_or_excc                    t          | dd          du rdS t          t          | dd          p|                                           }| j        j                                        }d|v pd|v pd|v S )z>Return True for timeout failures where retrying may duplicate.	retryableNTFr$  r   z	timed out)rt   r   r   	__class__r8   )r  r$  names      r   r  z6GatewayStreamConsumer._send_failure_may_have_delivered  s     =+t44<<5GM7D99J]KKQQSS&/5577E!N[E%9NY$=NNr   r   float | Nonec                   |                      |          sdS 	 t          t          |dd          pd          }n# t          t          f$ r d}Y nw xY w|| j        k    rt                              d|           dS t          d|          S )zCReturn a bounded retry delay for a fallback send, if safe to retry.Nretry_afterg      @zCFlood control requests %.1fs; leaving final delivery to the gatewayr3   )	_is_flood_errorr,   rt   r   r   rm   r   r   r   )r}   r   delays      r   rq  z1GatewayStreamConsumer._fallback_flood_retry_delay  s    ##F++ 	4	'&->>E#FFEE:& 	 	 	EEE	4999LLU   43s    : AAc                h    t          |dd          pd}|                                }d|v pd|v pd|v S )zFCheck if a SendResult failure is due to flood control / rate limiting.r$  r    floodzretry afterrate)rt   r   )r}   r   err	err_lowers       r   r  z%GatewayStreamConsumer._is_flood_error  sD    fgr**0bIIKK	)#X}	'AXVyEXXr   c                   | j         j        pd                                }|dk    rdS |dk    rdS t          | j        t
                    sdS 	 | j                            | j         j        pd| j                  }n.# t          $ r! t                              dd           d}Y nw xY w|s3|d	k    r+t                              d
| j        | j         j                   dS dS )u  Decide whether this run should use native draft streaming.

        Honors ``cfg.transport``:
          * ``"edit"``  → never use drafts (legacy progressive-edit path).
          * ``"draft"`` → require draft support; gracefully fall back to edit
            when the adapter declines.  Logs the downgrade at debug.
          * ``"auto"``  → use drafts when the adapter supports them for this
            chat type; otherwise edit.

        Adapter eligibility is checked via
        :meth:`BasePlatformAdapter.supports_draft_streaming`, which considers
        the chat type (e.g. Telegram drafts are DM-only) and platform-version
        gates (e.g. python-telegram-bot 22.6+).
        r5   FoffN)r7   rI   z%supports_draft_streaming probe raisedTr   draftuU   Draft streaming requested but unsupported (chat=%s, type=%r) — falling back to edit)rX   r6   r   r   rE   r  supports_draft_streamingr7   rI   r   r   r   rF   )r}   r6   	supporteds      r   r  z.GatewayStreamConsumer._resolve_draft_streaming  s    X'1688::	55 $,(<== 	5	==(,4 >  II  	 	 	LL@4LPPPIII	  	G##+L$("4  
 5ts   -A< <(B'&B'c                  K   | j         	d| _        dS 	 | j                            | j        | j         || j                   d{V }nJ# t          $ r=}t                              d|           | xj	        dz  c_	        d| _        Y d}~dS d}~ww xY wt          |dd          sCt                              dt          |dd	                     | xj	        dz  c_	        d| _        dS || _        d
S )a  Emit a single animated draft frame for the current accumulated text.

        Returns True when the frame landed.  On any failure, permanently
        disables drafts for the remainder of this run so subsequent frames
        flow through the edit-based path (which can adapt with flood-control
        backoff, etc.).  Drafts have no message_id and clear naturally on
        the client when the response finalizes via a regular sendMessage.
        NF)rF   draft_idr   rI   z=send_draft raised, disabling draft transport for this run: %sr   rE  z@send_draft returned success=False, disabling draft transport: %sr$  unknownT)rz   ry   rE   
send_draftrF   rI   r   r   r   r{   rt   rh   r}   r   r   r=  s       r   _send_draft_framez'GatewayStreamConsumer._send_draft_frame=  sH      >! ).D%5	<22	 3        FF  	 	 	LLOQR     A%  (-D%55555	 vy%00 	LLR33     A%  (-D%5#ts   3A 
B2B

Bc                H  K   | j         s|                                  d{V  | j        p|                                 }| j        }|r>|                    |          r)|t          |          d                                         }|                     |          }|	                                sdS 	 | j
                            | j        || j                   d{V }|j        r	d| _        dS dS # t           $ r&}t"                              d|           Y d}~dS d}~ww xY w)aK  Deliver un-sent tail content before a segment-break reset.

        When an edit fails (flood control, transport error) and a tool
        boundary arrives before the next retry, ``_accumulated`` holds text
        that was generated but never shown to the user. Without this flush,
        the segment reset would discard that tail and leave a frozen cursor
        in the partial message.

        Sends the tail that sits after the last successfully-delivered
        prefix as a new message, and best-effort strips the stuck cursor
        from the previous partial message.
        Nrn  Tz"Segment-break tail flush error: %s)rj   _try_strip_cursorrk   r   r_   rK  r   r  r   r   rE   rD  rF   rI   rE  re   r   r   r$  )r}   visibletailr   r=  s        r   r   z9GatewayStreamConsumer._flush_segment_tail_on_edit_failuree  sv      ( 	+((*********'A4+?+?+A+A  	0tw// 	0G&--//D&&t,,zz|| 	F		B<,, -        F
 ~ *%)"""* * 	B 	B 	BLL=qAAAAAAAAA	Bs   2;C1 1
D!;DD!c                &  K   | j         r| j         dk    rdS |                                 }|r|                                sdS 	 |                     | j         |           d{V }t	          |dd          r	|| _        dS dS # t          $ r Y dS w xY w)u   Best-effort edit to remove the cursor from the last visible message.

        Called when entering fallback mode so the user doesn't see a stuck
        cursor (▉) in the partial message.
        r   Nrk  rE  F)r`   r   r   r   rt   rh   r   )r}   rH  r   s      r   r  z'GatewayStreamConsumer._try_strip_cursor  s        	4#3}#D#DF%%'' 	V\\^^ 	F	--+ .        F vy%00 .'-$$$. . 	 	 	DD	s   :B 
BBc                  K   |                      |          }|                                sdS 	 | j                            | j        || j                   d{V }|j        r.|                                  | j        	                    |           |j        S # t          $ r&}t                              d|           Y d}~dS d}~ww xY w)z6Send a completed interim assistant commentary message.Frn  NzCommentary send error: %s)r   r   rE   rD  rF   rI   rE  r   rr   r   r   r   r$  r  s       r   r  z&GatewayStreamConsumer._send_commentary  s      &&t,,zz|| 	5	<,, -        F ~ > ((*** 077===>! 	 	 	LL4a88855555	s   A(B 
C"CCc                    t          | j        dd          pd}|dk    rdS | j        r| j        dk    rdS | j        dS t	          j                    | j        z
  }||k    S )a  Return True when a long-lived preview should be replaced with a
        fresh final message instead of an edit.

        Conditions:
        - Fresh-final is enabled (``fresh_final_after_seconds > 0``).
        - We have a real preview message id (not the ``__no_edit__`` sentinel
          and not ``None``).
        - The preview has been visible for at least the configured threshold.

        Ported from openclaw/openclaw#72038.
        r4   r3   r   Fr   )rt   rX   r`   ra   r  r  )r}   	thresholdages      r   _should_send_fresh_finalz.GatewayStreamConsumer._should_send_fresh_final  sv     DH&A3GGN3	>>5 	4#3}#D#D5#+5n!99ir   c                >   t          | j        dd          }t          | j        t                    rl	 | j                                        }n4# t
          $ r'}t                              d|           d}Y d}~nd}~ww xY wt          |t                    r||k    r|S |S )a  Per-message length budget (in the adapter's ``message_len_fn`` units)
        before the consumer splits an overflowing reply.

        Adapters with a richer send/draft path (e.g. Telegram rich messages)
        can raise this above ``MAX_MESSAGE_LENGTH`` via
        ``streaming_overflow_limit`` so a reply that fits one rich message isn't
        fragmented at the legacy edit limit.  Falls back to
        ``MAX_MESSAGE_LENGTH`` (4096 default) for everyone else.
        rl  rm  z)streaming_overflow_limit check failed: %sN)	rt   rE   r   r  streaming_overflow_limitr   r   r   r.   )r}   basecapr=  s       r   r  z(GatewayStreamConsumer._raw_message_limit  s     t|%94@@ dl$899 	l;;==   H!LLL #s## d


s   A 
A=A88A=c                    |rK|dk    rGt          |          }| j                            |           | j                            |           dS dS dS )z:Record a real preview message id for finalization cleanup.r   N)r   rc   r  rd   )r}   r   s     r   _track_preview_idz'GatewayStreamConsumer._track_preview_id  sc     	>*55ZJ%))*555-11*=====	> 	>55r   c                V   |                      t          |dd                     t          |dd          pdD ]}|                      |           t          |dd          pi }t          |t                    r/|                    d          pdD ]}|                      |           dS dS )zRecord every message id a send/edit result exposes: the primary id
        plus any continuation ids from an oversized split
        (``continuation_message_ids`` or ``raw_response['message_ids']``).r   Ncontinuation_message_idsr@   raw_responsemessage_ids)r  rt   r   r   get)r}   r   midraws       r   rF  z4GatewayStreamConsumer._track_preview_ids_from_result  s     	wv|TBBCCCF$>EEK 	( 	(C""3''''fnd339rc4   	,..4" , ,&&s++++	, 	,, ,r   c                .   | j         r| j         dk    rdS t          | j        dd          }|dS 	 	  ||| j                  }n# t          $ r  ||          }Y nw xY wn3# t
          $ r&}t                              d|           Y d}~dS d}~ww xY w|du S )u'  Return True when the adapter would rather finalize a streamed reply
        by sending a fresh message and deleting the preview than by editing the
        preview in place — e.g. Telegram, whose ``sendRichMessage`` send path
        currently renders richer markdown than Hermes' MarkdownV2 edit path.

        Returns False when there is no real preview to replace (no message id,
        or the ``__no_edit__`` sentinel), when the adapter doesn't expose the
        hook, or on any error (the consumer then keeps the edit-in-place path).
        r   Fprefers_fresh_final_streamingN)rI   z.prefers_fresh_final_streaming check failed: %sT)r`   rt   rE   rI   r   r   r   r   )r}   r   fnr   r=  s        r   _adapter_prefers_fresh_finalz2GatewayStreamConsumer._adapter_prefers_fresh_final  s      	4#3}#D#D5T\#BDII:5		"D4=999 " " " D"  	 	 	LLI1MMM55555	 ~s5   A A  AA  AA   
B*BBTr  r  c               V  K   t          | j                  }| j        r%| j        dk    r|                    | j                   	 | j                            | j        ||                     d                     d{V }n3# t          $ r&}t          
                    d|           Y d}~dS d}~ww xY wt          |dd          sdS t          |d	d          }t          | j        d
d          }|_|D ]\}|r|dk    s||k    r	  || j        |           d{V  *# t          $ r&}t          
                    d||           Y d}~Ud}~ww xY wt                      | _        |r || _        t          j                    | _        nd| _        d| _        d| _        || _        |rd| _        dS )a_  Send ``text`` as a brand-new message (best-effort delete the old
        preview) so the platform's visible timestamp reflects completion
        time.  Returns True on successful delivery, False on any failure so
        the caller falls back to the normal edit path.

        ``is_turn_final`` is False when finalizing an interim segment at a tool
        boundary (a preamble) rather than the turn-final answer; the
        final-delivery flag is then left unset so the gateway still delivers the
        real answer from the next API call (#29346).

        Ported from openclaw/openclaw#72038.
        r   Tr
  rn  Nz1Fresh-final send failed, falling back to edit: %sFrE  r   ro  z+Fresh-final preview cleanup failed (%s): %s)rb   rc   r`   r  rE   rD  rF   r   r   r   r   rt   r  r  ra   re   rh   rp   )	r}   r   r  r  r   r=  r  r}  r  s	            r   _try_fresh_finalz&GatewayStreamConsumer._try_fresh_final  sI     " 122	 	, 0M A AMM$*+++	<,,00t0<< -        FF
  	 	 	LLLaPPP55555	 vy%00 	5 !t<< DL*:DAA	 % 	 	 8}#<#<N@Z@Z#)DL(;;;;;;;;;;    LLE !       
 %(EE! 	,-D'+~'7'7D$$
  -D'+D$!# 	-(,D%ts0   <B 
B1B,,B1D
E&EEc                &  K   t          | j                  }| j        r%| j        dk    r|                    | j                   t	          | j        dd          }|Y|D ]V}|r|dk    r	  || j        |           d{V  $# t          $ r&}t          	                    d||           Y d}~Od}~ww xY wt                      | _        d| _        d| _
        d| _        d| _        d| _        d| _        t                              d| j                   dS )a  Retract any streamed preview when the final reply is a silence marker.

        The agent chose not to respond and emitted a bare control marker.  Any
        preview message the consumer already put on screen (a partial marker
        flushed on an interval tick, or a preamble before a tool boundary) must
        be removed so the raw marker is never left visible.  Deletion reuses the
        same best-effort ``delete_message`` path as :meth:`_try_fresh_final`.

        Crucially, the delivery flags (``_final_response_sent`` /
        ``_final_content_delivered``) are left **False**: nothing was delivered.
        The gateway then does not mistake the marker for a delivered reply, and
        its own whole-response filter turns the marker into "" so no fallback
        send happens either.  ``_already_sent`` is likewise cleared so the
        gateway's ``already_sent`` short-circuits do not fire.
        r   ro  Nz.Silence-marker preview cleanup failed (%s): %sr    Fz8Suppressed streamed intentional-silence marker (chat=%s))rb   rc   r`   r  rt   rE   rF   r   r   r   r_   rh   re   rp   rq   info)r}   r  r}  r  r=  s        r   r  z.GatewayStreamConsumer._suppress_silence_marker\  sm       122	 	, 0M A AMM$*+++DL*:DAA	 % 	 	 8}#<#<#)DL(;;;;;;;;;;    LLH !       
 %(EE!!"$)!(-%FL	
 	
 	
 	
 	
s   )B
B1B,,B1r  c          	       K   |                      |          }t          |          }|}| j        j        r |                    | j        j        d          }|                                }|sdS |                                sdS d}| j        /| j        j        r#| j        j        |v rt          |          |k     rdS | j        r5|s3| j        ,|| j	        k    rdS | 
                    |           d{V }|rdS d| _        	 | j        2| j        r(|| j	        k    r|r| j        sdS t          t          | j                  d          pdt#          | j        di           v }|                     |          }	|r7|	s|s3|                                 r|                     ||           d{V rdS |                     | j        ||	           d{V }
|
j        rd| _        |                     |
           t#          |
d
d          pd}|rk|
j        rd|
j        | j        k    rTd| _        t5          |
j                  | _        t7          j                    | _        d| _	        |                                  n|| _	        d| _        dS d}|rQ|rO| j        j        rC| j	                             | j        j                  r| !                                |k    rd| _"        t#          |
dd          }tG          |tH                    r|%                    d          rt5          |%                    d          p|
j        p| j                  | _        |%                    d          }tG          |t4                    r+|r)|| _	        || _&        |'                    |          | _(        n | !                                | _&        d| _(        d| _)        d| _        d| _        t#          |
d
d          r|                                  dS | *                    |
          r| xj        dz  c_        tW          | j,        dz  d          | _,        tZ          .                    d| j        | j/        | j,                   |o|ot#          | j        dd          du }| j        | j/        k     r|st7          j                    | _0        dS |rtZ          .                    d           tZ          .                    d| j                   | !                                | _&        d| _)        d| _        d| _        |s| 1                                 d{V  dS dS | j        2                    | j3        || j4        | 5                    |d                     d{V }
|
j        r|
j        r:|
j        | _        t7          j                    | _        |                     |
           nd| _        d| _        || _	        |
j        s'| !                                | _&        d| _)        d| _        |                                  dS d| _        dS # tl          $ r&}tZ          7                    d|           Y d}~dS d}~ww xY w)a\  Send or edit the streaming message.

        Returns True if the text was successfully delivered (sent or edited),
        False otherwise.  Callers like the overflow split loop use this to
        decide whether to advance past the delivered chunk.

        ``finalize`` is True when this is the last edit in a streaming
        sequence.
        r    T   NFr  __dict__r  )r   r   r   r  r@   r   r  partial_overflowrx  delivered_prefixr   r   g      $@u@   Flood control on edit (strike %d/%d), backoff interval → %.1fsFALLBACK_ON_FINAL_EDIT_FLOODz@Turn-final edit hit flood control; entering fallback immediatelyz0Edit failed (strikes=%d), entering fallback moder   rC  r   zStream send/edit error: %s)8r   r)   rX   r0   r   r   r`   r   ry   rh   r  ri   rf   ru   hasattrr   rE   rt   r  r  r  r   rE  re   rF  r   r   r  r  ra   r   rn   r   r   rq   r   r   r  rk   rK  rl   rj   r  minro   r   r   _MAX_FLOOD_STRIKESrg   r  rD  rF   r[   r   r   r$  )r}   r   r   r  visible_without_cursor_visible_stripped_MIN_NEW_MSG_CHARSr:  _has_prefers_hook_prefers_freshr   _continuation_idsimmediate_final_fallbackr  r  r=  s                   r   r  z#GatewayStreamConsumer._send_or_edit  s      &&t,, ).. "&8? 	Y%;%C%CDHOUW%X%X"288::  	4zz|| 	4 $HO %HOt++)**-???4 %		  ( t+++t--d33333333B 
 t &+"E	+' U! t333  4%)%D 4  $tF  T\ 2 2 ?A A E:&t|ZDDE & &*%F%Ft%L%LN $ +$
 %6$ %)$A$A$C$C$ #'"7"7  #8 # #      $  $t#'#5#5#'#3 $!) $6 $ $      F
 ~ J%-1* ;;FCCC -4F<VXZ,[,[,a_a)-8 & 18 !' 1T5E E E9=D6/263D/E/ED,7;~7G7GD435D0 44666637D0./+#t380$A -A !%A !% 4 = =dho N N	A
 !% 4 4 6 6$ > > =AD9'.v~t'L'L%lD99 )l>N>NOa>b>b ) 03 , 0 01B C C !4#)#4!4#'#30 0D,
 0</?/?@R/S/S,)*:C@@ QEU Q7G 48H 5KO??$4L" L" H H 9=8L8L8N8N 5KP H8<D538D015D.&v/I2NN ; $ 8 8 : : :#(5  //77 #" //14//:= $ ;a ?; ;D7 #LL!= $ 3 $ 7 $ ;   !) !*$1!*$+$(L$B$)%" %" &*	%* 5 !% 3d6M M M(@ !N 8<~7G7G 4',u7 " &%D!" !" !" N /   150D0D0F0F-481/4,-1*  8 ;"&"8"8":"::::::::$u !5  $|00 L !6!44&%) 5  	  1           > !( 
5+1+<( 48>3C3C0 ;;FCCCC/4,)-D&+/D(!, 9040D0D0F0F-481 ,9(
 ,,...4 ,1D( 5 	 	 	LL5q99955555	sH   ?$X& %BX& -CX& E4X& 9B4X& /BX& 9C"X& X& &
Y0YY)NNNNNN)rE   r   rF   r   rG   rH   rI   rJ   rK   rL   rM   rN   rO   rP   rQ   rR   )r   r1   r   r1   r   r   )r   r1   )r   r   )r   r   )r   r   r   r   r   r1   )r   r   r   r1   )r   r   r   r   )rT   )r   r,   r   r1   )r   r1   r   r   r   r   r   r   )r   r   rA  rP   r   r1   r   rP   )r   r   )rI  r   r   r   )r1  rM  r   rM  )r   r   rW  r.   rX  rY  r   rZ  )r  r   r   r1   )r   r   r   r  )r   r.   )r   rP   r   r   )r   r   r   r   )r   r   r  r1   r   r1   )r   r   r   r1   r  r1   r   r1   )=r8   r9   r:   r;   r  r   r   rD   r=   r~   r   propertyr   r   r   r   r   r   r   r   r   r   r   staticmethodr   r   r   r   r   classmethodr   r  r>  r
   	_MEDIA_REr   r  r   rL  rV  r   r  r  r  rp  r  rq  r  r  r  r   r  r  r  r  r  rF  r  r  r  r  r@   r   r   rB   rB      s6           
  26#'-1:>-1:>o/ o/ o/ o/ o/h "	     4 " " " X" ) ) ) X)       X  - - - X-
   & 9 9 9 9 9 9:
 
 
 
& & & &1 1 1 1
: : : : :.I I I I    \  @E !: !: !: !: !: !:F
$ 
$ 
$ 
$   c c c cJ    [@
$ 
$ 
$ 
$o o o on %I
M 
M 
M \
M" # # # # # #J/ / / /    $ $ $ \$L  *-K K K K \K>   6m9 m9 m9 m9^F F F FP O O O \O    Y Y Y Y) ) ) )V& & & &PB B B BB   *   <       ,   .> > > >
, 
, 
, 
,   < JN C C C C C CJ)
 )
 )
 )
X .3$R R R R R R R Rr   rB   r  ),r;   
__future__r   r!  r   loggingr\   r%   r   r  dataclassesr   typingr   r   r   gateway.platforms.baser   r  r	   r
   gateway.configr   r<   r   r>   r   r?   gateway.response_filtersr   r  r   r  	getLoggerr   objectr   r   r   r   r   r)   r+   rB   r@   r   r   <module>r     s    # " " " " "     				      ! ! ! ! ! ! * * * * * * * * * * N N N N N N 5 5 5 5 5 5 7 7 7 7 7 7         
       
 
	4	5	5 	vxxfhh 
, , , ,$1 1 1 1h        8|  |  |  |  |  |  |  |  |  | r   