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    pub async fn accept_call(
59        &self,
60        call_id: &str,
61        request: RealtimeCallAcceptRequest,
62    ) -> Result<(), OpenAIError> {
63        self.client
64            .post(
65                &format!("/realtime/calls/{}/accept", call_id),
66                request,
67                &self.request_options,
68            )
69            .await
70    }
71
72    /// End an active Realtime API call, whether it was initiated over SIP or WebRTC.
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    pub async fn refer_call(
85        &self,
86        call_id: &str,
87        request: RealtimeCallReferRequest,
88    ) -> Result<(), OpenAIError> {
89        self.client
90            .post(
91                &format!("/realtime/calls/{}/refer", call_id),
92                request,
93                &self.request_options,
94            )
95            .await
96    }
97
98    /// Decline an incoming SIP call handled by the Realtime API.
99    pub async fn reject_call(
100        &self,
101        call_id: &str,
102        request: Option<RealtimeCallRejectRequest>,
103    ) -> Result<(), OpenAIError> {
104        self.client
105            .post(
106                &format!("/realtime/calls/{}/reject", call_id),
107                request.unwrap_or_default(),
108                &self.request_options,
109            )
110            .await
111    }
112
113    /// Create a Realtime client secret with an associated session configuration.
114    pub async fn create_client_secret(
115        &self,
116        request: RealtimeCreateClientSecretRequest,
117    ) -> Result<RealtimeCreateClientSecretResponse, OpenAIError> {
118        self.client
119            .post("/realtime/client_secrets", request, &self.request_options)
120            .await
121    }
122}