"""Protocol event models matching the original active-call API.""" from typing import Optional, Dict, Any from pydantic import BaseModel, Field from datetime import datetime def current_timestamp_ms() -> int: """Get current timestamp in milliseconds.""" return int(datetime.now().timestamp() * 1000) # Base Event Model class BaseEvent(BaseModel): """Base event model.""" event: str = Field(..., description="Event type") track_id: str = Field(..., description="Unique track identifier") timestamp: int = Field(default_factory=current_timestamp_ms, description="Event timestamp in milliseconds") # Lifecycle Events class IncomingEvent(BaseEvent): """Incoming call event (SIP only).""" event: str = Field(default="incoming", description="Event type") caller: Optional[str] = Field(default=None, description="Caller's SIP URI") callee: Optional[str] = Field(default=None, description="Callee's SIP URI") sdp: Optional[str] = Field(default=None, description="SDP offer from caller") class AnswerEvent(BaseEvent): """Call answered event.""" event: str = Field(default="answer", description="Event type") sdp: Optional[str] = Field(default=None, description="SDP answer from server") class RejectEvent(BaseEvent): """Call rejected event.""" event: str = Field(default="reject", description="Event type") reason: Optional[str] = Field(default=None, description="Rejection reason") code: Optional[int] = Field(default=None, description="SIP response code") class RingingEvent(BaseEvent): """Call ringing event.""" event: str = Field(default="ringing", description="Event type") early_media: bool = Field(default=False, description="Early media available") class HangupEvent(BaseModel): """Call hangup event.""" event: str = Field(default="hangup", description="Event type") timestamp: int = Field(default_factory=current_timestamp_ms, description="Event timestamp") reason: Optional[str] = Field(default=None, description="Hangup reason") initiator: Optional[str] = Field(default=None, description="Who initiated hangup") start_time: Optional[str] = Field(default=None, description="Call start time (ISO 8601)") hangup_time: Optional[str] = Field(default=None, description="Hangup time (ISO 8601)") answer_time: Optional[str] = Field(default=None, description="Answer time (ISO 8601)") ringing_time: Optional[str] = Field(default=None, description="Ringing time (ISO 8601)") from_: Optional[Dict[str, Any]] = Field(default=None, alias="from", description="Caller info") to: Optional[Dict[str, Any]] = Field(default=None, description="Callee info") extra: Optional[Dict[str, Any]] = Field(default=None, description="Additional metadata") class Config: populate_by_name = True # VAD Events class SpeakingEvent(BaseEvent): """Speech detected event.""" event: str = Field(default="speaking", description="Event type") start_time: int = Field(default_factory=current_timestamp_ms, description="Speech start time") class SilenceEvent(BaseEvent): """Silence detected event.""" event: str = Field(default="silence", description="Event type") start_time: int = Field(default_factory=current_timestamp_ms, description="Silence start time") duration: int = Field(default=0, description="Silence duration in milliseconds") # AI/ASR Events class AsrFinalEvent(BaseEvent): """ASR final transcription event.""" event: str = Field(default="asrFinal", description="Event type") index: int = Field(..., description="ASR result sequence number") start_time: Optional[int] = Field(default=None, description="Speech start time") end_time: Optional[int] = Field(default=None, description="Speech end time") text: str = Field(..., description="Transcribed text") class AsrDeltaEvent(BaseEvent): """ASR partial transcription event (streaming).""" event: str = Field(default="asrDelta", description="Event type") index: int = Field(..., description="ASR result sequence number") start_time: Optional[int] = Field(default=None, description="Speech start time") end_time: Optional[int] = Field(default=None, description="Speech end time") text: str = Field(..., description="Partial transcribed text") class EouEvent(BaseEvent): """End of utterance detection event.""" event: str = Field(default="eou", description="Event type") completed: bool = Field(default=True, description="Whether utterance was completed") # Audio Track Events class TrackStartEvent(BaseEvent): """Audio track start event.""" event: str = Field(default="trackStart", description="Event type") play_id: Optional[str] = Field(default=None, description="Play ID from TTS/Play command") class TrackEndEvent(BaseEvent): """Audio track end event.""" event: str = Field(default="trackEnd", description="Event type") duration: int = Field(..., description="Track duration in milliseconds") ssrc: int = Field(..., description="RTP SSRC identifier") play_id: Optional[str] = Field(default=None, description="Play ID from TTS/Play command") class InterruptionEvent(BaseEvent): """Playback interruption event.""" event: str = Field(default="interruption", description="Event type") play_id: Optional[str] = Field(default=None, description="Play ID that was interrupted") subtitle: Optional[str] = Field(default=None, description="TTS text being played") position: Optional[int] = Field(default=None, description="Word index position") total_duration: Optional[int] = Field(default=None, description="Total TTS duration") current: Optional[int] = Field(default=None, description="Elapsed time when interrupted") # System Events class ErrorEvent(BaseEvent): """Error event.""" event: str = Field(default="error", description="Event type") sender: str = Field(..., description="Component that generated the error") error: str = Field(..., description="Error message") code: Optional[int] = Field(default=None, description="Error code") class MetricsEvent(BaseModel): """Performance metrics event.""" event: str = Field(default="metrics", description="Event type") timestamp: int = Field(default_factory=current_timestamp_ms, description="Event timestamp") key: str = Field(..., description="Metric key") duration: int = Field(..., description="Duration in milliseconds") data: Optional[Dict[str, Any]] = Field(default=None, description="Additional metric data") class AddHistoryEvent(BaseModel): """Conversation history entry added event.""" event: str = Field(default="addHistory", description="Event type") timestamp: int = Field(default_factory=current_timestamp_ms, description="Event timestamp") sender: Optional[str] = Field(default=None, description="Component that added history") speaker: str = Field(..., description="Speaker identifier") text: str = Field(..., description="Conversation text") class DTMFEvent(BaseEvent): """DTMF tone detected event.""" event: str = Field(default="dtmf", description="Event type") digit: str = Field(..., description="DTMF digit (0-9, *, #, A-D)") # Event type mapping EVENT_TYPES = { "incoming": IncomingEvent, "answer": AnswerEvent, "reject": RejectEvent, "ringing": RingingEvent, "hangup": HangupEvent, "speaking": SpeakingEvent, "silence": SilenceEvent, "asrFinal": AsrFinalEvent, "asrDelta": AsrDeltaEvent, "eou": EouEvent, "trackStart": TrackStartEvent, "trackEnd": TrackEndEvent, "interruption": InterruptionEvent, "error": ErrorEvent, "metrics": MetricsEvent, "addHistory": AddHistoryEvent, "dtmf": DTMFEvent, } def create_event(event_type: str, **kwargs) -> BaseModel: """ Create an event model. Args: event_type: Type of event to create **kwargs: Event fields Returns: Event model instance Raises: ValueError: If event type is unknown """ event_class = EVENT_TYPES.get(event_type) if not event_class: raise ValueError(f"Unknown event type: {event_type}") return event_class(event=event_type, **kwargs)