
    Jj                    x    d Z ddlmZ ddlZddlmZ dZ eh d          Zdd
Z	ddZ
ddZddZddZddZdS )zGateway response filtering helpers.

These helpers operate at the gateway boundary: they decide whether a completed
agent turn should be delivered to the chat, not what should be persisted in the
conversation history.
    )annotationsN)AnyNO_REPLY>   NO REPLY[SILENT]SILENTr   textstrreturnc                    d                     |                                                                                                           S )N )joinstripuppersplit)r	   s    >/home/rurouni/.hermes/hermes-agent/gateway/response_filters.py_canonical_silence_candidater      s4    88DJJLL&&((..00111    c                p   d}t          |           }||k     ry| |         dvrot          j        | |                                       d          rB|dz  }||k     r7| |         dvr-t          j        | |                                       d          B||k    r| |dz
           dvrxt          j        | |dz
                                         d          rH|dz  }||k    r=| |dz
           dvr0t          j        | |dz
                                         d          H| ||                                         S )zStrip stray edge punctuation without erasing marker structure.

    Models sometimes emit ``.NO_REPLY`` or ``*NO_REPLY*`` instead of the exact
    marker. Keep square brackets structural so malformed ``[SILENT`` does not
    become ``SILENT``.
    r   z[]P   )lenunicodedatacategory
startswithr   )r	   startends      r   _strip_edge_silence_punctuationr      sD    E
d))C
#++$u+T11k6J4PU;6W6W6b6bcf6g6g1
 #++$u+T11k6J4PU;6W6W6b6bcf6g6g1
++$sQw-t338LTRUXYRY]8[8[8f8fgj8k8k3q ++$sQw-t338LTRUXYRY]8[8[8f8fgj8k8k3c	?  """r   tuple[str, ...]c                    t          |           }t          |                                           }||                                 k    r|fS t          |          }||fS N)r   r   r   )r	   exactstrippedfallbacks       r   _canonical_silence_candidatesr%   /   sV    (..E.tzz||<<H4::<<x+H55H8r   responser   boolc                    t          | t                    sdS |                                 }|sdS t          |          dk    rdS t	          d t          |          D                       S )a  Return True only when ``response`` is exactly a silence marker.

    Substantive prose that merely mentions ``NO_REPLY`` or ``[SILENT]`` must be
    delivered normally.  A blank response is also not silence; blank output is
    handled by the empty-response failure path.
    F@   c              3  (   K   | ]}|t           v V  d S r!   )LIVE_GATEWAY_SILENT_MARKERS).0	candidates     r   	<genexpr>z2is_intentional_silence_response.<locals>.<genexpr>F   s(      qqIy77qqqqqqr   )
isinstancer
   r   r   anyr%   )r&   r#   s     r   is_intentional_silence_responser1   8   st     h$$ u~~H u
8}}ruqqIfgoIpIpqqqqqqr   agent_resultdict | Nonec                |    t          | t                    sdS |                     d          rdS t          |          S )zBSilence markers suppress delivery only for successful agent turns.Ffailed)r/   dictgetr1   )r2   r&   s     r   #is_intentional_silence_agent_resultr8   I   sB    lD)) u!! u*8444r   c                    t          | t                    sdS |                                 }|rt          |          dk    rdS t	          |          D ]'r#t          fdt          D                       r dS (dS )ua  Return True while ``text`` could still resolve to a silence marker.

    The streaming path accumulates the reply delta-by-delta and must decide,
    before the whole response is known, whether to show what it has so far.
    A buffer whose canonical form is a non-empty *prefix* of a silence marker
    (e.g. ``"NO"`` on the way to ``"NO_REPLY"``, or an exact marker that has
    not yet been terminated by stream-end) is held back so a raw marker is
    never edited onto the screen and then belatedly retracted.

    Anything that has already diverged from every marker (ordinary prose) —
    and anything longer than the marker cap — returns False so normal
    streaming resumes immediately.  This is the streaming counterpart to
    :func:`is_intentional_silence_response`, sharing the same marker set and
    canonicalization so the two never drift.
    Fr)   c              3  B   K   | ]}|                               V  d S r!   )r   )r,   markerr-   s     r   r.   z,is_partial_silence_marker.<locals>.<genexpr>h   s1      ddfV..y99ddddddr   T)r/   r
   r   r   r%   r0   r+   )r	   r#   r-   s     @r   is_partial_silence_markerr<   R   s      dC   uzz||H s8}}r))u28<<  	 	ddddHcddddd 	445r   )r	   r
   r   r
   )r	   r
   r   r   )r&   r   r   r'   )r2   r3   r&   r   r   r'   )r	   r   r   r'   )__doc__
__future__r   r   typingr   SILENT_REPLY_TOKEN	frozensetr+   r   r   r%   r1   r8   r<    r   r   <module>rC      s     # " " " " "             
 (i ) ) )   2 2 2 2# # # #    r r r r"5 5 5 5     r   