1use std::future::Future;
9
10use bytes::Bytes;
11use serde::Deserialize;
12use serde::Serialize;
13use url::Url;
14
15use crate::error::NoSuchModelError;
16use crate::error::ProviderError;
17use crate::json::JsonObject;
18use crate::json::JsonValue;
19use crate::shared::AudioFormat;
20use crate::shared::ModelId;
21use crate::shared::ProviderId;
22use crate::shared::base64_bytes;
23
24pub trait RealtimeModel: Send + Sync + 'static {
26 fn provider(&self) -> &ProviderId;
28
29 fn model_id(&self) -> &ModelId;
31
32 fn do_create_client_secret(
34 &self,
35 options: ClientSecretOptions,
36 ) -> impl Future<Output = Result<ClientSecret, ProviderError>> + Send;
37
38 fn websocket_config(&self, token: &str, url: &Url) -> WebSocketConfig;
40
41 fn parse_server_event(&self, raw: JsonValue)
49 -> Result<Vec<RealtimeServerEvent>, ProviderError>;
50
51 fn serialize_client_event(
53 &self,
54 event: RealtimeClientEvent,
55 ) -> impl Future<Output = Result<JsonValue, ProviderError>> + Send;
56
57 fn build_session_config(
63 &self,
64 config: &RealtimeSessionConfig,
65 ) -> Result<JsonValue, ProviderError>;
66
67 fn health_check_response(&self, raw: &JsonValue) -> Option<JsonValue> {
69 let _ = raw;
70 None
71 }
72}
73
74pub trait RealtimeFactory: Send + Sync + 'static {
76 fn provider(&self) -> &ProviderId;
78
79 fn model(&self, model_id: &str) -> Result<crate::dynamic::RealtimeModelRef, NoSuchModelError>;
85
86 fn get_token(
88 &self,
89 options: GetTokenOptions,
90 ) -> impl Future<Output = Result<ClientSecret, ProviderError>> + Send;
91}
92
93#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
95pub struct ClientSecretOptions {
96 #[serde(default, skip_serializing_if = "Option::is_none")]
98 pub expires_after_seconds: Option<u64>,
99 #[serde(default, skip_serializing_if = "Option::is_none")]
101 pub session_config: Option<RealtimeSessionConfig>,
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
106pub struct GetTokenOptions {
107 pub model: ModelId,
109 #[serde(default, skip_serializing_if = "Option::is_none")]
111 pub expires_after_seconds: Option<u64>,
112 #[serde(default, skip_serializing_if = "Option::is_none")]
114 pub session_config: Option<RealtimeSessionConfig>,
115}
116
117#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
119pub struct ClientSecret {
120 pub token: String,
122 pub url: Url,
124 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub expires_at: Option<u64>,
127}
128
129impl std::fmt::Debug for ClientSecret {
130 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
131 f.debug_struct("ClientSecret")
132 .field("token", &"***")
133 .field("url", &self.url)
134 .field("expires_at", &self.expires_at)
135 .finish()
136 }
137}
138
139#[derive(Debug, Clone, PartialEq, Eq)]
141pub struct WebSocketConfig {
142 pub url: Url,
144 pub protocols: Vec<String>,
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
150#[serde(rename_all = "lowercase")]
151#[non_exhaustive]
152pub enum Modality {
153 Text,
155 Audio,
157}
158
159#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
161pub struct TranscriptionConfig {
162 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub model: Option<String>,
165 #[serde(default, skip_serializing_if = "Option::is_none")]
167 pub language: Option<String>,
168 #[serde(default, skip_serializing_if = "Option::is_none")]
170 pub prompt: Option<String>,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
175#[serde(rename_all = "kebab-case")]
176#[non_exhaustive]
177pub enum TurnDetectionKind {
178 ServerVad,
180 SemanticVad,
182 Disabled,
184}
185
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
188pub struct TurnDetection {
189 #[serde(rename = "type")]
191 pub kind: TurnDetectionKind,
192 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub threshold: Option<f64>,
195 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub silence_duration_ms: Option<u64>,
198 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub prefix_padding_ms: Option<u64>,
201}
202
203#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
205pub struct RealtimeToolDefinition {
206 pub name: String,
208 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub description: Option<String>,
211 pub parameters: JsonValue,
213}
214
215#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
217pub struct RealtimeSessionConfig {
218 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub instructions: Option<String>,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
223 pub voice: Option<String>,
224 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub output_modalities: Option<Vec<Modality>>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
229 pub input_audio_format: Option<AudioFormat>,
230 #[serde(default, skip_serializing_if = "Option::is_none")]
232 pub input_audio_transcription: Option<TranscriptionConfig>,
233 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub output_audio_transcription: Option<TranscriptionConfig>,
236 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub output_audio_format: Option<AudioFormat>,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
241 pub turn_detection: Option<TurnDetection>,
242 #[serde(default, skip_serializing_if = "Vec::is_empty")]
244 pub tools: Vec<RealtimeToolDefinition>,
245 #[serde(default, skip_serializing_if = "Option::is_none")]
247 pub provider_options: Option<JsonObject>,
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
252#[serde(rename_all = "lowercase")]
253#[non_exhaustive]
254pub enum ConversationRole {
255 User,
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
261#[serde(tag = "type", rename_all = "kebab-case")]
262#[non_exhaustive]
263pub enum ConversationItem {
264 TextMessage {
266 role: ConversationRole,
268 text: String,
270 },
271 AudioMessage {
273 role: ConversationRole,
275 #[serde(with = "base64_bytes")]
277 audio: Bytes,
278 },
279 FunctionCallOutput {
281 call_id: String,
283 #[serde(default, skip_serializing_if = "Option::is_none")]
285 name: Option<String>,
286 output: String,
288 },
289}
290
291#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
293pub struct ResponseCreateOptions {
294 #[serde(default, skip_serializing_if = "Option::is_none")]
296 pub modalities: Option<Vec<String>>,
297 #[serde(default, skip_serializing_if = "Option::is_none")]
299 pub instructions: Option<String>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
302 pub metadata: Option<JsonObject>,
303}
304
305#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
307#[serde(tag = "type", rename_all = "kebab-case")]
308#[non_exhaustive]
309pub enum RealtimeClientEvent {
310 SessionUpdate {
312 config: Box<RealtimeSessionConfig>,
314 },
315 InputAudioAppend {
317 #[serde(with = "base64_bytes")]
319 audio: Bytes,
320 },
321 InputAudioCommit,
323 InputAudioClear,
325 ConversationItemCreate {
327 item: ConversationItem,
329 },
330 ConversationItemTruncate {
332 item_id: String,
334 content_index: u32,
336 audio_end_ms: u64,
338 },
339 ResponseCreate {
341 #[serde(default, skip_serializing_if = "Option::is_none")]
343 options: Option<ResponseCreateOptions>,
344 },
345 ResponseCancel,
347}
348
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
353#[serde(tag = "type", rename_all = "kebab-case")]
354#[non_exhaustive]
355pub enum RealtimeServerEvent {
356 SessionCreated {
358 #[serde(default, skip_serializing_if = "Option::is_none")]
360 session_id: Option<String>,
361 raw: JsonValue,
363 },
364 SessionUpdated {
366 raw: JsonValue,
368 },
369 SpeechStarted {
371 #[serde(default, skip_serializing_if = "Option::is_none")]
373 item_id: Option<String>,
374 raw: JsonValue,
376 },
377 SpeechStopped {
379 #[serde(default, skip_serializing_if = "Option::is_none")]
381 item_id: Option<String>,
382 raw: JsonValue,
384 },
385 AudioCommitted {
387 #[serde(default, skip_serializing_if = "Option::is_none")]
389 item_id: Option<String>,
390 #[serde(default, skip_serializing_if = "Option::is_none")]
392 previous_item_id: Option<String>,
393 raw: JsonValue,
395 },
396 ConversationItemAdded {
398 item_id: String,
400 item: JsonValue,
402 raw: JsonValue,
404 },
405 InputTranscriptionCompleted {
407 item_id: String,
409 transcript: String,
411 raw: JsonValue,
413 },
414 ResponseCreated {
416 response_id: String,
418 raw: JsonValue,
420 },
421 ResponseDone {
423 response_id: String,
425 status: String,
427 raw: JsonValue,
429 },
430 OutputItemAdded {
432 response_id: String,
434 item_id: String,
436 raw: JsonValue,
438 },
439 OutputItemDone {
441 response_id: String,
443 item_id: String,
445 raw: JsonValue,
447 },
448 ContentPartAdded {
450 response_id: String,
452 item_id: String,
454 raw: JsonValue,
456 },
457 ContentPartDone {
459 response_id: String,
461 item_id: String,
463 raw: JsonValue,
465 },
466 AudioDelta {
468 response_id: String,
470 item_id: String,
472 #[serde(with = "base64_bytes")]
474 delta: Bytes,
475 raw: JsonValue,
477 },
478 AudioDone {
480 response_id: String,
482 item_id: String,
484 raw: JsonValue,
486 },
487 AudioTranscriptDelta {
489 response_id: String,
491 item_id: String,
493 delta: String,
495 raw: JsonValue,
497 },
498 AudioTranscriptDone {
500 response_id: String,
502 item_id: String,
504 #[serde(default, skip_serializing_if = "Option::is_none")]
506 transcript: Option<String>,
507 raw: JsonValue,
509 },
510 TextDelta {
512 response_id: String,
514 item_id: String,
516 delta: String,
518 raw: JsonValue,
520 },
521 TextDone {
523 response_id: String,
525 item_id: String,
527 #[serde(default, skip_serializing_if = "Option::is_none")]
529 text: Option<String>,
530 raw: JsonValue,
532 },
533 FunctionCallArgumentsDelta {
535 response_id: String,
537 item_id: String,
539 call_id: String,
541 delta: String,
543 raw: JsonValue,
545 },
546 FunctionCallArgumentsDone {
548 response_id: String,
550 item_id: String,
552 call_id: String,
554 name: String,
556 arguments: String,
558 raw: JsonValue,
560 },
561 Error {
563 message: String,
565 #[serde(default, skip_serializing_if = "Option::is_none")]
567 code: Option<String>,
568 raw: JsonValue,
570 },
571 Custom {
573 raw_type: String,
575 raw: JsonValue,
577 },
578}