Skip to main content

outfox_openai/
realtime.rs

1use crate::config::Config;
2use crate::error::OpenAIError;
3use crate::spec::realtime::{
4    RealtimeCallAcceptRequest, RealtimeCallCreateRequest, RealtimeCallCreateResponse,
5    RealtimeCallReferRequest, RealtimeCallRejectRequest, RealtimeCreateClientSecretRequest,
6    RealtimeCreateClientSecretResponse,
7};
8use crate::{Client, RequestOptions};
9
10/// Realtime API for creating sessions, managing calls, and handling WebRTC connections.
11/// Related guide: [Realtime API](https://platform.openai.com/docs/guides/realtime)
12pub struct Realtime<'c, C: Config> {
13    client: &'c Client<C>,
14    pub(crate) request_options: RequestOptions,
15}
16
17impl<'c, C: Config> Realtime<'c, C> {
18    pub fn new(client: &'c Client<C>) -> Self {
19        Self {
20            client,
21            request_options: RequestOptions::new(),
22        }
23    }
24
25    /// Create a new Realtime API call over WebRTC and receive the SDP answer needed
26    /// to complete the peer connection.
27    ///
28    /// Returns the SDP answer in the response body and the call ID in the Location header.
29    pub async fn create_call(
30        &self,
31        request: RealtimeCallCreateRequest,
32    ) -> Result<RealtimeCallCreateResponse, OpenAIError> {
33        let (bytes, headers) = self
34            .client
35            .post_form_raw("/realtime/calls", request, &self.request_options)
36            .await?;
37
38        // Extract Location header
39        let location = headers
40            .get("location")
41            .and_then(|v| v.to_str().ok())
42            .map(|s| s.to_string());
43
44        if location.is_none() {
45            tracing::warn!("Location header not found in Realtime call creation response");
46        }
47
48        // Use from_utf8_lossy to handle any invalid UTF-8 bytes in SDP
49        let sdp = String::from_utf8_lossy(&bytes).into_owned();
50
51        Ok(RealtimeCallCreateResponse { sdp, location })
52    }
53
54    /// Accept an incoming SIP call and configure the realtime session that will
55    /// handle the call.
56    #[crate::byot(T0 = std::fmt::Display, T1 = serde::Serialize, R = serde::de::DeserializeOwned)]
57    pub async fn accept_call(
58        &self,
59        call_id: &str,
60        request: RealtimeCallAcceptRequest,
61    ) -> Result<(), OpenAIError> {
62        self.client
63            .post(
64                &format!("/realtime/calls/{}/accept", call_id),
65                request,
66                &self.request_options,
67            )
68            .await
69    }
70
71    /// End an active Realtime API call, whether it was initiated over SIP or WebRTC.
72    #[crate::byot(T0 = std::fmt::Display, R = serde::de::DeserializeOwned)]
73    pub async fn hangup_call(&self, call_id: &str) -> Result<(), OpenAIError> {
74        self.client
75            .post(
76                &format!("/realtime/calls/{}/hangup", call_id),
77                (),
78                &self.request_options,
79            )
80            .await
81    }
82
83    /// Transfer a SIP call to a new destination using the Realtime API.
84    #[crate::byot(T0 = std::fmt::Display, T1 = serde::Serialize, R = serde::de::DeserializeOwned)]
85    pub async fn refer_call(
86        &self,
87        call_id: &str,
88        request: RealtimeCallReferRequest,
89    ) -> Result<(), OpenAIError> {
90        self.client
91            .post(
92                &format!("/realtime/calls/{}/refer", call_id),
93                request,
94                &self.request_options,
95            )
96            .await
97    }
98
99    /// Decline an incoming SIP call handled by the Realtime API.
100    #[crate::byot(T0 = std::fmt::Display, T1 = serde::Serialize, R = serde::de::DeserializeOwned)]
101    pub async fn reject_call(
102        &self,
103        call_id: &str,
104        request: RealtimeCallRejectRequest,
105    ) -> Result<(), OpenAIError> {
106        self.client
107            .post(
108                &format!("/realtime/calls/{}/reject", call_id),
109                request,
110                &self.request_options,
111            )
112            .await
113    }
114
115    /// Create a Realtime client secret with an associated session configuration.
116    #[crate::byot(T0 = serde::Serialize, R = serde::de::DeserializeOwned)]
117    pub async fn create_client_secret(
118        &self,
119        request: RealtimeCreateClientSecretRequest,
120    ) -> Result<RealtimeCreateClientSecretResponse, OpenAIError> {
121        self.client
122            .post("/realtime/client_secrets", request, &self.request_options)
123            .await
124    }
125}