Skip to main content

adk_realtime/
events.rs

1//! Event types for realtime communication.
2//!
3//! These events follow a unified model inspired by the OpenAI Agents SDK,
4//! abstracting over provider-specific event formats.
5//!
6//! Audio data is transported as raw bytes (`Vec<u8>`) internally but serialized
7//! as base64 on the wire for JSON compatibility.
8
9use base64::Engine;
10use serde::{Deserialize, Serialize};
11use serde_json::Value;
12
13// ── Custom serde for base64-encoded audio ───────────────────────────────
14
15fn deserialize_audio_bytes<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
16where
17    D: serde::Deserializer<'de>,
18{
19    let s = String::deserialize(deserializer)?;
20    base64::engine::general_purpose::STANDARD.decode(&s).map_err(serde::de::Error::custom)
21}
22
23fn serialize_audio_bytes<S>(bytes: &[u8], serializer: S) -> Result<S::Ok, S::Error>
24where
25    S: serde::Serializer,
26{
27    let s = base64::engine::general_purpose::STANDARD.encode(bytes);
28    serializer.serialize_str(&s)
29}
30
31// ── Client Events ───────────────────────────────────────────────────────
32
33/// Events sent from the client to the realtime server.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35#[serde(tag = "type")]
36#[non_exhaustive]
37pub enum ClientEvent {
38    /// Update session configuration.
39    #[serde(rename = "session.update")]
40    SessionUpdate {
41        /// Updated session configuration.
42        session: Value,
43    },
44
45    /// Append audio to the input buffer.
46    #[serde(rename = "input_audio_buffer.append")]
47    AudioDelta {
48        /// Optional event ID.
49        #[serde(skip_serializing_if = "Option::is_none")]
50        event_id: Option<String>,
51        /// Audio data (raw bytes, serialized as base64 on the wire).
52        #[serde(
53            serialize_with = "serialize_audio_bytes",
54            deserialize_with = "deserialize_audio_bytes"
55        )]
56        audio: Vec<u8>,
57        /// Audio format metadata for multi-format pipelines and debugging.
58        /// Skipped during serialization — the server infers format from the session config.
59        #[serde(skip)]
60        format: Option<crate::audio::AudioFormat>,
61    },
62
63    /// Commit the current audio buffer (manual mode).
64    #[serde(rename = "input_audio_buffer.commit")]
65    InputAudioBufferCommit,
66
67    /// Clear the audio input buffer.
68    #[serde(rename = "input_audio_buffer.clear")]
69    InputAudioBufferClear,
70
71    /// Send a text message or tool response.
72    #[serde(rename = "conversation.item.create")]
73    ConversationItemCreate {
74        /// The conversation item (flexible JSON for provider compatibility).
75        item: Value,
76    },
77
78    /// Trigger a response from the model.
79    #[serde(rename = "response.create")]
80    ResponseCreate {
81        /// Optional response configuration.
82        #[serde(skip_serializing_if = "Option::is_none")]
83        config: Option<Value>,
84    },
85
86    /// Cancel/interrupt the current response.
87    #[serde(rename = "response.cancel")]
88    ResponseCancel,
89
90    /// A standard message using `adk_core`'s native Role and Part types.
91    #[serde(rename = "message")]
92    Message {
93        /// Role of the message.
94        role: String,
95        /// Content parts of the message.
96        parts: Vec<adk_core::types::Part>,
97    },
98
99    /// Universal intent to update session configuration mid-flight.
100    ///
101    /// This is treated as a runner/control-plane internal intent and should not
102    /// be sent directly to providers without interception. By construction, it
103    /// is explicitly untagged from serialization to guarantee it cannot
104    /// leak onto the WebSocket wire.
105    #[serde(skip_serializing)]
106    UpdateSession {
107        /// New system instructions.
108        #[serde(skip_serializing_if = "Option::is_none")]
109        instructions: Option<String>,
110        /// New tools definition.
111        #[serde(skip_serializing_if = "Option::is_none")]
112        tools: Option<Vec<crate::config::ToolDefinition>>,
113    },
114}
115
116/// A conversation item for text or tool responses.
117#[derive(Debug, Clone, Serialize, Deserialize)]
118pub struct ConversationItem {
119    /// Unique ID for this item.
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub id: Option<String>,
122    /// Item type: "message" or "function_call_output".
123    #[serde(rename = "type")]
124    pub item_type: String,
125    /// Role: "user", "assistant", or "system".
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub role: Option<String>,
128    /// Content parts.
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub content: Option<Vec<ContentPart>>,
131    /// For tool responses: the call ID being responded to.
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub call_id: Option<String>,
134    /// For tool responses: the output value.
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub output: Option<String>,
137}
138
139/// A content part within a conversation item.
140#[derive(Debug, Clone, Serialize, Deserialize)]
141pub struct ContentPart {
142    /// Content type: "input_text", "input_audio", "text", "audio".
143    #[serde(rename = "type")]
144    pub content_type: String,
145    /// Text content.
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub text: Option<String>,
148    /// Base64-encoded audio content.
149    #[serde(skip_serializing_if = "Option::is_none")]
150    pub audio: Option<String>,
151    /// Transcript of audio content.
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub transcript: Option<String>,
154}
155
156impl ConversationItem {
157    /// Create a user text message item.
158    pub fn user_text(text: impl Into<String>) -> Self {
159        Self {
160            id: None,
161            item_type: "message".to_string(),
162            role: Some("user".to_string()),
163            content: Some(vec![ContentPart {
164                content_type: "input_text".to_string(),
165                text: Some(text.into()),
166                audio: None,
167                transcript: None,
168            }]),
169            call_id: None,
170            output: None,
171        }
172    }
173
174    /// Create a tool response item.
175    pub fn tool_response(call_id: impl Into<String>, output: impl Into<String>) -> Self {
176        Self {
177            id: None,
178            item_type: "function_call_output".to_string(),
179            role: None,
180            content: None,
181            call_id: Some(call_id.into()),
182            output: Some(output.into()),
183        }
184    }
185}
186
187// ── Server Events ───────────────────────────────────────────────────────
188
189/// Events received from the realtime server.
190///
191/// This is a unified event type that abstracts over provider-specific formats.
192/// Audio data is stored as raw bytes (`Vec<u8>`) — decoded from base64 at the
193/// transport boundary so consumers never need to deal with encoding.
194#[derive(Debug, Clone, Serialize, Deserialize)]
195#[serde(tag = "type")]
196#[non_exhaustive]
197pub enum ServerEvent {
198    /// Session was created/connected.
199    #[serde(rename = "session.created")]
200    SessionCreated {
201        /// Unique event ID.
202        event_id: String,
203        /// Session details.
204        session: Value,
205    },
206
207    /// Session configuration was updated.
208    #[serde(rename = "session.updated")]
209    SessionUpdated {
210        /// Unique event ID.
211        event_id: String,
212        /// Updated session details.
213        session: Value,
214    },
215
216    /// Error occurred.
217    #[serde(rename = "error")]
218    Error {
219        /// Unique event ID.
220        event_id: String,
221        /// Error details.
222        error: ErrorInfo,
223    },
224
225    /// User speech started (VAD detected).
226    #[serde(rename = "input_audio_buffer.speech_started")]
227    SpeechStarted {
228        /// Unique event ID.
229        event_id: String,
230        /// Audio start time in milliseconds.
231        audio_start_ms: u64,
232    },
233
234    /// User speech ended (VAD detected).
235    #[serde(rename = "input_audio_buffer.speech_stopped")]
236    SpeechStopped {
237        /// Unique event ID.
238        event_id: String,
239        /// Audio end time in milliseconds.
240        audio_end_ms: u64,
241    },
242
243    /// Audio input buffer was committed.
244    #[serde(rename = "input_audio_buffer.committed")]
245    AudioCommitted {
246        /// Unique event ID.
247        event_id: String,
248        /// ID of the created item.
249        item_id: String,
250    },
251
252    /// Audio input buffer was cleared.
253    #[serde(rename = "input_audio_buffer.cleared")]
254    AudioCleared {
255        /// Unique event ID.
256        event_id: String,
257    },
258
259    /// Conversation item was created.
260    #[serde(rename = "conversation.item.created")]
261    ItemCreated {
262        /// Unique event ID.
263        event_id: String,
264        /// The created item.
265        item: Value,
266    },
267
268    /// Response generation started.
269    #[serde(rename = "response.created")]
270    ResponseCreated {
271        /// Unique event ID.
272        event_id: String,
273        /// Response details.
274        response: Value,
275    },
276
277    /// Response generation completed.
278    #[serde(rename = "response.done")]
279    ResponseDone {
280        /// Unique event ID.
281        event_id: String,
282        /// Final response details.
283        response: Value,
284    },
285
286    /// Response output item added.
287    #[serde(rename = "response.output_item.added")]
288    OutputItemAdded {
289        /// Unique event ID.
290        event_id: String,
291        /// Response ID.
292        response_id: String,
293        /// Output index.
294        output_index: u32,
295        /// The output item.
296        item: Value,
297    },
298
299    /// Response output item completed.
300    #[serde(rename = "response.output_item.done")]
301    OutputItemDone {
302        /// Unique event ID.
303        event_id: String,
304        /// Response ID.
305        response_id: String,
306        /// Output index.
307        output_index: u32,
308        /// The completed item.
309        item: Value,
310    },
311
312    /// Audio delta (chunk of output audio as raw bytes).
313    #[serde(alias = "response.audio.delta", rename = "response.output_audio.delta")]
314    AudioDelta {
315        /// Unique event ID.
316        event_id: String,
317        /// Response ID.
318        response_id: String,
319        /// Item ID.
320        item_id: String,
321        /// Output index.
322        output_index: u32,
323        /// Content index.
324        content_index: u32,
325        /// Audio data (raw bytes, serialized as base64 on the wire).
326        #[serde(
327            serialize_with = "serialize_audio_bytes",
328            deserialize_with = "deserialize_audio_bytes"
329        )]
330        delta: Vec<u8>,
331    },
332
333    /// Audio output completed.
334    #[serde(alias = "response.audio.done", rename = "response.output_audio.done")]
335    AudioDone {
336        /// Unique event ID.
337        event_id: String,
338        /// Response ID.
339        response_id: String,
340        /// Item ID.
341        item_id: String,
342        /// Output index.
343        output_index: u32,
344        /// Content index.
345        content_index: u32,
346    },
347
348    /// Text delta (chunk of output text).
349    #[serde(alias = "response.text.delta", rename = "response.output_text.delta")]
350    TextDelta {
351        /// Unique event ID.
352        event_id: String,
353        /// Response ID.
354        response_id: String,
355        /// Item ID.
356        item_id: String,
357        /// Output index.
358        output_index: u32,
359        /// Content index.
360        content_index: u32,
361        /// Text content.
362        delta: String,
363    },
364
365    /// Text output completed.
366    #[serde(alias = "response.text.done", rename = "response.output_text.done")]
367    TextDone {
368        /// Unique event ID.
369        event_id: String,
370        /// Response ID.
371        response_id: String,
372        /// Item ID.
373        item_id: String,
374        /// Output index.
375        output_index: u32,
376        /// Content index.
377        content_index: u32,
378        /// Complete text.
379        text: String,
380    },
381
382    /// Audio transcript delta.
383    #[serde(
384        alias = "response.audio_transcript.delta",
385        rename = "response.output_audio_transcript.delta"
386    )]
387    TranscriptDelta {
388        /// Unique event ID.
389        event_id: String,
390        /// Response ID.
391        response_id: String,
392        /// Item ID.
393        item_id: String,
394        /// Output index.
395        output_index: u32,
396        /// Content index.
397        content_index: u32,
398        /// Transcript delta.
399        delta: String,
400    },
401
402    /// Audio transcript completed.
403    #[serde(
404        alias = "response.audio_transcript.done",
405        rename = "response.output_audio_transcript.done"
406    )]
407    TranscriptDone {
408        /// Unique event ID.
409        event_id: String,
410        /// Response ID.
411        response_id: String,
412        /// Item ID.
413        item_id: String,
414        /// Output index.
415        output_index: u32,
416        /// Content index.
417        content_index: u32,
418        /// Complete transcript.
419        transcript: String,
420    },
421
422    /// Function call arguments delta.
423    #[serde(rename = "response.function_call_arguments.delta")]
424    FunctionCallDelta {
425        /// Unique event ID.
426        event_id: String,
427        /// Response ID.
428        response_id: String,
429        /// Item ID.
430        item_id: String,
431        /// Output index.
432        output_index: u32,
433        /// Call ID.
434        call_id: String,
435        /// Arguments delta.
436        delta: String,
437    },
438
439    /// Function call completed.
440    #[serde(rename = "response.function_call_arguments.done")]
441    FunctionCallDone {
442        /// Unique event ID.
443        event_id: String,
444        /// Response ID.
445        response_id: String,
446        /// Item ID.
447        item_id: String,
448        /// Output index.
449        output_index: u32,
450        /// Call ID.
451        call_id: String,
452        /// Function name.
453        name: String,
454        /// Complete arguments.
455        arguments: String,
456    },
457
458    /// Rate limit information.
459    #[serde(rename = "rate_limits.updated")]
460    RateLimitsUpdated {
461        /// Unique event ID.
462        event_id: String,
463        /// Rate limit details.
464        rate_limits: Vec<RateLimit>,
465    },
466
467    /// GA API: User input audio transcription delta (streaming partial transcript).
468    #[serde(rename = "conversation.item.input_audio_transcription.delta")]
469    InputTranscriptDelta {
470        /// Item ID for the input audio item being transcribed.
471        item_id: String,
472        /// Content index.
473        content_index: u32,
474        /// Partial transcript text.
475        delta: String,
476    },
477
478    /// GA API: User input audio transcription completed (final transcript).
479    #[serde(rename = "conversation.item.input_audio_transcription.completed")]
480    InputTranscriptCompleted {
481        /// Item ID for the input audio item.
482        item_id: String,
483        /// Content index.
484        content_index: u32,
485        /// Final complete transcript.
486        transcript: String,
487    },
488
489    /// Unknown event type (for forward compatibility).
490    #[serde(other)]
491    Unknown,
492}
493
494/// Error information from the server.
495#[derive(Debug, Clone, Serialize, Deserialize)]
496pub struct ErrorInfo {
497    /// Error type/code.
498    #[serde(rename = "type")]
499    pub error_type: String,
500    /// Error code.
501    #[serde(skip_serializing_if = "Option::is_none")]
502    pub code: Option<String>,
503    /// Human-readable error message.
504    pub message: String,
505    /// Additional error parameters.
506    #[serde(skip_serializing_if = "Option::is_none")]
507    pub param: Option<String>,
508}
509
510/// Rate limit information.
511#[derive(Debug, Clone, Serialize, Deserialize)]
512pub struct RateLimit {
513    /// Limit name.
514    pub name: String,
515    /// Maximum allowed.
516    pub limit: u64,
517    /// Currently remaining.
518    pub remaining: u64,
519    /// Time until reset.
520    pub reset_seconds: f64,
521}
522
523/// A simplified tool call representation.
524#[derive(Debug, Clone, Serialize, Deserialize)]
525pub struct ToolCall {
526    /// Unique call ID (used for responses).
527    pub call_id: String,
528    /// Tool/function name.
529    pub name: String,
530    /// Arguments as JSON.
531    pub arguments: Value,
532}
533
534/// A tool response to send back to the model.
535#[derive(Debug, Clone, Serialize, Deserialize)]
536pub struct ToolResponse {
537    /// The call ID being responded to.
538    pub call_id: String,
539    /// The result/output of the tool execution.
540    pub output: Value,
541}
542
543impl ToolResponse {
544    /// Create a new tool response.
545    pub fn new(call_id: impl Into<String>, output: impl Serialize) -> Self {
546        Self {
547            call_id: call_id.into(),
548            output: serde_json::to_value(output).unwrap_or(Value::Null),
549        }
550    }
551
552    /// Create a tool response from a string output.
553    pub fn from_string(call_id: impl Into<String>, output: impl Into<String>) -> Self {
554        Self { call_id: call_id.into(), output: Value::String(output.into()) }
555    }
556}