gproxy_protocol/protocol/openai/realtime/
mod.rs1mod client_events;
8mod items;
9mod response;
10mod server_events;
11mod session;
12
13pub use client_events::*;
14pub use items::*;
15pub use response::*;
16pub use server_events::*;
17pub use session::*;
18
19use super::common::OpenAiWireModel;
20
21pub type RealtimeWireModel = OpenAiWireModel<RealtimeClientEvent, RealtimeServerEvent>;
22
23macro_rules! realtime_string_enum {
24 ($outer:ident, $known:ident { $($variant:ident => $wire:literal),+ $(,)? }) => {
25 #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
26 #[serde(untagged)]
27 #[non_exhaustive]
28 pub enum $outer {
29 Known($known),
30 Unknown(String),
31 }
32
33 impl<'de> serde::Deserialize<'de> for $outer {
34 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
35 crate::protocol::extensible::deserialize_extensible(d, Self::Known, Self::Unknown)
36 }
37 }
38
39 #[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
40 #[non_exhaustive]
41 pub enum $known {
42 $(#[serde(rename = $wire)] $variant,)+
43 }
44 };
45}
46
47pub(crate) use realtime_string_enum;
48
49#[cfg(test)]
50mod tests {
51 use serde_json::json;
52
53 use super::*;
54
55 #[test]
56 fn session_update_round_trips_ga_audio_shape() {
57 let value = json!({
58 "type": "session.update",
59 "session": {
60 "type": "realtime",
61 "model": "gpt-realtime",
62 "output_modalities": ["audio"],
63 "instructions": "be brief",
64 "audio": {
65 "input": {
66 "format": {"type": "audio/pcm", "rate": 24000},
67 "turn_detection": {"type": "semantic_vad", "eagerness": "low"}
68 },
69 "output": {"format": {"type": "audio/pcmu"}, "voice": "marin", "speed": 1.1}
70 }
71 }
72 });
73 let parsed: RealtimeClientEvent = serde_json::from_value(value.clone()).unwrap();
74 let RealtimeClientEvent::SessionUpdate { session, .. } = &parsed else {
75 panic!("expected session.update")
76 };
77 assert!(matches!(
78 session
79 .audio
80 .as_ref()
81 .unwrap()
82 .input
83 .as_ref()
84 .unwrap()
85 .format,
86 Some(RealtimeAudioFormat::Pcm {
87 rate: Some(24000),
88 ..
89 })
90 ));
91 assert_eq!(serde_json::to_value(&parsed).unwrap(), value);
92 }
93
94 #[test]
95 fn server_events_parse_known_and_fall_back_to_unknown() {
96 let known: RealtimeServerEvent = serde_json::from_value(json!({
97 "type": "response.output_audio.delta",
98 "response_id": "resp_1",
99 "item_id": "item_1",
100 "output_index": 0,
101 "content_index": 0,
102 "delta": "b64"
103 }))
104 .unwrap();
105 let RealtimeServerEvent::Known(known) = known else {
106 panic!("expected known event")
107 };
108 assert!(matches!(
109 *known,
110 KnownRealtimeServerEvent::OutputAudioDelta { .. }
111 ));
112
113 let unknown: RealtimeServerEvent = serde_json::from_value(json!({
114 "type": "response.hologram.delta",
115 "delta": "?"
116 }))
117 .unwrap();
118 assert!(matches!(unknown, RealtimeServerEvent::Unknown(_)));
119 }
120}