use serde::{Deserialize, Serialize};
use super::audio::{InputAudioFormat, OutputAudioFormat};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TurnDetectionType {
#[default]
ClientVad,
ServerVad,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TurnDetection {
#[serde(rename = "type")]
pub type_: TurnDetectionType,
#[serde(skip_serializing_if = "Option::is_none")]
pub create_response: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interrupt_response: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prefix_padding_ms: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub silence_duration_ms: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub threshold: Option<f64>,
}
impl TurnDetection {
pub fn new(type_: TurnDetectionType) -> Self {
Self {
type_,
create_response: None,
interrupt_response: None,
prefix_padding_ms: None,
silence_duration_ms: None,
threshold: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ChatMode {
VideoPassive,
#[default]
Audio,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RealtimeModality {
Text,
Audio,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub enum RealtimeVoice {
#[default]
#[serde(rename = "tongtong")]
Tongtong,
#[serde(rename = "xiaochen")]
Xiaochen,
#[serde(rename = "female-tianmei")]
FemaleTianmei,
#[serde(rename = "male-qn-daxuesheng")]
MaleQnDaxuesheng,
#[serde(rename = "male-qn-jingying")]
MaleQnJingying,
#[serde(rename = "lovely_girl")]
LovelyGirl,
#[serde(rename = "female-shaonv")]
FemaleShaonv,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NoiseReductionType {
NearField,
FarField,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct InputAudioNoiseReduction {
#[serde(rename = "type")]
pub type_: NoiseReductionType,
}
impl InputAudioNoiseReduction {
pub fn new(type_: NoiseReductionType) -> Self {
Self { type_ }
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GreetingConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub enable: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BetaFields {
#[serde(skip_serializing_if = "Option::is_none")]
pub chat_mode: Option<ChatMode>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tts_source: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_search: Option<bool>,
}
impl Default for BetaFields {
fn default() -> Self {
Self {
chat_mode: Some(ChatMode::Audio),
tts_source: None,
auto_search: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealtimeTool {
#[serde(rename = "type")]
pub type_: String,
pub name: String,
pub description: String,
#[serde(default = "empty_json_object")]
pub parameters: serde_json::Value,
}
fn empty_json_object() -> serde_json::Value {
serde_json::Value::Object(serde_json::Map::new())
}
impl RealtimeTool {
pub fn function(
name: impl Into<String>,
description: impl Into<String>,
parameters: serde_json::Value,
) -> Self {
Self {
type_: "function".to_string(),
name: name.into(),
description: description.into(),
parameters,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionConfig {
#[serde(skip_serializing_if = "Option::is_none")]
pub model: Option<String>,
pub input_audio_format: InputAudioFormat,
pub output_audio_format: OutputAudioFormat,
#[serde(skip_serializing_if = "Option::is_none")]
pub instructions: Option<String>,
#[serde(default = "default_modalities")]
pub modalities: Vec<RealtimeModality>,
#[serde(skip_serializing_if = "Option::is_none")]
pub voice: Option<RealtimeVoice>,
#[serde(skip_serializing_if = "Option::is_none")]
pub temperature: Option<f64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
with = "optional_u16_string"
)]
pub max_response_output_tokens: Option<u16>,
pub turn_detection: TurnDetection,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_audio_noise_reduction: Option<InputAudioNoiseReduction>,
#[serde(default)]
pub beta_fields: BetaFields,
#[serde(skip_serializing_if = "Option::is_none")]
pub greeting_config: Option<GreetingConfig>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tools: Vec<RealtimeTool>,
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
model: None,
input_audio_format: InputAudioFormat::default(),
output_audio_format: OutputAudioFormat::default(),
instructions: None,
modalities: default_modalities(),
voice: None,
temperature: None,
max_response_output_tokens: None,
turn_detection: TurnDetection::new(TurnDetectionType::default()),
input_audio_noise_reduction: None,
beta_fields: BetaFields::default(),
greeting_config: None,
tools: Vec::new(),
}
}
}
fn default_modalities() -> Vec<RealtimeModality> {
vec![RealtimeModality::Text, RealtimeModality::Audio]
}
mod optional_u16_string {
use serde::{Deserialize as _, Deserializer, Serializer, de::Error as _};
pub(super) fn serialize<S>(value: &Option<u16>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match value {
Some(value) => serializer.serialize_some(&value.to_string()),
None => serializer.serialize_none(),
}
}
pub(super) fn deserialize<'de, D>(deserializer: D) -> Result<Option<u16>, D::Error>
where
D: Deserializer<'de>,
{
let value = Option::<String>::deserialize(deserializer)?;
value
.map(|value| {
if value == "inf" {
return Ok(None);
}
value
.parse::<u16>()
.map_err(|_| {
D::Error::custom(
"max_response_output_tokens must be \"inf\" or a decimal u16 string",
)
})
.map(Some)
})
.transpose()
.map(Option::flatten)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ItemType {
Message,
FunctionCall,
FunctionCallOutput,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ItemContent {
#[serde(rename = "type", skip_serializing_if = "Option::is_none")]
pub type_: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub transcript: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealtimeConversationItem {
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<String>,
#[serde(rename = "type")]
pub type_: ItemType,
pub object: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub status: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub content: Vec<ItemContent>,
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output: Option<String>,
}
impl RealtimeConversationItem {
pub fn user_text(text: impl Into<String>) -> Self {
Self {
id: None,
type_: ItemType::Message,
object: "realtime.item".to_string(),
status: Some("completed".to_string()),
role: Some("user".to_string()),
content: vec![ItemContent {
type_: Some("input_text".to_string()),
text: Some(text.into()),
audio: None,
transcript: None,
}],
name: None,
arguments: None,
output: None,
}
}
pub fn function_output(call_name: impl Into<String>, output: impl Into<String>) -> Self {
Self {
id: None,
type_: ItemType::FunctionCallOutput,
object: "realtime.item".to_string(),
status: None,
role: None,
content: Vec::new(),
name: Some(call_name.into()),
arguments: None,
output: Some(output.into()),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TokenDetails {
#[serde(skip_serializing_if = "Option::is_none")]
pub text_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub audio_tokens: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub cached_tokens: Option<u64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct RealtimeUsage {
#[serde(default)]
pub total_tokens: u64,
#[serde(default)]
pub input_tokens: u64,
#[serde(default)]
pub output_tokens: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_token_details: Option<TokenDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_token_details: Option<TokenDetails>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealtimeResponse {
pub id: String,
pub object: String,
pub status: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub usage: Option<RealtimeUsage>,
}