"""Wire-safe projections of runtime state for the TUI backend.""" from __future__ import annotations import json import re from typing import Any SCAN_MODES = ("quick", "standard", "deep") SCOPE_MODES = ("auto", "diff", "full") MAX_PROJECTION_STRING = 64 * 1024 MAX_IMAGE_DATA_URI_BYTES = 2 * 1024 * 1024 MAX_COLLECTION_ITEM_BYTES = 512 * 1024 MAX_TERMINAL_EVENTS = 5_000 MAX_TERMINAL_VULNERABILITIES = 1_000 STATE_TARGET_BYTES = 48 * 1024 TERMINAL_ESCAPE_RE = re.compile(r"\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)|\x1b[@-_][0-?]*[ -/]*[@-~]") def sanitize_terminal_text(value: str) -> str: without_escapes = TERMINAL_ESCAPE_RE.sub("", value) return "".join( character for character in without_escapes if character in "\n\t" or (ord(character) >= 32 and not 127 <= ord(character) <= 159) ) def terminal_projection( # noqa: PLR0911 value: Any, *, max_string: int = MAX_PROJECTION_STRING, max_items: int = 200, depth: int = 0, ) -> Any: """Copy and bound terminal-only data without changing durable history.""" if isinstance(value, str): if value.startswith("data:image/"): if len(value) <= MAX_IMAGE_DATA_URI_BYTES: return value return "[image omitted from terminal projection]" clean = sanitize_terminal_text(value) if len(clean) <= max_string: return clean omitted = len(clean) - max_string return f"{clean[:max_string]}\n...[{omitted} characters omitted from terminal projection]" if value is None or isinstance(value, bool | int | float): return value if depth >= 8: return "[nested value omitted from terminal projection]" if isinstance(value, dict): items = list(value.items()) projected = { sanitize_terminal_text(str(key)): terminal_projection( item, max_string=max_string, max_items=max_items, depth=depth + 1, ) for key, item in items[:max_items] } if len(items) > max_items: projected["_projection_notice"] = ( f"{len(items) - max_items} fields omitted from terminal projection" ) return projected if isinstance(value, list | tuple): projected_items = [ terminal_projection( item, max_string=max_string, max_items=max_items, depth=depth + 1, ) for item in value[:max_items] ] if len(value) > max_items: projected_items.append( f"[{len(value) - max_items} items omitted from terminal projection]" ) return projected_items return terminal_projection( str(value), max_string=max_string, max_items=max_items, depth=depth, ) def collection_item_projection(item: dict[str, Any]) -> dict[str, Any]: # Image data URIs are exempt from string truncation, so grant them their # own byte budget on top of the regular per-item budget. item_budget = MAX_COLLECTION_ITEM_BYTES + MAX_IMAGE_DATA_URI_BYTES projected = terminal_projection(item) assert isinstance(projected, dict) if len(json.dumps(projected, default=str, separators=(",", ":")).encode()) <= item_budget: return projected projected = terminal_projection(item, max_string=8 * 1024, max_items=40) assert isinstance(projected, dict) projected["projection_truncated"] = True if len(json.dumps(projected, default=str, separators=(",", ":")).encode()) <= item_budget: return projected # Preserve identity and useful summary fields even for pathological nested # tool output or finding evidence. compact: dict[str, Any] = { key: terminal_projection(item[key], max_string=8 * 1024, max_items=10) for key in ( "id", "version", "type", "agent_id", "timestamp", "title", "severity", "description", ) if key in item } compact["projection_truncated"] = True return compact def bounded_state_projection(state: dict[str, Any]) -> dict[str, Any]: """Keep mutable control state comfortably below the 64 KiB frame limit.""" def encoded_size(value: dict[str, Any]) -> int: return len( json.dumps(value, default=str, ensure_ascii=False, separators=(",", ":")).encode() ) if encoded_size(state) <= STATE_TARGET_BYTES: return state state["projection_truncated"] = True state["targets"] = [ terminal_projection(target, max_string=64) for target in state["targets"][:8] ] state["instruction"] = terminal_projection(state["instruction"], max_string=512) state["messages"] = [ { **message, "text": terminal_projection(message.get("text", ""), max_string=128), } for message in state["messages"][-5:] ] state["usage"] = {} state["error"] = terminal_projection(state["error"], max_string=512) state["model_warning"] = terminal_projection(state["model_warning"], max_string=256) state["caido_url"] = terminal_projection(state["caido_url"], max_string=256) state["viewer_url"] = terminal_projection(state["viewer_url"], max_string=256) if encoded_size(state) <= STATE_TARGET_BYTES: return state # Defensive final projection: use an explicit schema so future snapshot # fields cannot silently bypass the aggregate byte budget. return { "setup_mode": state["setup_mode"], "scan_started": state["scan_started"], "scan_state": state["scan_state"], "targets": state["targets"][:4], "target_count": state["target_count"], "instruction": terminal_projection(state["instruction"], max_string=128), "scan_mode": state["scan_mode"], "max_budget_usd": state["max_budget_usd"], "max_turns": state["max_turns"], "scope_mode": state["scope_mode"], "diff_base": state["diff_base"], "provider": state["provider"], "model": state["model"], "model_warning": "", "caido_url": None, "messages": [], "usage": {}, "subscription": state["subscription"], "viewer_status": state["viewer_status"], "viewer_url": None, "error": terminal_projection(state["error"], max_string=256), "projection_truncated": True, }