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,
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}
17
18impl<'c, C: Config> Realtime<'c, C> {
19    pub fn new(client: &'c Client<C>) -> Self {
20        Self { client }
21    }
22
23    /// Create a new Realtime API call over WebRTC and receive the SDP answer needed
24    /// to complete the peer connection.
25    ///
26    /// Returns the SDP answer in the response body and the call ID in the Location header.
27    pub async fn create_call(
28        &self,
29        request: RealtimeCallCreateRequest,
30    ) -> Result<RealtimeCallCreateResponse, OpenAIError> {
31        let (bytes, headers) = self
32            .client
33            .post_form_raw("/realtime/calls", request)
34            .await?;
35
36        // Extract Location header
37        let location = headers
38            .get("location")
39            .and_then(|v| v.to_str().ok())
40            .map(|s| s.to_string());
41
42        if location.is_none() {
43            tracing::warn!("Location header not found in Realtime call creation response");
44        }
45
46        // Use from_utf8_lossy to handle any invalid UTF-8 bytes in SDP
47        let sdp = String::from_utf8_lossy(&bytes).into_owned();
48
49        Ok(RealtimeCallCreateResponse { sdp, location })
50    }
51
52    /// Accept an incoming SIP call and configure the realtime session that will
53    /// handle the call.
54    pub async fn accept_call(
55        &self,
56        call_id: &str,
57        request: RealtimeCallAcceptRequest,
58    ) -> Result<(), OpenAIError> {
59        self.client
60            .post(&format!("/realtime/calls/{}/accept", call_id), request)
61            .await
62    }
63
64    /// End an active Realtime API call, whether it was initiated over SIP or WebRTC.
65    pub async fn hangup_call(&self, call_id: &str) -> Result<(), OpenAIError> {
66        self.client
67            .post(&format!("/realtime/calls/{}/hangup", call_id), ())
68            .await
69    }
70
71    /// Transfer a SIP call to a new destination using the Realtime API.
72    pub async fn refer_call(
73        &self,
74        call_id: &str,
75        request: RealtimeCallReferRequest,
76    ) -> Result<(), OpenAIError> {
77        self.client
78            .post(&format!("/realtime/calls/{}/refer", call_id), request)
79            .await
80    }
81
82    /// Decline an incoming SIP call handled by the Realtime API.
83    pub async fn reject_call(
84        &self,
85        call_id: &str,
86        request: Option<RealtimeCallRejectRequest>,
87    ) -> Result<(), OpenAIError> {
88        self.client
89            .post(
90                &format!("/realtime/calls/{}/reject", call_id),
91                request.unwrap_or_default(),
92            )
93            .await
94    }
95
96    /// Create a Realtime client secret with an associated session configuration.
97    pub async fn create_client_secret(
98        &self,
99        request: RealtimeCreateClientSecretRequest,
100    ) -> Result<RealtimeCreateClientSecretResponse, OpenAIError> {
101        self.client.post("/realtime/client_secrets", request).await
102    }
103}