"""Gateway 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. """ from __future__ import annotations import unicodedata from typing import Any # Canonical model-emitted control token for intentional silence. SILENT_REPLY_TOKEN = "NO_REPLY" # Exact whole-response markers that mean "the agent intentionally chose not to # reply". Keep this list small and explicit; arbitrary empty output remains an # error/empty-response path, not silence. LIVE_GATEWAY_SILENT_MARKERS = frozenset({ "[SILENT]", "SILENT", "NO_REPLY", "NO REPLY", }) def _canonical_silence_candidate(text: str) -> str: return " ".join(text.strip().upper().split()) def _strip_edge_silence_punctuation(text: str) -> str: """Strip 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``. """ start = 0 end = len(text) while start < end and text[start] not in "[]" and unicodedata.category(text[start]).startswith("P"): start += 1 while end > start and text[end - 1] not in "[]" and unicodedata.category(text[end - 1]).startswith("P"): end -= 1 return text[start:end].strip() def _canonical_silence_candidates(text: str) -> tuple[str, ...]: exact = _canonical_silence_candidate(text) stripped = _strip_edge_silence_punctuation(text.strip()) if stripped == text.strip(): return (exact,) fallback = _canonical_silence_candidate(stripped) return (exact, fallback) def is_intentional_silence_response(response: Any) -> bool: """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. """ if not isinstance(response, str): return False stripped = response.strip() if not stripped: return False if len(stripped) > 64: return False return any(candidate in LIVE_GATEWAY_SILENT_MARKERS for candidate in _canonical_silence_candidates(stripped)) def is_intentional_silence_agent_result(agent_result: dict | None, response: Any) -> bool: """Silence markers suppress delivery only for successful agent turns.""" if not isinstance(agent_result, dict): return False if agent_result.get("failed"): return False return is_intentional_silence_response(response) def is_partial_silence_marker(text: Any) -> bool: """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. """ if not isinstance(text, str): return False stripped = text.strip() if not stripped or len(stripped) > 64: return False for candidate in _canonical_silence_candidates(stripped): if candidate and any(marker.startswith(candidate) for marker in LIVE_GATEWAY_SILENT_MARKERS): return True return False