1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
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<TurnId> {
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,
}
}
}
|