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 speech_context: Option<bool>,
80 pub extra: EventData,
81}
82
83#[derive(Debug, Clone, Default)]
84pub struct ResponseOptions {
85 pub allow_interruptions: Option<bool>,
86 pub generation_id: Option<String>,
87}
88
89#[derive(Debug, Clone)]
90pub struct ClientEventEnvelope {
91 pub event: String,
92 pub payload: Value,
93}
94
95#[derive(Debug, Clone)]
96pub struct WireEvent {
97 pub r#type: String,
98 pub data: EventData,
99 pub session_id: String,
100 pub channel_name: String,
101}
102
103#[derive(Debug, Clone)]
104pub struct SessionAttachedEvent {
105 pub session_id: String,
106 pub channel_name: String,
107 pub data: EventData,
108}
109
110#[derive(Debug, Clone)]
111pub struct SessionCreatedEvent {
112 pub session_id: String,
113 pub channel_name: String,
114 pub data: EventData,
115 pub session: Option<EventData>,
116}
117
118#[derive(Debug, Clone)]
119pub struct TranscriptEvent {
120 pub session_id: String,
121 pub channel_name: String,
122 pub data: EventData,
123 pub transcript: String,
124 pub language: Option<String>,
125 pub start_ms: Option<f64>,
126 pub end_ms: Option<f64>,
127 pub eou_probability: Option<f64>,
128 pub topics: Option<Vec<String>>,
129 pub entities: Vec<TranscriptEntity>,
130 pub words: Vec<TranscriptWord>,
131 pub speech_context: Option<EventData>,
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct TranscriptEntity {
136 pub r#type: String,
137 pub text: String,
138 pub start_char: u64,
139 pub end_char: u64,
140}
141
142#[derive(Debug, Clone, PartialEq)]
143pub struct TranscriptWord {
144 pub word: String,
145 pub start_ms: f64,
146 pub end_ms: f64,
147 pub confidence: Option<f64>,
148}
149
150#[derive(Debug, Clone)]
151pub struct TurnStateEvent {
152 pub session_id: String,
153 pub channel_name: String,
154 pub data: EventData,
155 pub state: String,
156 pub previous_state: Option<String>,
157}
158
159#[derive(Debug, Clone)]
160pub struct SpeechStartedEvent {
161 pub session_id: String,
162 pub channel_name: String,
163 pub data: EventData,
164 pub timestamp_ms: Option<f64>,
165}
166
167#[derive(Debug, Clone)]
168pub struct SpeechStoppedEvent {
169 pub session_id: String,
170 pub channel_name: String,
171 pub data: EventData,
172 pub timestamp_ms: Option<f64>,
173}
174
175#[derive(Debug, Clone)]
176pub struct TranscriptDeltaEvent {
177 pub session_id: String,
178 pub channel_name: String,
179 pub data: EventData,
180 pub delta: String,
181 pub start_ms: Option<f64>,
182 pub end_ms: Option<f64>,
183}
184
185#[derive(Debug, Clone)]
186pub struct TurnEouPredictedEvent {
187 pub session_id: String,
188 pub channel_name: String,
189 pub data: EventData,
190 pub probability: Option<f64>,
191 pub threshold: Option<f64>,
192 pub delay_ms: Option<f64>,
193 pub start_ms: Option<f64>,
194 pub end_ms: Option<f64>,
195 pub decision: Option<String>,
196 pub action: Option<String>,
197 pub turn_detector: Option<String>,
198}
199
200#[derive(Debug, Clone)]
201pub struct ResponseEvent {
202 pub session_id: String,
203 pub channel_name: String,
204 pub data: EventData,
205 pub response_id: Option<String>,
206 pub generation_id: Option<String>,
207}
208
209#[derive(Debug, Clone)]
210pub struct InterruptionEvent {
211 pub response: ResponseEvent,
212 pub vad_active_ms: Option<f64>,
213 pub partial_transcript: Option<String>,
214 pub reason: Option<String>,
215}
216
217#[derive(Debug, Clone)]
218pub struct BrowserEvent {
219 pub session_id: String,
220 pub channel_name: String,
221 pub data: EventData,
222 pub event: String,
223 pub payload: Value,
224}
225
226#[derive(Debug, Clone)]
227pub struct CloseEvent {
228 pub session_id: String,
229 pub channel_name: String,
230 pub data: EventData,
231 pub reason: String,
232 pub connection_state: Option<String>,
233 pub ice_connection_state: Option<String>,
234 pub data_channel_state: Option<String>,
235}
236
237#[derive(Debug, Clone)]
238pub struct ErrorEvent {
239 pub session_id: String,
240 pub channel_name: String,
241 pub data: EventData,
242 pub message: Option<String>,
243 pub code: Option<String>,
244 pub recoverable: bool,
245 pub generation_id: Option<String>,
246}
247
248#[derive(Debug, Clone)]
249pub struct SignalingErrorEvent {
250 pub session_id: String,
251 pub channel_name: String,
252 pub data: EventData,
253 pub message: Option<String>,
254 pub generation: Option<i64>,
255}
256
257#[derive(Debug, Clone)]
258pub struct StartAck {
259 pub accepted: bool,
260 pub generation_id: String,
261 pub response_id: Option<String>,
262 pub error_code: Option<String>,
263 pub error_message: Option<String>,
264 pub recoverable: bool,
265}
266
267pub(crate) fn optional_string(data: &EventData, key: &str) -> Option<String> {
268 data.get(key).and_then(Value::as_str).map(ToOwned::to_owned)
269}
270
271pub(crate) fn required_string(data: &EventData, key: &str, fallback: &str) -> String {
272 optional_string(data, key)
273 .filter(|s| !s.is_empty())
274 .unwrap_or_else(|| fallback.to_owned())
275}
276
277pub(crate) fn optional_number(data: &EventData, key: &str) -> Option<f64> {
278 data.get(key).and_then(Value::as_f64)
279}
280
281pub(crate) fn optional_i64(data: &EventData, key: &str) -> Option<i64> {
282 data.get(key).and_then(Value::as_i64)
283}
284
285pub(crate) fn optional_nonempty_string(data: &EventData, key: &str) -> Option<String> {
286 optional_string(data, key).filter(|s| !s.is_empty())
287}
288
289pub(crate) fn recoverable_flag(data: &EventData) -> bool {
290 data.get("recoverable").and_then(Value::as_bool).unwrap_or(true)
291}
292
293pub(crate) fn optional_string_vec(data: &EventData, key: &str) -> Option<Vec<String>> {
294 data.get(key).and_then(Value::as_array).and_then(|items| {
295 items
296 .iter()
297 .map(|item| item.as_str().map(ToOwned::to_owned))
298 .collect()
299 })
300}
301
302pub(crate) fn transcript_entities(data: &EventData) -> Vec<TranscriptEntity> {
303 data.get("entities")
304 .and_then(Value::as_array)
305 .map(|items| items.iter().filter_map(transcript_entity).collect())
306 .unwrap_or_default()
307}
308
309fn transcript_entity(value: &Value) -> Option<TranscriptEntity> {
310 let object = value.as_object()?;
311 Some(TranscriptEntity {
312 r#type: optional_string(object, "type").unwrap_or_default(),
313 text: optional_string(object, "text").unwrap_or_default(),
314 start_char: object.get("start_char").and_then(Value::as_u64).unwrap_or(0),
315 end_char: object.get("end_char").and_then(Value::as_u64).unwrap_or(0),
316 })
317}
318
319pub(crate) fn transcript_words(data: &EventData) -> Vec<TranscriptWord> {
320 data.get("words")
321 .and_then(Value::as_array)
322 .map(|items| items.iter().filter_map(transcript_word).collect())
323 .unwrap_or_default()
324}
325
326fn transcript_word(value: &Value) -> Option<TranscriptWord> {
327 let object = value.as_object()?;
328 Some(TranscriptWord {
329 word: optional_string(object, "word").unwrap_or_default(),
330 start_ms: optional_number(object, "start_ms").unwrap_or(0.0),
331 end_ms: optional_number(object, "end_ms").unwrap_or(0.0),
332 confidence: optional_number(object, "confidence"),
333 })
334}