
    Pmj                        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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SILENTr   [SILENT]textstrreturnc                    d                     |                                                                                                           S )N )joinstripuppersplit)r	   s    >/home/thesage/.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   c                `   t          | t                    sdS |                                 }|sdS dd} ||          rdS d |                                D             }|r$ ||d	                   s ||d
                   rdS |                                                    d          rdS dS )a-  Loose silence matcher for autonomous lanes (cron, webhook).

    Autonomous lanes instruct the agent to emit ``[SILENT]`` when a tick
    produced nothing worth a human's attention, and models reliably bracket
    the marker with a short note explaining why they stayed quiet.  Unlike
    :func:`is_intentional_silence_response` (the interactive-chat rule, which
    demands the response be EXACTLY a marker), this suppresses when a marker
    is the whole response, sits on its own first or last line, or the
    bracketed sentinel opens the response (the documented
    ``[SILENT] No changes detected`` pattern).  A token buried mid-sentence
    in a genuine report is still delivered.

    Shares :data:`LIVE_GATEWAY_SILENT_MARKERS` so the interactive and
    autonomous marker sets can never drift apart.
    Fliner
   r   r'   c                .    t          |           t          v S r!   )r   r+   )r3   s    r   	_is_tokenz1is_autonomous_silence_response.<locals>._is_token_   s    +D115PPPr   Tc                :    g | ]}|                                 |S  )r   )r,   lns     r   
<listcomp>z2is_autonomous_silence_response.<locals>.<listcomp>g   s%    >>>B288::>R>>>r   r   r   )r3   r
   r   r'   )r/   r
   r   
splitlinesr   r   )r&   r#   r5   liness       r   is_autonomous_silence_responser=   I   s      h$$ u~~H uQ Q Q Q y t ?>(--//>>>E ))E!H%% 59)=)= t ~~"":.. t5r   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   )r>   r&   s     r   #is_intentional_silence_agent_resultrD   r   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>   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_markerrH   {   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'   )r>   r?   r&   r   r   r'   )r	   r   r   r'   )__doc__
__future__r   r   typingr   SILENT_REPLY_TOKEN	frozensetr+   r   r   r%   r1   r=   rD   rH   r7   r   r   <module>rN      s    # " " " " "             
 (i ) ) )   2 2 2 2# # # #    r r r r"& & & &R5 5 5 5     r   