"""Interaction commands and user intent for Dvādaśa UX.""" import re from enum import Enum from pydantic import BaseModel, Field from fusionagi.schemas.head import HeadId class UserIntent(str, Enum): """User intent derived from commands or natural input.""" NORMAL = "normal" HEAD_STRATEGY = "head_strategy" SHOW_DISSENT = "show_dissent" RERUN_RISK = "rerun_risk" EXPLAIN_REASONING = "explain_reasoning" SOURCES = "sources" # Command patterns: /command [arg] or /command \n prompt _COMMAND_PATTERNS: list[tuple[re.Pattern[str], UserIntent]] = [ (re.compile(r"^/head\s+(\w+)(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.HEAD_STRATEGY), (re.compile(r"^/show\s+dissent(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.SHOW_DISSENT), (re.compile(r"^/rerun\s+risk(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.RERUN_RISK), (re.compile(r"^/explain\s+reasoning(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.EXPLAIN_REASONING), (re.compile(r"^/sources(?:\s+(.+))?\s*$", re.I | re.DOTALL), UserIntent.SOURCES), ] class ParsedCommand(BaseModel): """Parsed user command with intent and optional arguments.""" intent: UserIntent = Field(..., description="Detected intent") head_id: HeadId | None = Field(default=None, description="For HEAD_STRATEGY: which head") raw_input: str = Field(default="", description="Original user input") cleaned_prompt: str = Field(default="", description="Prompt with command stripped if applicable") def parse_user_input(text: str) -> ParsedCommand: """ Parse user input to detect commands and extract intent. Examples: "/head strategy" -> HEAD_STRATEGY, head_id=STRATEGY "/show dissent" -> SHOW_DISSENT "What is X?" -> NORMAL, cleaned_prompt="What is X?" """ stripped = (text or "").strip() if not stripped: return ParsedCommand( intent=UserIntent.NORMAL, raw_input=stripped, cleaned_prompt=stripped, ) for pattern, intent in _COMMAND_PATTERNS: match = pattern.match(stripped) if match: head_id = None cleaned = "" if intent == UserIntent.HEAD_STRATEGY: arg = (match.group(1) or "").lower() rest = match.group(2) if match.lastindex and match.lastindex >= 2 else None cleaned = (rest or "").strip() try: head_id = HeadId(arg) except ValueError: head_id = None else: rest = match.group(1) if match.lastindex and match.lastindex >= 1 else None cleaned = (rest or "").strip() return ParsedCommand( intent=intent, head_id=head_id, raw_input=stripped, cleaned_prompt=cleaned, ) return ParsedCommand( intent=UserIntent.NORMAL, raw_input=stripped, cleaned_prompt=stripped, ) __all__ = [ "UserIntent", "ParsedCommand", "parse_user_input", ]