outfox_openai/spec/realtime/
client_event.rs

1use serde::{Deserialize, Serialize};
2use tokio_tungstenite::tungstenite::Message;
3
4use super::{item::Item, session_resource::SessionResource};
5
6#[derive(Debug, Serialize, Deserialize, Clone, Default)]
7pub struct SessionUpdateEvent {
8    /// Optional client-generated ID used to identify this event.
9    #[serde(skip_serializing_if = "Option::is_none")]
10    pub event_id: Option<String>,
11    /// Session configuration to update.
12    pub session: SessionResource,
13}
14
15#[derive(Debug, Serialize, Deserialize, Clone, Default)]
16pub struct InputAudioBufferAppendEvent {
17    /// Optional client-generated ID used to identify this event.
18    #[serde(skip_serializing_if = "Option::is_none")]
19    pub event_id: Option<String>,
20    /// Base64-encoded audio bytes.
21    pub audio: String,
22}
23
24#[derive(Debug, Serialize, Deserialize, Clone, Default)]
25pub struct InputAudioBufferCommitEvent {
26    /// Optional client-generated ID used to identify this event.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub event_id: Option<String>,
29}
30
31#[derive(Debug, Serialize, Deserialize, Clone, Default)]
32pub struct InputAudioBufferClearEvent {
33    /// Optional client-generated ID used to identify this event.
34    #[serde(skip_serializing_if = "Option::is_none")]
35    pub event_id: Option<String>,
36}
37
38#[derive(Debug, Serialize, Deserialize, Clone)]
39pub struct ConversationItemCreateEvent {
40    /// Optional client-generated ID used to identify this event.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub event_id: Option<String>,
43
44    /// The ID of the preceding item after which the new item will be inserted.
45    #[serde(skip_serializing_if = "Option::is_none")]
46    pub previous_item_id: Option<String>,
47
48    /// The item to add to the conversation.
49    pub item: Item,
50}
51
52#[derive(Debug, Serialize, Deserialize, Clone, Default)]
53pub struct ConversationItemTruncateEvent {
54    /// Optional client-generated ID used to identify this event.
55    #[serde(skip_serializing_if = "Option::is_none")]
56    pub event_id: Option<String>,
57
58    /// The ID of the assistant message item to truncate.
59    pub item_id: String,
60
61    /// The index of the content part to truncate.
62    pub content_index: u32,
63
64    /// Inclusive duration up to which audio is truncated, in milliseconds.
65    pub audio_end_ms: u32,
66}
67
68#[derive(Debug, Serialize, Deserialize, Clone, Default)]
69pub struct ConversationItemDeleteEvent {
70    /// Optional client-generated ID used to identify this event.
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub event_id: Option<String>,
73
74    /// The ID of the item to delete.
75    pub item_id: String,
76}
77
78#[derive(Debug, Serialize, Deserialize, Clone, Default)]
79pub struct ResponseCreateEvent {
80    /// Optional client-generated ID used to identify this event.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub event_id: Option<String>,
83
84    /// Configuration for the response.
85    pub response: Option<SessionResource>,
86}
87
88#[derive(Debug, Serialize, Deserialize, Clone, Default)]
89pub struct ResponseCancelEvent {
90    /// Optional client-generated ID used to identify this event.
91    #[serde(skip_serializing_if = "Option::is_none")]
92    pub event_id: Option<String>,
93}
94
95/// These are events that the OpenAI Realtime WebSocket server will accept from the client.
96#[derive(Debug, Serialize, Deserialize)]
97#[serde(tag = "type")]
98pub enum ClientEvent {
99    /// Send this event to update the session’s default configuration.
100    #[serde(rename = "session.update")]
101    SessionUpdate(SessionUpdateEvent),
102
103    /// Send this event to append audio bytes to the input audio buffer.
104    #[serde(rename = "input_audio_buffer.append")]
105    InputAudioBufferAppend(InputAudioBufferAppendEvent),
106
107    /// Send this event to commit audio bytes to a user message.
108    #[serde(rename = "input_audio_buffer.commit")]
109    InputAudioBufferCommit(InputAudioBufferCommitEvent),
110
111    /// Send this event to clear the audio bytes in the buffer.
112    #[serde(rename = "input_audio_buffer.clear")]
113    InputAudioBufferClear(InputAudioBufferClearEvent),
114
115    /// Send this event when adding an item to the conversation.
116    #[serde(rename = "conversation.item.create")]
117    ConversationItemCreate(ConversationItemCreateEvent),
118
119    /// Send this event when you want to truncate a previous assistant message’s audio.
120    #[serde(rename = "conversation.item.truncate")]
121    ConversationItemTruncate(ConversationItemTruncateEvent),
122
123    /// Send this event when you want to remove any item from the conversation history.
124    #[serde(rename = "conversation.item.delete")]
125    ConversationItemDelete(ConversationItemDeleteEvent),
126
127    /// Send this event to trigger a response generation.
128    #[serde(rename = "response.create")]
129    ResponseCreate(ResponseCreateEvent),
130
131    /// Send this event to cancel an in-progress response.
132    #[serde(rename = "response.cancel")]
133    ResponseCancel(ResponseCancelEvent),
134}
135
136impl From<&ClientEvent> for String {
137    fn from(value: &ClientEvent) -> Self {
138        serde_json::to_string(value).unwrap()
139    }
140}
141
142impl From<ClientEvent> for Message {
143    fn from(value: ClientEvent) -> Self {
144        Message::Text(String::from(&value).into())
145    }
146}
147
148macro_rules! message_from_event {
149    ($from_typ:ty, $evt_typ:ty) => {
150        impl From<$from_typ> for Message {
151            fn from(value: $from_typ) -> Self {
152                Self::from(<$evt_typ>::from(value))
153            }
154        }
155    };
156}
157
158macro_rules! event_from {
159    ($from_typ:ty, $evt_typ:ty, $variant:ident) => {
160        impl From<$from_typ> for $evt_typ {
161            fn from(value: $from_typ) -> Self {
162                <$evt_typ>::$variant(value)
163            }
164        }
165    };
166}
167
168event_from!(SessionUpdateEvent, ClientEvent, SessionUpdate);
169event_from!(
170    InputAudioBufferAppendEvent,
171    ClientEvent,
172    InputAudioBufferAppend
173);
174event_from!(
175    InputAudioBufferCommitEvent,
176    ClientEvent,
177    InputAudioBufferCommit
178);
179event_from!(
180    InputAudioBufferClearEvent,
181    ClientEvent,
182    InputAudioBufferClear
183);
184event_from!(
185    ConversationItemCreateEvent,
186    ClientEvent,
187    ConversationItemCreate
188);
189event_from!(
190    ConversationItemTruncateEvent,
191    ClientEvent,
192    ConversationItemTruncate
193);
194event_from!(
195    ConversationItemDeleteEvent,
196    ClientEvent,
197    ConversationItemDelete
198);
199event_from!(ResponseCreateEvent, ClientEvent, ResponseCreate);
200event_from!(ResponseCancelEvent, ClientEvent, ResponseCancel);
201
202message_from_event!(SessionUpdateEvent, ClientEvent);
203message_from_event!(InputAudioBufferAppendEvent, ClientEvent);
204message_from_event!(InputAudioBufferCommitEvent, ClientEvent);
205message_from_event!(InputAudioBufferClearEvent, ClientEvent);
206message_from_event!(ConversationItemCreateEvent, ClientEvent);
207message_from_event!(ConversationItemTruncateEvent, ClientEvent);
208message_from_event!(ConversationItemDeleteEvent, ClientEvent);
209message_from_event!(ResponseCreateEvent, ClientEvent);
210message_from_event!(ResponseCancelEvent, ClientEvent);
211
212impl From<Item> for ConversationItemCreateEvent {
213    fn from(value: Item) -> Self {
214        Self {
215            event_id: None,
216            previous_item_id: None,
217            item: value,
218        }
219    }
220}