Skip to main content

vox_rtc_server/
types.rs

1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3
4pub const EVENT_CLIENT_EVENT: &str = "client.event";
5pub const EVENT_BROWSER_EVENT: &str = "browser.event";
6pub const EVENT_RTC_CLIENT_DISCONNECTED: &str = "rtc.client.disconnected";
7pub const EVENT_ERROR: &str = "error";
8pub const EVENT_INTERRUPTION_DETECTED: &str = "interruption.detected";
9pub const EVENT_INTERRUPTION_FALSE_POSITIVE: &str = "interruption.false_positive";
10pub const EVENT_RESPONSE_AUDIO_CLEAR: &str = "response.audio.clear";
11pub const EVENT_RESPONSE_CANCELLED: &str = "response.cancelled";
12pub const EVENT_RESPONSE_COMMITTED: &str = "response.committed";
13pub const EVENT_RESPONSE_CREATED: &str = "response.created";
14pub const EVENT_RESPONSE_DONE: &str = "response.done";
15pub const EVENT_RTC_SESSION_ATTACHED: &str = "rtc.session.attached";
16pub const EVENT_RTC_SIGNALING_ERROR: &str = "rtc.signaling_error";
17pub const EVENT_SESSION_CREATED: &str = "session.created";
18pub const EVENT_TRANSCRIPT_COMPLETED: &str =
19    "conversation.item.input_audio_transcription.completed";
20pub const EVENT_TURN_STATE_CHANGED: &str = "turn.state_changed";
21pub const EVENT_SPEECH_STARTED: &str = "input_audio_buffer.speech_started";
22pub const EVENT_SPEECH_STOPPED: &str = "input_audio_buffer.speech_stopped";
23pub const EVENT_TRANSCRIPT_DELTA: &str = "conversation.item.input_audio_transcription.delta";
24pub const EVENT_TURN_EOU_PREDICTED: &str = "turn.eou.predicted";
25
26pub const ERROR_CODE_RESPONSE_REJECTED_TURN_STATE: &str = "response_rejected_turn_state";
27pub const ERROR_CODE_RESPONSE_REJECTED_USER_SPEECH: &str = "response_rejected_user_speech";
28pub const ERROR_CODE_RESPONSE_STALE_GENERATION: &str = "response_stale_generation";
29pub const ERROR_CODE_RESPONSE_ALREADY_ACTIVE: &str = "response_already_active";
30pub const ERROR_CODE_RESPONSE_FAILED: &str = "response_failed";
31pub const ERROR_CODE_COMMAND_INVALID: &str = "command_invalid";
32pub const ERROR_CODE_SESSION_FAILED: &str = "session_failed";
33
34pub type EventData = Map<String, Value>;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum ConnectionState {
38    Disconnected,
39    Connecting,
40    Connected,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum ChannelState {
45    Idle,
46    Joining,
47    Joined,
48    Closed,
49    Declined,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
53pub struct RtcIceServer {
54    pub urls: Value,
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub username: Option<String>,
57    #[serde(default, skip_serializing_if = "Option::is_none")]
58    pub credential: Option<String>,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
62pub struct SessionBootstrap {
63    pub session_id: String,
64    pub expires_at: String,
65    #[serde(default)]
66    pub attach_ttl_seconds: u64,
67    #[serde(default)]
68    pub ice_servers: Vec<RtcIceServer>,
69}
70
71#[derive(Debug, Clone, Default)]
72pub struct SessionConfig {
73    pub stt_model: Option<String>,
74    pub tts_model: Option<String>,
75    pub voice: Option<String>,
76    pub turn_profile: Option<String>,
77    pub vad_backend: Option<String>,
78    pub turn_detector: Option<String>,
79    pub extra: EventData,
80}
81
82#[derive(Debug, Clone, Default)]
83pub struct ResponseOptions {
84    pub allow_interruptions: Option<bool>,
85    pub generation_id: Option<String>,
86}
87
88#[derive(Debug, Clone)]
89pub struct ClientEventEnvelope {
90    pub event: String,
91    pub payload: Value,
92}
93
94#[derive(Debug, Clone)]
95pub struct WireEvent {
96    pub r#type: String,
97    pub data: EventData,
98    pub session_id: String,
99    pub channel_name: String,
100}
101
102#[derive(Debug, Clone)]
103pub struct SessionAttachedEvent {
104    pub session_id: String,
105    pub channel_name: String,
106    pub data: EventData,
107}
108
109#[derive(Debug, Clone)]
110pub struct SessionCreatedEvent {
111    pub session_id: String,
112    pub channel_name: String,
113    pub data: EventData,
114    pub session: Option<EventData>,
115}
116
117#[derive(Debug, Clone)]
118pub struct TranscriptEvent {
119    pub session_id: String,
120    pub channel_name: String,
121    pub data: EventData,
122    pub transcript: String,
123    pub language: Option<String>,
124    pub start_ms: Option<f64>,
125    pub end_ms: Option<f64>,
126    pub eou_probability: Option<f64>,
127    pub topics: Option<Vec<String>>,
128    pub entities: Vec<TranscriptEntity>,
129    pub words: Vec<TranscriptWord>,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq)]
133pub struct TranscriptEntity {
134    pub r#type: String,
135    pub text: String,
136    pub start_char: u64,
137    pub end_char: u64,
138}
139
140#[derive(Debug, Clone, PartialEq)]
141pub struct TranscriptWord {
142    pub word: String,
143    pub start_ms: f64,
144    pub end_ms: f64,
145    pub confidence: Option<f64>,
146}
147
148#[derive(Debug, Clone)]
149pub struct TurnStateEvent {
150    pub session_id: String,
151    pub channel_name: String,
152    pub data: EventData,
153    pub state: String,
154    pub previous_state: Option<String>,
155}
156
157#[derive(Debug, Clone)]
158pub struct SpeechStartedEvent {
159    pub session_id: String,
160    pub channel_name: String,
161    pub data: EventData,
162    pub timestamp_ms: Option<f64>,
163}
164
165#[derive(Debug, Clone)]
166pub struct SpeechStoppedEvent {
167    pub session_id: String,
168    pub channel_name: String,
169    pub data: EventData,
170    pub timestamp_ms: Option<f64>,
171}
172
173#[derive(Debug, Clone)]
174pub struct TranscriptDeltaEvent {
175    pub session_id: String,
176    pub channel_name: String,
177    pub data: EventData,
178    pub delta: String,
179    pub start_ms: Option<f64>,
180    pub end_ms: Option<f64>,
181}
182
183#[derive(Debug, Clone)]
184pub struct TurnEouPredictedEvent {
185    pub session_id: String,
186    pub channel_name: String,
187    pub data: EventData,
188    pub probability: Option<f64>,
189    pub threshold: Option<f64>,
190    pub delay_ms: Option<f64>,
191    pub start_ms: Option<f64>,
192    pub end_ms: Option<f64>,
193    pub decision: Option<String>,
194    pub action: Option<String>,
195    pub turn_detector: Option<String>,
196}
197
198#[derive(Debug, Clone)]
199pub struct ResponseEvent {
200    pub session_id: String,
201    pub channel_name: String,
202    pub data: EventData,
203    pub response_id: Option<String>,
204    pub generation_id: Option<String>,
205}
206
207#[derive(Debug, Clone)]
208pub struct InterruptionEvent {
209    pub response: ResponseEvent,
210    pub vad_active_ms: Option<f64>,
211    pub partial_transcript: Option<String>,
212    pub reason: Option<String>,
213}
214
215#[derive(Debug, Clone)]
216pub struct BrowserEvent {
217    pub session_id: String,
218    pub channel_name: String,
219    pub data: EventData,
220    pub event: String,
221    pub payload: Value,
222}
223
224#[derive(Debug, Clone)]
225pub struct CloseEvent {
226    pub session_id: String,
227    pub channel_name: String,
228    pub data: EventData,
229    pub reason: String,
230    pub connection_state: Option<String>,
231    pub ice_connection_state: Option<String>,
232    pub data_channel_state: Option<String>,
233}
234
235#[derive(Debug, Clone)]
236pub struct ErrorEvent {
237    pub session_id: String,
238    pub channel_name: String,
239    pub data: EventData,
240    pub message: Option<String>,
241    pub code: Option<String>,
242    pub recoverable: bool,
243    pub generation_id: Option<String>,
244}
245
246#[derive(Debug, Clone)]
247pub struct SignalingErrorEvent {
248    pub session_id: String,
249    pub channel_name: String,
250    pub data: EventData,
251    pub message: Option<String>,
252    pub generation: Option<i64>,
253}
254
255#[derive(Debug, Clone)]
256pub struct StartAck {
257    pub accepted: bool,
258    pub generation_id: String,
259    pub response_id: Option<String>,
260    pub error_code: Option<String>,
261    pub error_message: Option<String>,
262    pub recoverable: bool,
263}
264
265pub(crate) fn optional_string(data: &EventData, key: &str) -> Option<String> {
266    data.get(key).and_then(Value::as_str).map(ToOwned::to_owned)
267}
268
269pub(crate) fn required_string(data: &EventData, key: &str, fallback: &str) -> String {
270    optional_string(data, key)
271        .filter(|s| !s.is_empty())
272        .unwrap_or_else(|| fallback.to_owned())
273}
274
275pub(crate) fn optional_number(data: &EventData, key: &str) -> Option<f64> {
276    data.get(key).and_then(Value::as_f64)
277}
278
279pub(crate) fn optional_i64(data: &EventData, key: &str) -> Option<i64> {
280    data.get(key).and_then(Value::as_i64)
281}
282
283pub(crate) fn optional_nonempty_string(data: &EventData, key: &str) -> Option<String> {
284    optional_string(data, key).filter(|s| !s.is_empty())
285}
286
287pub(crate) fn recoverable_flag(data: &EventData) -> bool {
288    data.get("recoverable").and_then(Value::as_bool).unwrap_or(true)
289}
290
291pub(crate) fn optional_string_vec(data: &EventData, key: &str) -> Option<Vec<String>> {
292    data.get(key).and_then(Value::as_array).and_then(|items| {
293        items
294            .iter()
295            .map(|item| item.as_str().map(ToOwned::to_owned))
296            .collect()
297    })
298}
299
300pub(crate) fn transcript_entities(data: &EventData) -> Vec<TranscriptEntity> {
301    data.get("entities")
302        .and_then(Value::as_array)
303        .map(|items| items.iter().filter_map(transcript_entity).collect())
304        .unwrap_or_default()
305}
306
307fn transcript_entity(value: &Value) -> Option<TranscriptEntity> {
308    let object = value.as_object()?;
309    Some(TranscriptEntity {
310        r#type: optional_string(object, "type").unwrap_or_default(),
311        text: optional_string(object, "text").unwrap_or_default(),
312        start_char: object.get("start_char").and_then(Value::as_u64).unwrap_or(0),
313        end_char: object.get("end_char").and_then(Value::as_u64).unwrap_or(0),
314    })
315}
316
317pub(crate) fn transcript_words(data: &EventData) -> Vec<TranscriptWord> {
318    data.get("words")
319        .and_then(Value::as_array)
320        .map(|items| items.iter().filter_map(transcript_word).collect())
321        .unwrap_or_default()
322}
323
324fn transcript_word(value: &Value) -> Option<TranscriptWord> {
325    let object = value.as_object()?;
326    Some(TranscriptWord {
327        word: optional_string(object, "word").unwrap_or_default(),
328        start_ms: optional_number(object, "start_ms").unwrap_or(0.0),
329        end_ms: optional_number(object, "end_ms").unwrap_or(0.0),
330        confidence: optional_number(object, "confidence"),
331    })
332}