Files
hermes-agent/tools/ansi_strip.py
T
Teknium 5442a2a371 refactor(tools): trim small tool modules (docstrings, dead constant, prefix table)
- audio_container: CONTAINER_TO_EXT built from one tuple; fixed-prefix sniffs table-driven.
- apply_layout_tool: dead _TIMEOUT_NOTE indirection inlined.
- Module/function docstrings compacted by hand; regexes, byte patterns, schemas and
  registry.register kwargs byte-identical (SCHEMA_SAME verified).
2026-09-02 22:36:16 -07:00

66 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Strip ANSI escape sequences from subprocess output so they never reach the
model's context (models otherwise copy them into file writes). Covers full
ECMA-48: CSI, OSC (BEL/ST), DCS/SOS/PM/APC, nF, Fp/Fe/Fs and 8-bit C1 controls."""
import re
_ANSI_ESCAPE_RE = re.compile(
r"\x1b"
r"(?:"
r"\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]" # CSI sequence
r"|\][\s\S]*?(?:\x07|\x1b\\)" # OSC (BEL or ST terminator)
r"|[PX^_][\s\S]*?(?:\x1b\\)" # DCS/SOS/PM/APC strings
r"|[\x20-\x2f]+[\x30-\x7e]" # nF escape sequences
r"|[\x30-\x7e]" # Fp/Fe/Fs single-byte
r")"
r"|\x9b[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]" # 8-bit CSI
r"|\x9d[\s\S]*?(?:\x07|\x9c)" # 8-bit OSC
r"|[\x80-\x9f]", # Other 8-bit C1 controls
re.DOTALL,
)
# Fast-path checks: skip the full regex when no candidate bytes are present.
_HAS_ESCAPE = re.compile(r"[\x1b\x80-\x9f]")
_HAS_CONTROL = re.compile(r"[\x00-\x08\x0b-\x1f\x7f-\x9f]")
_HAS_UNICODE_TAG = re.compile(r"[\U000E0000-\U000E007F]")
# C0 controls (minus tab/newline/CR) plus DEL: they survive strip_ansi() (which only
# removes *sequences*) but are dangerous echoed to a terminal (BEL, backspace, NUL).
_CONTROL_CHARS_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]")
# Unicode TAG chars (U+E0000–U+E007F) render as nothing but LLM tokenizers see them:
# the "ASCII smuggling" injection channel. Emoji tag sequences (TR51: U+1F3F4 base +
# tag spec + U+E007F CANCEL TAG, e.g. Scotland/Wales flags) are the only legit use.
_UNICODE_TAG_SUB_RE = re.compile(
r"(\U0001F3F4[\U000E0020-\U000E007E]+\U000E007F)" # valid emoji tag seq (kept)
r"|[\U000E0000-\U000E007F]" # any other tag char (stripped)
)
def strip_ansi(text: str) -> str:
"""Remove ANSI escape sequences; clean text passes through unchanged (fast path)."""
if not text or not _HAS_ESCAPE.search(text):
return text
return _ANSI_ESCAPE_RE.sub("", text)
def sanitize_display_text(text: str) -> str:
"""Sanitize stored/untrusted text before echoing it to a terminal: strips ANSI
sequences AND bare control chars, keeping only newlines/tabs (CRs become newlines
so ``\\r``-overwrite spoofing can't hide content). Rich's ``Text()`` does NOT
neutralize raw escape bytes, so a replayed ``/resume`` message must not be able
to clear the screen, retitle the window, or restyle UI."""
if not text or not _HAS_CONTROL.search(text):
return text
text = strip_ansi(text)
if "\r" in text:
text = text.replace("\r\n", "\n").replace("\r", "\n")
return _CONTROL_CHARS_RE.sub("", text)
def strip_unicode_tags(text: str) -> str:
"""Remove invisible Unicode TAG chars (a prompt-injection smuggling channel in
untrusted tool output); valid emoji tag sequences are preserved."""
if not text or not _HAS_UNICODE_TAG.search(text):
return text
return _UNICODE_TAG_SUB_RE.sub(lambda m: m.group(1) or "", text)