Skip to main content

gproxy_protocol/wire/openai/realtime/
rtc.rs

1//! WebRTC call setup: Create realtime call.md. SDP answer and Location remain
2//! raw HTTP body/headers; multipart session is a direct JSON session object.
3use super::session::RealtimeSessionCreateRequest;
4use crate::{
5    WireRequest, WireResponse,
6    connection::{HttpBody, MultipartPart},
7};
8
9#[derive(Debug, gproxy_protocol_macros::WireBuilder)]
10#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
11#[derive(gproxy_protocol_macros::DeclaredFields)]
12pub struct CreateRealtimeCallMultipartForm {
13    pub sdp: MultipartPart,
14    pub session: Option<RealtimeSessionCreateRequest>,
15}
16pub type CreateRealtimeCallRequest = WireRequest<CreateRealtimeCallMultipartForm>;
17pub type CreateRealtimeCallResponse = WireResponse<HttpBody>;
18/// JSON value of the multipart session part, without an extra wrapper object.
19pub type RealtimeCallSessionJson = RealtimeSessionCreateRequest;
20
21/// Frameless WebRTC setup at `POST /v1/live`. Unlike Realtime, this session
22/// has no required `type: realtime`. Source: samples/codex/codex-rs/codex-api/
23/// src/endpoint/realtime_websocket/methods_frameless_bidi.rs::session_json.
24#[derive(
25    Debug,
26    Clone,
27    PartialEq,
28    serde::Serialize,
29    serde::Deserialize,
30    gproxy_protocol_macros::WireBuilder,
31    gproxy_protocol_macros::DeclaredFields,
32)]
33#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
34pub struct LiveSessionCreateRequest {
35    #[serde(skip_serializing_if = "Option::is_none")]
36    pub model: Option<String>,
37    #[serde(skip_serializing_if = "Option::is_none")]
38    pub instructions: Option<String>,
39    #[serde(skip_serializing_if = "Option::is_none")]
40    pub audio: Option<super::RealtimeAudioConfig>,
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub delegation: Option<LiveDelegation>,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub initial_items: Option<Vec<LiveInitialMessage>>,
45    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
46    pub rest: crate::Rest,
47}
48
49#[derive(
50    Debug,
51    Clone,
52    PartialEq,
53    serde::Serialize,
54    serde::Deserialize,
55    gproxy_protocol_macros::WireBuilder,
56    gproxy_protocol_macros::DeclaredFields,
57)]
58#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
59pub struct LiveDelegation {
60    #[serde(rename = "type")]
61    pub type_: String,
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub ack_filler: Option<bool>,
64    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
65    pub rest: crate::Rest,
66}
67
68#[derive(
69    Debug,
70    Clone,
71    PartialEq,
72    serde::Serialize,
73    serde::Deserialize,
74    gproxy_protocol_macros::WireBuilder,
75    gproxy_protocol_macros::DeclaredFields,
76)]
77#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
78pub struct LiveInitialMessage {
79    #[serde(rename = "type")]
80    pub type_: String,
81    pub role: String,
82    pub content: Vec<LiveInitialText>,
83    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
84    pub rest: crate::Rest,
85}
86
87#[derive(
88    Debug,
89    Clone,
90    PartialEq,
91    serde::Serialize,
92    serde::Deserialize,
93    gproxy_protocol_macros::WireBuilder,
94    gproxy_protocol_macros::DeclaredFields,
95)]
96#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
97pub struct LiveInitialText {
98    #[serde(rename = "type")]
99    pub type_: String,
100    pub text: String,
101    #[serde(default, flatten, skip_serializing_if = "serde_json::Map::is_empty")]
102    pub rest: crate::Rest,
103}
104
105#[derive(Debug, gproxy_protocol_macros::WireBuilder, gproxy_protocol_macros::DeclaredFields)]
106#[cfg_attr(not(feature = "exhaustive"), non_exhaustive)]
107pub struct CreateLiveCallMultipartForm {
108    pub sdp: MultipartPart,
109    pub session: LiveSessionCreateRequest,
110}
111pub type CreateLiveCallRequest = WireRequest<CreateLiveCallMultipartForm>;
112pub type CreateLiveCallResponse = WireResponse<HttpBody>;
113
114/// HTTP/WS entry paths for the OpenAI realtime family. The host still owns
115/// authentication, model routing and upgrading the connection.
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
117pub enum RealtimeRoute<'a> {
118    CreateCall,
119    CreateLiveCall,
120    Connect,
121    Live { call_id: Option<&'a str> },
122}
123impl<'a> RealtimeRoute<'a> {
124    /// `/live` serves HTTP setup and a WebSocket; the method disambiguates it.
125    pub fn from_request(method: &http::Method, path: &'a str) -> Option<Self> {
126        let route = Self::from_path(path)?;
127        match (method, route) {
128            (&http::Method::POST, Self::Live { call_id: None }) => Some(Self::CreateLiveCall),
129            (&http::Method::POST, Self::CreateCall) => Some(route),
130            (&http::Method::GET, Self::Connect | Self::Live { .. }) => Some(route),
131            _ => None,
132        }
133    }
134    /// Match native paths, with or without the public `/v1` mount.
135    /// Path call IDs remain percent-encoded so adapters can preserve them.
136    pub fn from_path(path: &'a str) -> Option<Self> {
137        let path = path.strip_prefix("/v1/").unwrap_or(path);
138        let path = path.trim_start_matches('/').trim_end_matches('/');
139        match path {
140            "realtime/calls" => Some(Self::CreateCall),
141            "realtime" => Some(Self::Connect),
142            "live" => Some(Self::Live { call_id: None }),
143            _ => path
144                .strip_prefix("live/")
145                .filter(|id| !id.is_empty() && !id.contains('/') && !matches!(*id, "." | ".."))
146                .map(|id| Self::Live { call_id: Some(id) }),
147        }
148    }
149    pub fn operation(self) -> crate::OperationKey {
150        crate::OperationKey {
151            operation: match self {
152                Self::CreateCall | Self::CreateLiveCall => crate::Operation::CreateRealtimeCall,
153                _ => crate::Operation::ConnectRealtime,
154            },
155            dialect: crate::Dialect::OpenAi,
156        }
157    }
158}