Skip to main content

openai_protocol/builders/realtime/
response.rs

1//! Builder for RealtimeResponse
2//!
3//! Provides an ergonomic fluent API for constructing realtime response instances.
4
5use std::collections::HashMap;
6
7use crate::{
8    realtime_conversation::RealtimeConversationItem,
9    realtime_response::{
10        RealtimeResponse, RealtimeResponseCreateAudioOutput, RealtimeResponseObject,
11        RealtimeResponseStatus, RealtimeResponseUsage, ResponseStatus,
12    },
13    realtime_session::{MaxOutputTokens, OutputModality},
14};
15
16/// Builder for RealtimeResponse
17///
18/// Provides a fluent interface for constructing realtime responses with sensible defaults.
19#[must_use = "Builder does nothing until .build() is called"]
20#[derive(Clone, Debug)]
21pub struct RealtimeResponseBuilder {
22    id: String,
23    object: Option<RealtimeResponseObject>,
24    status: Option<ResponseStatus>,
25    status_details: Option<RealtimeResponseStatus>,
26    output: Vec<RealtimeConversationItem>,
27    metadata: HashMap<String, String>,
28    audio: Option<RealtimeResponseCreateAudioOutput>,
29    usage: Option<RealtimeResponseUsage>,
30    conversation_id: Option<String>,
31    output_modalities: Option<Vec<OutputModality>>,
32    max_output_tokens: Option<MaxOutputTokens>,
33}
34
35impl RealtimeResponseBuilder {
36    /// Create a new builder with the response ID.
37    ///
38    /// # Arguments
39    /// - `id`: Response ID (e.g., "resp_abc123")
40    pub fn new(id: impl Into<String>) -> Self {
41        Self {
42            id: id.into(),
43            object: Some(RealtimeResponseObject::RealtimeResponse),
44            status: Some(ResponseStatus::InProgress),
45            status_details: None,
46            output: Vec::new(),
47            metadata: HashMap::new(),
48            audio: None,
49            usage: None,
50            conversation_id: None,
51            output_modalities: None,
52            max_output_tokens: None,
53        }
54    }
55
56    /// Set the response status (default: InProgress).
57    pub fn status(mut self, status: ResponseStatus) -> Self {
58        self.status = Some(status);
59        self
60    }
61
62    /// Set status details (e.g. cancellation reason, failure error).
63    pub fn status_details(mut self, details: RealtimeResponseStatus) -> Self {
64        self.status_details = Some(details);
65        self
66    }
67
68    /// Set the full output items list.
69    pub fn output(mut self, output: Vec<RealtimeConversationItem>) -> Self {
70        self.output = output;
71        self
72    }
73
74    /// Add a single output item.
75    pub fn add_output(mut self, item: RealtimeConversationItem) -> Self {
76        self.output.push(item);
77        self
78    }
79
80    /// Set metadata.
81    pub fn metadata(mut self, metadata: HashMap<String, String>) -> Self {
82        self.metadata = metadata;
83        self
84    }
85
86    /// Add a single metadata entry.
87    pub fn add_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
88        self.metadata.insert(key.into(), value.into());
89        self
90    }
91
92    /// Set audio configuration.
93    pub fn audio(mut self, audio: RealtimeResponseCreateAudioOutput) -> Self {
94        self.audio = Some(audio);
95        self
96    }
97
98    /// Set usage statistics.
99    pub fn usage(mut self, usage: RealtimeResponseUsage) -> Self {
100        self.usage = Some(usage);
101        self
102    }
103
104    /// Set usage if provided (handles Option).
105    pub fn maybe_usage(mut self, usage: Option<RealtimeResponseUsage>) -> Self {
106        if let Some(u) = usage {
107            self.usage = Some(u);
108        }
109        self
110    }
111
112    /// Set the conversation ID.
113    pub fn conversation_id(mut self, id: impl Into<String>) -> Self {
114        self.conversation_id = Some(id.into());
115        self
116    }
117
118    /// Set output modalities.
119    pub fn output_modalities(mut self, modalities: Vec<OutputModality>) -> Self {
120        self.output_modalities = Some(modalities);
121        self
122    }
123
124    /// Set max output tokens.
125    pub fn max_output_tokens(mut self, max: MaxOutputTokens) -> Self {
126        self.max_output_tokens = Some(max);
127        self
128    }
129
130    /// Build the RealtimeResponse.
131    pub fn build(self) -> RealtimeResponse {
132        let metadata = if self.metadata.is_empty() {
133            None
134        } else {
135            Some(self.metadata)
136        };
137
138        let output = if self.output.is_empty() {
139            None
140        } else {
141            Some(self.output)
142        };
143
144        RealtimeResponse {
145            id: Some(self.id),
146            object: self.object,
147            status: self.status,
148            status_details: self.status_details,
149            output,
150            metadata,
151            audio: self.audio,
152            usage: self.usage,
153            conversation_id: self.conversation_id,
154            output_modalities: self.output_modalities,
155            max_output_tokens: self.max_output_tokens,
156        }
157    }
158}
159
160// ============================================================================
161// Tests
162// ============================================================================
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn test_build_minimal() {
170        let response = RealtimeResponseBuilder::new("resp_123").build();
171
172        assert_eq!(response.id.as_ref().unwrap(), "resp_123");
173        assert_eq!(
174            response.object.as_ref().unwrap(),
175            &RealtimeResponseObject::RealtimeResponse
176        );
177        assert_eq!(
178            response.status.as_ref().unwrap(),
179            &ResponseStatus::InProgress
180        );
181        assert!(response.output.is_none());
182        assert!(response.metadata.is_none());
183        assert!(response.usage.is_none());
184    }
185
186    #[test]
187    fn test_build_complete() {
188        let usage = RealtimeResponseUsage {
189            total_tokens: Some(100),
190            input_tokens: Some(40),
191            output_tokens: Some(60),
192            input_token_details: None,
193            output_token_details: None,
194        };
195
196        let response = RealtimeResponseBuilder::new("resp_full")
197            .status(ResponseStatus::Completed)
198            .conversation_id("conv_123")
199            .output_modalities(vec![OutputModality::Audio, OutputModality::Text])
200            .max_output_tokens(MaxOutputTokens::Integer(4096))
201            .add_metadata("session", "test")
202            .maybe_usage(Some(usage))
203            .build();
204
205        assert_eq!(response.id.as_ref().unwrap(), "resp_full");
206        assert_eq!(
207            response.status.as_ref().unwrap(),
208            &ResponseStatus::Completed
209        );
210        assert_eq!(response.conversation_id.as_ref().unwrap(), "conv_123");
211        assert_eq!(response.output_modalities.as_ref().unwrap().len(), 2);
212        assert!(response.metadata.is_some());
213        assert_eq!(response.usage.as_ref().unwrap().total_tokens, Some(100));
214    }
215}