1use serde::{Deserialize, Serialize};
8
9use super::audio::{InputAudioFormat, OutputAudioFormat};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
15#[serde(rename_all = "snake_case")]
16pub enum TurnDetectionType {
17 #[default]
19 ClientVad,
20 ServerVad,
23}
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct TurnDetection {
32 #[serde(rename = "type")]
34 pub type_: TurnDetectionType,
35 #[serde(skip_serializing_if = "Option::is_none")]
38 pub create_response: Option<bool>,
39 #[serde(skip_serializing_if = "Option::is_none")]
41 pub interrupt_response: Option<bool>,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub prefix_padding_ms: Option<u32>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub silence_duration_ms: Option<u32>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub threshold: Option<f64>,
51}
52
53impl TurnDetection {
54 pub fn new(type_: TurnDetectionType) -> Self {
56 Self {
57 type_,
58 create_response: None,
59 interrupt_response: None,
60 prefix_padding_ms: None,
61 silence_duration_ms: None,
62 threshold: None,
63 }
64 }
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
69#[serde(rename_all = "snake_case")]
70pub enum ChatMode {
71 VideoPassive,
73 #[default]
75 Audio,
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
80#[serde(rename_all = "snake_case")]
81pub enum RealtimeModality {
82 Text,
84 Audio,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
90pub enum RealtimeVoice {
91 #[default]
93 #[serde(rename = "tongtong")]
94 Tongtong,
95 #[serde(rename = "xiaochen")]
97 Xiaochen,
98 #[serde(rename = "female-tianmei")]
100 FemaleTianmei,
101 #[serde(rename = "male-qn-daxuesheng")]
103 MaleQnDaxuesheng,
104 #[serde(rename = "male-qn-jingying")]
106 MaleQnJingying,
107 #[serde(rename = "lovely_girl")]
109 LovelyGirl,
110 #[serde(rename = "female-shaonv")]
112 FemaleShaonv,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum NoiseReductionType {
119 NearField,
121 FarField,
123}
124
125#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
127pub struct InputAudioNoiseReduction {
128 #[serde(rename = "type")]
130 pub type_: NoiseReductionType,
131}
132
133impl InputAudioNoiseReduction {
134 pub fn new(type_: NoiseReductionType) -> Self {
136 Self { type_ }
137 }
138}
139
140#[derive(Debug, Clone, Default, Serialize, Deserialize)]
142pub struct GreetingConfig {
143 #[serde(skip_serializing_if = "Option::is_none")]
145 pub enable: Option<bool>,
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub content: Option<String>,
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
153pub struct BetaFields {
154 #[serde(skip_serializing_if = "Option::is_none")]
156 pub chat_mode: Option<ChatMode>,
157 #[serde(skip_serializing_if = "Option::is_none")]
159 pub tts_source: Option<String>,
160 #[serde(skip_serializing_if = "Option::is_none")]
162 pub auto_search: Option<bool>,
163}
164
165impl Default for BetaFields {
166 fn default() -> Self {
167 Self {
168 chat_mode: Some(ChatMode::Audio),
169 tts_source: None,
170 auto_search: None,
171 }
172 }
173}
174
175#[derive(Debug, Clone, Serialize, Deserialize)]
177pub struct RealtimeTool {
178 #[serde(rename = "type")]
180 pub type_: String,
181 pub name: String,
183 pub description: String,
185 #[serde(default = "empty_json_object")]
187 pub parameters: serde_json::Value,
188}
189
190fn empty_json_object() -> serde_json::Value {
191 serde_json::Value::Object(serde_json::Map::new())
192}
193
194impl RealtimeTool {
195 pub fn function(
198 name: impl Into<String>,
199 description: impl Into<String>,
200 parameters: serde_json::Value,
201 ) -> Self {
202 Self {
203 type_: "function".to_string(),
204 name: name.into(),
205 description: description.into(),
206 parameters,
207 }
208 }
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct SessionConfig {
214 #[serde(skip_serializing_if = "Option::is_none")]
218 pub model: Option<String>,
219 pub input_audio_format: InputAudioFormat,
221 pub output_audio_format: OutputAudioFormat,
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub instructions: Option<String>,
226 #[serde(default = "default_modalities")]
228 pub modalities: Vec<RealtimeModality>,
229 #[serde(skip_serializing_if = "Option::is_none")]
231 pub voice: Option<RealtimeVoice>,
232 #[serde(skip_serializing_if = "Option::is_none")]
234 pub temperature: Option<f64>,
235 #[serde(
241 default,
242 skip_serializing_if = "Option::is_none",
243 with = "optional_u16_string"
244 )]
245 pub max_response_output_tokens: Option<u16>,
246 pub turn_detection: TurnDetection,
248 #[serde(skip_serializing_if = "Option::is_none")]
250 pub input_audio_noise_reduction: Option<InputAudioNoiseReduction>,
251 #[serde(default)]
253 pub beta_fields: BetaFields,
254 #[serde(skip_serializing_if = "Option::is_none")]
256 pub greeting_config: Option<GreetingConfig>,
257 #[serde(default, skip_serializing_if = "Vec::is_empty")]
259 pub tools: Vec<RealtimeTool>,
260}
261
262impl Default for SessionConfig {
263 fn default() -> Self {
264 Self {
265 model: None,
266 input_audio_format: InputAudioFormat::default(),
267 output_audio_format: OutputAudioFormat::default(),
268 instructions: None,
269 modalities: default_modalities(),
270 voice: None,
271 temperature: None,
272 max_response_output_tokens: None,
273 turn_detection: TurnDetection::new(TurnDetectionType::default()),
274 input_audio_noise_reduction: None,
275 beta_fields: BetaFields::default(),
276 greeting_config: None,
277 tools: Vec::new(),
278 }
279 }
280}
281
282fn default_modalities() -> Vec<RealtimeModality> {
283 vec![RealtimeModality::Text, RealtimeModality::Audio]
284}
285
286mod optional_u16_string {
287 use serde::{Deserialize as _, Deserializer, Serializer, de::Error as _};
288
289 pub(super) fn serialize<S>(value: &Option<u16>, serializer: S) -> Result<S::Ok, S::Error>
290 where
291 S: Serializer,
292 {
293 match value {
294 Some(value) => serializer.serialize_some(&value.to_string()),
295 None => serializer.serialize_none(),
296 }
297 }
298
299 pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
300 where
301 D: Deserializer<'de>,
302 {
303 let value = Option::<String>::deserialize(deserializer)?;
304 value
305 .map(|value| {
306 if value == "inf" {
307 return Ok(None);
308 }
309 value
310 .parse::<u16>()
311 .map_err(|_| {
312 D::Error::custom(
313 "max_response_output_tokens must be \"inf\" or a decimal u16 string",
314 )
315 })
316 .map(Some)
317 })
318 .transpose()
319 .map(Option::flatten)
320 }
321}
322
323#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
325#[serde(rename_all = "snake_case")]
326pub enum ItemType {
327 Message,
329 FunctionCall,
331 FunctionCallOutput,
333}
334
335#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct ItemContent {
338 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
340 pub type_: Option<String>,
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub text: Option<String>,
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub audio: Option<String>,
347 #[serde(skip_serializing_if = "Option::is_none")]
349 pub transcript: Option<String>,
350}
351
352#[derive(Debug, Clone, Serialize, Deserialize)]
355pub struct RealtimeConversationItem {
356 #[serde(skip_serializing_if = "Option::is_none")]
358 pub id: Option<String>,
359 #[serde(rename = "type")]
361 pub type_: ItemType,
362 pub object: String,
364 #[serde(skip_serializing_if = "Option::is_none")]
366 pub status: Option<String>,
367 #[serde(skip_serializing_if = "Option::is_none")]
369 pub role: Option<String>,
370 #[serde(default, skip_serializing_if = "Vec::is_empty")]
372 pub content: Vec<ItemContent>,
373 #[serde(skip_serializing_if = "Option::is_none")]
375 pub name: Option<String>,
376 #[serde(skip_serializing_if = "Option::is_none")]
378 pub arguments: Option<String>,
379 #[serde(skip_serializing_if = "Option::is_none")]
381 pub output: Option<String>,
382}
383
384impl RealtimeConversationItem {
385 pub fn user_text(text: impl Into<String>) -> Self {
387 Self {
388 id: None,
389 type_: ItemType::Message,
390 object: "realtime.item".to_string(),
391 status: Some("completed".to_string()),
392 role: Some("user".to_string()),
393 content: vec![ItemContent {
394 type_: Some("input_text".to_string()),
395 text: Some(text.into()),
396 audio: None,
397 transcript: None,
398 }],
399 name: None,
400 arguments: None,
401 output: None,
402 }
403 }
404
405 pub fn function_output(call_name: impl Into<String>, output: impl Into<String>) -> Self {
407 Self {
408 id: None,
409 type_: ItemType::FunctionCallOutput,
410 object: "realtime.item".to_string(),
411 status: None,
412 role: None,
413 content: Vec::new(),
414 name: Some(call_name.into()),
415 arguments: None,
416 output: Some(output.into()),
417 }
418 }
419}
420
421#[derive(Debug, Clone, Default, Serialize, Deserialize)]
423pub struct TokenDetails {
424 #[serde(skip_serializing_if = "Option::is_none")]
426 pub text_tokens: Option<u64>,
427 #[serde(skip_serializing_if = "Option::is_none")]
429 pub audio_tokens: Option<u64>,
430 #[serde(skip_serializing_if = "Option::is_none")]
432 pub cached_tokens: Option<u64>,
433}
434
435#[derive(Debug, Clone, Default, Serialize, Deserialize)]
437pub struct RealtimeUsage {
438 #[serde(default)]
440 pub total_tokens: u64,
441 #[serde(default)]
443 pub input_tokens: u64,
444 #[serde(default)]
446 pub output_tokens: u64,
447 #[serde(skip_serializing_if = "Option::is_none")]
449 pub input_token_details: Option<TokenDetails>,
450 #[serde(skip_serializing_if = "Option::is_none")]
452 pub output_token_details: Option<TokenDetails>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
457pub struct RealtimeResponse {
458 pub id: String,
460 pub object: String,
462 pub status: String,
465 #[serde(skip_serializing_if = "Option::is_none")]
467 pub usage: Option<RealtimeUsage>,
468}