async_openai/
realtime.rs

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