Skip to main content

livekit_api/services/
connector.rs

1// Copyright 2025 LiveKit, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use livekit_protocol as proto;
16use std::collections::HashMap;
17
18use super::{ServiceBase, ServiceResult, LIVEKIT_PACKAGE};
19use crate::{access_token::VideoGrants, get_env_keys, services::twirp_client::TwirpClient};
20
21const SVC: &str = "Connector";
22
23/// Options for dialing a WhatsApp call
24#[derive(Default, Clone, Debug)]
25pub struct DialWhatsAppCallOptions {
26    /// Optional - An arbitrary string useful for tracking and logging purposes
27    pub biz_opaque_callback_data: Option<String>,
28    /// Optional - What LiveKit room should this participant be connected to
29    pub room_name: Option<String>,
30    /// Optional - Agents to dispatch the call to
31    pub agents: Option<Vec<proto::RoomAgentDispatch>>,
32    /// Optional - Identity of the participant in LiveKit room
33    pub participant_identity: Option<String>,
34    /// Optional - Name of the participant in LiveKit room
35    pub participant_name: Option<String>,
36    /// Optional - User-defined metadata attached to the participant in the room
37    pub participant_metadata: Option<String>,
38    /// Optional - User-defined attributes attached to the participant in the room
39    pub participant_attributes: Option<HashMap<String, String>>,
40    /// Optional - Country where the call terminates as ISO 3166-1 alpha-2
41    pub destination_country: Option<String>,
42}
43
44/// Options for accepting a WhatsApp call
45#[derive(Default, Clone, Debug)]
46pub struct AcceptWhatsAppCallOptions {
47    /// Optional - An arbitrary string useful for tracking and logging purposes
48    pub biz_opaque_callback_data: Option<String>,
49    /// Optional - What LiveKit room should this participant be connected to
50    pub room_name: Option<String>,
51    /// Optional - Agents to dispatch the call to
52    pub agents: Option<Vec<proto::RoomAgentDispatch>>,
53    /// Optional - Identity of the participant in LiveKit room
54    pub participant_identity: Option<String>,
55    /// Optional - Name of the participant in LiveKit room
56    pub participant_name: Option<String>,
57    /// Optional - User-defined metadata attached to the participant in the room
58    pub participant_metadata: Option<String>,
59    /// Optional - User-defined attributes attached to the participant in the room
60    pub participant_attributes: Option<HashMap<String, String>>,
61    /// Optional - Country where the call terminates as ISO 3166-1 alpha-2
62    pub destination_country: Option<String>,
63}
64
65/// Options for connecting a Twilio call
66#[derive(Default, Clone, Debug)]
67pub struct ConnectTwilioCallOptions {
68    /// Optional - Agents to dispatch the call to
69    pub agents: Option<Vec<proto::RoomAgentDispatch>>,
70    /// Optional - Identity of the participant in LiveKit room
71    pub participant_identity: Option<String>,
72    /// Optional - Name of the participant in LiveKit room
73    pub participant_name: Option<String>,
74    /// Optional - User-defined metadata attached to the participant in the room
75    pub participant_metadata: Option<String>,
76    /// Optional - User-defined attributes attached to the participant in the room
77    pub participant_attributes: Option<HashMap<String, String>>,
78    /// Optional - Country where the call terminates as ISO 3166-1 alpha-2
79    pub destination_country: Option<String>,
80}
81
82#[derive(Debug)]
83pub struct ConnectorClient {
84    base: ServiceBase,
85    client: TwirpClient,
86}
87
88impl ConnectorClient {
89    pub fn with_api_key(host: &str, api_key: &str, api_secret: &str) -> Self {
90        Self {
91            base: ServiceBase::with_api_key(api_key, api_secret),
92            client: TwirpClient::new(host, LIVEKIT_PACKAGE, None),
93        }
94    }
95
96    pub fn new(host: &str) -> ServiceResult<Self> {
97        let (api_key, api_secret) = get_env_keys()?;
98        Ok(Self::with_api_key(host, &api_key, &api_secret))
99    }
100
101    /// Dials a WhatsApp call
102    ///
103    /// # Arguments
104    /// * `phone_number_id` - The identifier of the number for business initiating the call
105    /// * `to_phone_number` - The number of the user that should receive the call
106    /// * `api_key` - The API key of the business initiating the call
107    /// * `cloud_api_version` - WhatsApp Cloud API version (e.g., "23.0", "24.0")
108    /// * `options` - Additional options for the call
109    ///
110    /// # Returns
111    /// Information about the dialed call including the WhatsApp call ID and room name
112    pub async fn dial_whatsapp_call(
113        &self,
114        phone_number_id: impl Into<String>,
115        to_phone_number: impl Into<String>,
116        api_key: impl Into<String>,
117        cloud_api_version: impl Into<String>,
118        options: DialWhatsAppCallOptions,
119    ) -> ServiceResult<proto::DialWhatsAppCallResponse> {
120        self.client
121            .request(
122                SVC,
123                "DialWhatsAppCall",
124                proto::DialWhatsAppCallRequest {
125                    whatsapp_phone_number_id: phone_number_id.into(),
126                    whatsapp_to_phone_number: to_phone_number.into(),
127                    whatsapp_api_key: api_key.into(),
128                    whatsapp_cloud_api_version: cloud_api_version.into(),
129                    whatsapp_biz_opaque_callback_data: options
130                        .biz_opaque_callback_data
131                        .unwrap_or_default(),
132                    room_name: options.room_name.unwrap_or_default(),
133                    agents: options.agents.unwrap_or_default(),
134                    participant_identity: options.participant_identity.unwrap_or_default(),
135                    participant_name: options.participant_name.unwrap_or_default(),
136                    participant_metadata: options.participant_metadata.unwrap_or_default(),
137                    participant_attributes: options.participant_attributes.unwrap_or_default(),
138                    destination_country: options.destination_country.unwrap_or_default(),
139                    ringing_timeout: Default::default(),
140                },
141                self.base
142                    .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
143            )
144            .await
145            .map_err(Into::into)
146    }
147
148    /// Disconnects a WhatsApp call
149    ///
150    /// # Arguments
151    /// * `call_id` - Call ID sent by Meta
152    /// * `api_key` - The API key of the business disconnecting the call
153    ///
154    /// # Returns
155    /// Empty response on success
156    pub async fn disconnect_whatsapp_call(
157        &self,
158        call_id: impl Into<String>,
159        api_key: impl Into<String>,
160    ) -> ServiceResult<proto::DisconnectWhatsAppCallResponse> {
161        self.client
162            .request(
163                SVC,
164                "DisconnectWhatsAppCall",
165                proto::DisconnectWhatsAppCallRequest {
166                    whatsapp_call_id: call_id.into(),
167                    whatsapp_api_key: api_key.into(),
168                    ..Default::default()
169                },
170                self.base
171                    .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
172            )
173            .await
174            .map_err(Into::into)
175    }
176
177    /// Connects a WhatsApp call (handles the SDP exchange)
178    ///
179    /// # Arguments
180    /// * `call_id` - Call ID sent by Meta
181    /// * `sdp` - The SDP from Meta (answer SDP for business-initiated call)
182    ///
183    /// # Returns
184    /// Empty response on success
185    pub async fn connect_whatsapp_call(
186        &self,
187        call_id: impl Into<String>,
188        sdp: proto::SessionDescription,
189    ) -> ServiceResult<proto::ConnectWhatsAppCallResponse> {
190        self.client
191            .request(
192                SVC,
193                "ConnectWhatsAppCall",
194                proto::ConnectWhatsAppCallRequest {
195                    whatsapp_call_id: call_id.into(),
196                    sdp: Some(sdp),
197                },
198                self.base
199                    .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
200            )
201            .await
202            .map_err(Into::into)
203    }
204
205    /// Accepts an incoming WhatsApp call
206    ///
207    /// # Arguments
208    /// * `phone_number_id` - The identifier of the number for business initiating the call
209    /// * `api_key` - The API key of the business connecting the call
210    /// * `cloud_api_version` - WhatsApp Cloud API version (e.g., "23.0", "24.0")
211    /// * `call_id` - Call ID sent by Meta
212    /// * `sdp` - The SDP from Meta (for user-initiated call)
213    /// * `options` - Additional options for the call
214    ///
215    /// # Returns
216    /// Information about the accepted call including the room name
217    pub async fn accept_whatsapp_call(
218        &self,
219        phone_number_id: impl Into<String>,
220        api_key: impl Into<String>,
221        cloud_api_version: impl Into<String>,
222        call_id: impl Into<String>,
223        sdp: proto::SessionDescription,
224        options: AcceptWhatsAppCallOptions,
225    ) -> ServiceResult<proto::AcceptWhatsAppCallResponse> {
226        self.client
227            .request(
228                SVC,
229                "AcceptWhatsAppCall",
230                proto::AcceptWhatsAppCallRequest {
231                    whatsapp_phone_number_id: phone_number_id.into(),
232                    whatsapp_api_key: api_key.into(),
233                    whatsapp_cloud_api_version: cloud_api_version.into(),
234                    whatsapp_call_id: call_id.into(),
235                    whatsapp_biz_opaque_callback_data: options
236                        .biz_opaque_callback_data
237                        .unwrap_or_default(),
238                    sdp: Some(sdp),
239                    room_name: options.room_name.unwrap_or_default(),
240                    agents: options.agents.unwrap_or_default(),
241                    participant_identity: options.participant_identity.unwrap_or_default(),
242                    participant_name: options.participant_name.unwrap_or_default(),
243                    participant_metadata: options.participant_metadata.unwrap_or_default(),
244                    participant_attributes: options.participant_attributes.unwrap_or_default(),
245                    destination_country: options.destination_country.unwrap_or_default(),
246                    ringing_timeout: Default::default(),
247                    wait_until_answered: Default::default(),
248                },
249                self.base
250                    .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
251            )
252            .await
253            .map_err(Into::into)
254    }
255
256    /// Connects a Twilio call
257    ///
258    /// # Arguments
259    /// * `direction` - The direction of the call (inbound or outbound)
260    /// * `room_name` - What LiveKit room should this call be connected to
261    /// * `options` - Additional options for the call
262    ///
263    /// # Returns
264    /// The WebSocket URL which Twilio media stream should connect to
265    pub async fn connect_twilio_call(
266        &self,
267        direction: proto::connect_twilio_call_request::TwilioCallDirection,
268        room_name: impl Into<String>,
269        options: ConnectTwilioCallOptions,
270    ) -> ServiceResult<proto::ConnectTwilioCallResponse> {
271        self.client
272            .request(
273                SVC,
274                "ConnectTwilioCall",
275                proto::ConnectTwilioCallRequest {
276                    twilio_call_direction: direction as i32,
277                    room_name: room_name.into(),
278                    agents: options.agents.unwrap_or_default(),
279                    participant_identity: options.participant_identity.unwrap_or_default(),
280                    participant_name: options.participant_name.unwrap_or_default(),
281                    participant_metadata: options.participant_metadata.unwrap_or_default(),
282                    participant_attributes: options.participant_attributes.unwrap_or_default(),
283                    destination_country: options.destination_country.unwrap_or_default(),
284                },
285                self.base
286                    .auth_header(VideoGrants { room_create: true, ..Default::default() }, None)?,
287            )
288            .await
289            .map_err(Into::into)
290    }
291}