from dataclasses import dataclass, field from uuid import uuid4 @dataclass class SessionState: session_id: str = field(default_factory=lambda: str(uuid4())) active_turn_id: int | None = None next_turn_id: int = 1 expected_audio_sequence: int = 0 closed: bool = False def start_turn(self) -> int: if self.closed: raise RuntimeError("Session is closed.") if self.active_turn_id is not None: raise RuntimeError("A turn is already active.") self.active_turn_id = self.next_turn_id self.next_turn_id += 1 self.expected_audio_sequence = 0 return self.active_turn_id def accept_audio_chunk( self, turn_id: int, sequence_number: int, payload_size: int, ) -> None: if self.closed: raise RuntimeError("Session is closed.") if self.active_turn_id != turn_id: raise ValueError( f"Unexpected turn_id={turn_id}; " f"active_turn_id={self.active_turn_id}." ) if sequence_number != self.expected_audio_sequence: raise ValueError( f"Expected sequence_number=" f"{self.expected_audio_sequence}, " f"received={sequence_number}." ) if payload_size == 0: raise ValueError("Audio chunk cannot be empty.") self.expected_audio_sequence += 1 def end_turn(self, turn_id: int) -> None: if self.active_turn_id != turn_id: raise ValueError( f"Cannot end turn {turn_id}; " f"active_turn_id={self.active_turn_id}." ) self.active_turn_id = None self.expected_audio_sequence = 0 def interrupt(self, turn_id: int) -> None: if self.active_turn_id == turn_id: self.active_turn_id = None self.expected_audio_sequence = 0 def close(self) -> None: self.active_turn_id = None self.closed = True