use std::time::{Duration, Instant}; /// Identifies a conversation turn (one user utterance and the answer it /// triggers). Everything travelling on the bus carries it, so a result that /// arrives late is not mistaken for the turn in progress, the typical case /// being the user interrupting the assistant. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct TurnId(pub u64); impl std::fmt::Display for TurnId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "#{}", self.0) } } /// Why the current playback is cut. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum InterruptReason { /// The user started talking over the assistant (barge-in). UserSpoke, /// Explicit request: key, signal or shutdown. Requested, } /// Everything the stages tell each other. A single enum keeps the bus /// observable: the renderer and the telemetry see exactly the same events as /// the orchestrator, with no parallel channels that fall out of sync. #[derive(Debug, Clone)] pub enum Event { /// The VAD detected the start of an utterance. SpeechStarted { turn: TurnId, at: Instant }, /// Partial transcription of the sliding window: `committed` is already /// stable, `volatile` may still change. Partial { turn: TurnId, committed: String, volatile: String, }, /// Final transcription of the whole utterance. Transcript { turn: TurnId, text: String, audio_secs: f32, decode: Duration, }, /// The utterance had nothing transcribable. Discarded { turn: TurnId }, /// First text fragment returned by the model. ReplyStarted { turn: TurnId, ttft: Duration }, /// A piece of the answer as it arrives from the model. ReplyDelta { turn: TurnId, text: String }, /// A complete sentence ready to be synthesized. Sentence { turn: TurnId, index: usize, text: String, }, /// Complete model answer for this turn. ReplyDone { turn: TurnId, text: String }, /// The model asked to run a tool. ToolRequested { turn: TurnId, name: String, arguments: String, }, /// Result of that run. ToolFinished { turn: TurnId, name: String, ok: bool, output: String, took: Duration, }, /// First audible audio of the turn: the metric the speaker really perceives. AudioStarted { turn: TurnId, latency: Duration }, /// Everything of the turn has finished playing. AudioFinished { turn: TurnId }, /// Playback was cut. Interrupted { turn: TurnId, reason: InterruptReason, }, /// Non-fatal warning; the turn continues. Warning { turn: TurnId, message: String }, /// Failure that aborts the turn. Failed { turn: TurnId, message: String }, /// Cierre ordenado. Shutdown, } impl Event { pub fn turn(&self) -> Option { match self { Event::SpeechStarted { turn, .. } | Event::Partial { turn, .. } | Event::Transcript { turn, .. } | Event::Discarded { turn } | Event::ReplyStarted { turn, .. } | Event::ReplyDelta { turn, .. } | Event::Sentence { turn, .. } | Event::ReplyDone { turn, .. } | Event::ToolRequested { turn, .. } | Event::ToolFinished { turn, .. } | Event::AudioStarted { turn, .. } | Event::AudioFinished { turn } | Event::Interrupted { turn, .. } | Event::Warning { turn, .. } | Event::Failed { turn, .. } => Some(*turn), Event::Shutdown => None, } } }