"""Conversation state management."""
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Tuple
@dataclass
class ConversationManager:
"""
Manages conversation history and metrics.
Extracted from VoiceModeController to enable:
- State preservation across mode switches (Phase 2)
- Independent testing of conversation logic
- Clear ownership of conversation data
"""
history: List[Tuple[str, str]] = field(default_factory=list)
tokens_in: int = 0
tokens_out: int = 0
latency_ms: Optional[int] = None
def add_turn(self, role: str, content: str, tokens: Optional[int] = None) -> None:
"""
Add a conversation turn.
Args:
role: 'user' or 'assistant'
content: Message content
tokens: Optional explicit token count (if provided by LLM response).
If None, estimates using 4 chars per token heuristic.
"""
self.history.append((role, content))
# Use explicit token count if provided, otherwise estimate
token_count = tokens if tokens is not None else len(content) // 4
if role == "user":
self.tokens_in += token_count
else:
self.tokens_out += token_count
def get_recent(self, count: int = 5) -> List[Tuple[str, str]]:
"""Get most recent conversation turns."""
if len(self.history) <= count:
return self.history.copy()
return self.history[-count:]
def clear(self) -> None:
"""Clear conversation history and reset metrics."""
self.history = []
self.tokens_in = 0
self.tokens_out = 0
self.latency_ms = None
def set_latency(self, ms: int) -> None:
"""Record latency for last response."""
self.latency_ms = ms
def to_llm_messages(self) -> List[Dict[str, str]]:
"""Convert history to LLM message format."""
return [
{"role": role, "content": content}
for role, content in self.history
]
@property
def last_user_text(self) -> str:
"""Get most recent user message."""
for role, content in reversed(self.history):
if role == "user":
return content
return ""
@property
def last_response(self) -> str:
"""Get most recent assistant message."""
for role, content in reversed(self.history):
if role == "assistant":
return content
return ""
@property
def turn_count(self) -> int:
"""Get total number of turns in conversation."""
return len(self.history)
@property
def is_empty(self) -> bool:
"""Check if conversation has no turns."""
return len(self.history) == 0