Skip to main content

openai_tools/live/
session.rs

1//! Session configuration types for the OpenAI Live API.
2
3use serde::{de, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer};
4
5use crate::common::models::LiveModel;
6use crate::responses::request::{ReasoningEffort, ReasoningSummary};
7
8/// A built-in or custom voice for Live audio output.
9#[derive(Debug, Clone, PartialEq, Eq, Default)]
10#[non_exhaustive]
11pub enum LiveVoice {
12    Alloy,
13    Ash,
14    Ballad,
15    Beacon,
16    Bossa,
17    Cedar,
18    Cinder,
19    Coral,
20    Delta,
21    Echo,
22    Gleam,
23    #[default]
24    Marin,
25    Meridian,
26    Quartz,
27    Ripple,
28    Sage,
29    Shimmer,
30    Stone,
31    Tempo,
32    Verse,
33    Vesper,
34    Willow,
35    /// A project-specific custom voice.
36    Custom {
37        id: String,
38    },
39}
40
41impl LiveVoice {
42    fn built_in_name(&self) -> Option<&'static str> {
43        match self {
44            Self::Alloy => Some("alloy"),
45            Self::Ash => Some("ash"),
46            Self::Ballad => Some("ballad"),
47            Self::Beacon => Some("beacon"),
48            Self::Bossa => Some("bossa"),
49            Self::Cedar => Some("cedar"),
50            Self::Cinder => Some("cinder"),
51            Self::Coral => Some("coral"),
52            Self::Delta => Some("delta"),
53            Self::Echo => Some("echo"),
54            Self::Gleam => Some("gleam"),
55            Self::Marin => Some("marin"),
56            Self::Meridian => Some("meridian"),
57            Self::Quartz => Some("quartz"),
58            Self::Ripple => Some("ripple"),
59            Self::Sage => Some("sage"),
60            Self::Shimmer => Some("shimmer"),
61            Self::Stone => Some("stone"),
62            Self::Tempo => Some("tempo"),
63            Self::Verse => Some("verse"),
64            Self::Vesper => Some("vesper"),
65            Self::Willow => Some("willow"),
66            Self::Custom { .. } => None,
67        }
68    }
69
70    fn from_built_in(name: &str) -> Option<Self> {
71        Some(match name {
72            "alloy" => Self::Alloy,
73            "ash" => Self::Ash,
74            "ballad" => Self::Ballad,
75            "beacon" => Self::Beacon,
76            "bossa" => Self::Bossa,
77            "cedar" => Self::Cedar,
78            "cinder" => Self::Cinder,
79            "coral" => Self::Coral,
80            "delta" => Self::Delta,
81            "echo" => Self::Echo,
82            "gleam" => Self::Gleam,
83            "marin" => Self::Marin,
84            "meridian" => Self::Meridian,
85            "quartz" => Self::Quartz,
86            "ripple" => Self::Ripple,
87            "sage" => Self::Sage,
88            "shimmer" => Self::Shimmer,
89            "stone" => Self::Stone,
90            "tempo" => Self::Tempo,
91            "verse" => Self::Verse,
92            "vesper" => Self::Vesper,
93            "willow" => Self::Willow,
94            _ => return None,
95        })
96    }
97}
98
99impl Serialize for LiveVoice {
100    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
101    where
102        S: Serializer,
103    {
104        if let Some(name) = self.built_in_name() {
105            serializer.serialize_str(name)
106        } else if let Self::Custom { id } = self {
107            #[derive(Serialize)]
108            struct CustomVoice<'a> {
109                id: &'a str,
110            }
111            CustomVoice { id }.serialize(serializer)
112        } else {
113            unreachable!("all LiveVoice variants are handled")
114        }
115    }
116}
117
118impl<'de> Deserialize<'de> for LiveVoice {
119    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
120    where
121        D: Deserializer<'de>,
122    {
123        #[derive(Deserialize)]
124        #[serde(untagged)]
125        enum VoiceValue {
126            BuiltIn(String),
127            Custom { id: String },
128        }
129
130        match VoiceValue::deserialize(deserializer)? {
131            VoiceValue::BuiltIn(name) => Self::from_built_in(&name).ok_or_else(|| de::Error::unknown_variant(&name, &[])),
132            VoiceValue::Custom { id } => Ok(Self::Custom { id }),
133        }
134    }
135}
136
137/// Encoding used for Live input and output audio.
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[non_exhaustive]
140pub enum LiveAudioEncoding {
141    #[serde(rename = "audio/pcm")]
142    Pcm,
143    #[serde(rename = "audio/pcmu")]
144    Pcmu,
145    #[serde(rename = "audio/pcma")]
146    Pcma,
147    /// An encoding added after this library version.
148    #[serde(untagged)]
149    Other(String),
150}
151
152/// Audio encoding and sample rate shared by Live input and output.
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct LiveAudioFormat {
155    /// Audio MIME-style encoding name.
156    #[serde(rename = "type")]
157    pub encoding: LiveAudioEncoding,
158    /// Sample rate in hertz.
159    pub rate: u32,
160}
161
162impl LiveAudioFormat {
163    /// Signed PCM16 little-endian audio at 24 kHz.
164    pub const fn pcm24k() -> Self {
165        Self { encoding: LiveAudioEncoding::Pcm, rate: 24_000 }
166    }
167
168    /// Signed PCM16 little-endian audio at 16 kHz.
169    pub const fn pcm16k() -> Self {
170        Self { encoding: LiveAudioEncoding::Pcm, rate: 16_000 }
171    }
172
173    /// G.711 mu-law audio at 8 kHz.
174    pub const fn pcmu() -> Self {
175        Self { encoding: LiveAudioEncoding::Pcmu, rate: 8_000 }
176    }
177
178    /// G.711 A-law audio at 8 kHz.
179    pub const fn pcma() -> Self {
180        Self { encoding: LiveAudioEncoding::Pcma, rate: 8_000 }
181    }
182}
183
184/// Live audio settings.
185#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
186pub struct LiveAudioConfig {
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub format: Option<LiveAudioFormat>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub output: Option<LiveAudioOutputConfig>,
191}
192
193/// Live audio output settings.
194#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
195pub struct LiveAudioOutputConfig {
196    #[serde(skip_serializing_if = "Option::is_none")]
197    pub voice: Option<LiveVoice>,
198}
199
200/// Delegation target and configuration.
201#[derive(Debug, Clone)]
202pub enum Delegation {
203    /// The application handles delegated work.
204    Client,
205    /// The Live service delegates to the Responses API.
206    Responses(ResponsesDelegationConfig),
207}
208
209impl Delegation {
210    /// Creates Responses API delegation settings.
211    pub fn responses(config: ResponsesDelegationConfig) -> Self {
212        Self::Responses(config)
213    }
214}
215
216impl Serialize for Delegation {
217    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
218    where
219        S: Serializer,
220    {
221        match self {
222            Self::Client => {
223                let mut state = serializer.serialize_struct("Delegation", 1)?;
224                state.serialize_field("type", "client")?;
225                state.end()
226            }
227            Self::Responses(config) => {
228                let mut state = serializer.serialize_struct("Delegation", 2)?;
229                state.serialize_field("type", "responses")?;
230                state.serialize_field("responses", config)?;
231                state.end()
232            }
233        }
234    }
235}
236
237/// Responses API backend configuration used by Live delegation.
238#[derive(Debug, Clone, Serialize)]
239pub struct ResponsesDelegationConfig {
240    pub model: String,
241    #[serde(skip_serializing_if = "Option::is_none")]
242    pub instructions: Option<String>,
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub max_output_tokens: Option<u32>,
245    #[serde(skip_serializing_if = "Option::is_none")]
246    pub parallel_tool_calls: Option<bool>,
247    #[serde(skip_serializing_if = "Option::is_none")]
248    pub reasoning: Option<ResponsesReasoningConfig>,
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub service_tier: Option<serde_json::Value>,
251    #[serde(skip_serializing_if = "Option::is_none")]
252    pub text: Option<ResponsesTextConfig>,
253    #[serde(skip_serializing_if = "Option::is_none")]
254    pub tool_choice: Option<serde_json::Value>,
255    #[serde(skip_serializing_if = "Option::is_none")]
256    pub tools: Option<serde_json::Value>,
257}
258
259/// Reasoning settings for Responses delegation.
260#[derive(Debug, Clone, Default, Serialize)]
261pub struct ResponsesReasoningConfig {
262    #[serde(skip_serializing_if = "Option::is_none")]
263    pub effort: Option<ReasoningEffort>,
264    #[serde(skip_serializing_if = "Option::is_none")]
265    pub summary: Option<ReasoningSummary>,
266}
267
268/// Text settings for Responses delegation.
269#[derive(Debug, Clone, Default, Serialize)]
270pub struct ResponsesTextConfig {
271    #[serde(skip_serializing_if = "Option::is_none")]
272    pub verbosity: Option<String>,
273}
274
275/// Configuration carried by `session.start`.
276#[derive(Debug, Clone, Default, Serialize)]
277pub struct LiveSessionConfig {
278    #[serde(skip_serializing_if = "Option::is_none")]
279    pub model: Option<LiveModel>,
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub instructions: Option<String>,
282    #[serde(skip_serializing_if = "Option::is_none")]
283    pub audio: Option<LiveAudioConfig>,
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub delegation: Option<Delegation>,
286    #[serde(skip_serializing_if = "Option::is_none")]
287    pub input: Option<Vec<serde_json::Value>>,
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub store: Option<bool>,
290}
291
292/// Mutable portion of a Live session configuration.
293#[derive(Debug, Clone, Serialize)]
294pub struct LiveSessionUpdate {
295    pub delegation: Delegation,
296}