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+
15+ use livekit_protocol as proto;
16+ use std:: collections:: HashMap ;
17+
18+ use super :: { ServiceBase , ServiceResult , LIVEKIT_PACKAGE } ;
19+ use crate :: { access_token:: VideoGrants , get_env_keys, services:: twirp_client:: TwirpClient } ;
20+
21+ const SVC : & str = "Connector" ;
22+
23+ /// Options for dialing a WhatsApp call
24+ #[ derive( Default , Clone , Debug ) ]
25+ pub 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 ) ]
46+ pub 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 ) ]
67+ pub 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 ) ]
83+ pub struct ConnectorClient {
84+ base : ServiceBase ,
85+ client : TwirpClient ,
86+ }
87+
88+ impl 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 number of the 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. biz_opaque_callback_data . unwrap_or_default ( ) ,
130+ room_name : options. room_name . unwrap_or_default ( ) ,
131+ agents : options. agents . unwrap_or_default ( ) ,
132+ participant_identity : options. participant_identity . unwrap_or_default ( ) ,
133+ participant_name : options. participant_name . unwrap_or_default ( ) ,
134+ participant_metadata : options. participant_metadata . unwrap_or_default ( ) ,
135+ participant_attributes : options. participant_attributes . unwrap_or_default ( ) ,
136+ destination_country : options. destination_country . unwrap_or_default ( ) ,
137+ } ,
138+ self . base
139+ . auth_header ( VideoGrants { room_create : true , ..Default :: default ( ) } , None ) ?,
140+ )
141+ . await
142+ . map_err ( Into :: into)
143+ }
144+
145+ /// Disconnects a WhatsApp call
146+ ///
147+ /// # Arguments
148+ /// * `call_id` - Call ID sent by Meta
149+ /// * `api_key` - The API key of the business disconnecting the call
150+ ///
151+ /// # Returns
152+ /// Empty response on success
153+ pub async fn disconnect_whatsapp_call (
154+ & self ,
155+ call_id : impl Into < String > ,
156+ api_key : impl Into < String > ,
157+ ) -> ServiceResult < proto:: DisconnectWhatsAppCallResponse > {
158+ self . client
159+ . request (
160+ SVC ,
161+ "DisconnectWhatsAppCall" ,
162+ proto:: DisconnectWhatsAppCallRequest {
163+ whatsapp_call_id : call_id. into ( ) ,
164+ whatsapp_api_key : api_key. into ( ) ,
165+ } ,
166+ self . base
167+ . auth_header ( VideoGrants { room_create : true , ..Default :: default ( ) } , None ) ?,
168+ )
169+ . await
170+ . map_err ( Into :: into)
171+ }
172+
173+ /// Connects a WhatsApp call (handles the SDP exchange)
174+ ///
175+ /// # Arguments
176+ /// * `call_id` - Call ID sent by Meta
177+ /// * `sdp` - The SDP from Meta (answer SDP for business-initiated call)
178+ ///
179+ /// # Returns
180+ /// Empty response on success
181+ pub async fn connect_whatsapp_call (
182+ & self ,
183+ call_id : impl Into < String > ,
184+ sdp : proto:: SessionDescription ,
185+ ) -> ServiceResult < proto:: ConnectWhatsAppCallResponse > {
186+ self . client
187+ . request (
188+ SVC ,
189+ "ConnectWhatsAppCall" ,
190+ proto:: ConnectWhatsAppCallRequest {
191+ whatsapp_call_id : call_id. into ( ) ,
192+ sdp : Some ( sdp) ,
193+ } ,
194+ self . base
195+ . auth_header ( VideoGrants { room_create : true , ..Default :: default ( ) } , None ) ?,
196+ )
197+ . await
198+ . map_err ( Into :: into)
199+ }
200+
201+ /// Accepts an incoming WhatsApp call
202+ ///
203+ /// # Arguments
204+ /// * `phone_number_id` - The number of the business connecting the call
205+ /// * `api_key` - The API key of the business connecting the call
206+ /// * `cloud_api_version` - WhatsApp Cloud API version (e.g., "23.0", "24.0")
207+ /// * `call_id` - Call ID sent by Meta
208+ /// * `sdp` - The SDP from Meta (for user-initiated call)
209+ /// * `options` - Additional options for the call
210+ ///
211+ /// # Returns
212+ /// Information about the accepted call including the room name
213+ pub async fn accept_whatsapp_call (
214+ & self ,
215+ phone_number_id : impl Into < String > ,
216+ api_key : impl Into < String > ,
217+ cloud_api_version : impl Into < String > ,
218+ call_id : impl Into < String > ,
219+ sdp : proto:: SessionDescription ,
220+ options : AcceptWhatsAppCallOptions ,
221+ ) -> ServiceResult < proto:: AcceptWhatsAppCallResponse > {
222+ self . client
223+ . request (
224+ SVC ,
225+ "AcceptWhatsAppCall" ,
226+ proto:: AcceptWhatsAppCallRequest {
227+ whatsapp_phone_number_id : phone_number_id. into ( ) ,
228+ whatsapp_api_key : api_key. into ( ) ,
229+ whatsapp_cloud_api_version : cloud_api_version. into ( ) ,
230+ whatsapp_call_id : call_id. into ( ) ,
231+ whatsapp_biz_opaque_callback_data : options. biz_opaque_callback_data . unwrap_or_default ( ) ,
232+ sdp : Some ( sdp) ,
233+ room_name : options. room_name . unwrap_or_default ( ) ,
234+ agents : options. agents . unwrap_or_default ( ) ,
235+ participant_identity : options. participant_identity . unwrap_or_default ( ) ,
236+ participant_name : options. participant_name . unwrap_or_default ( ) ,
237+ participant_metadata : options. participant_metadata . unwrap_or_default ( ) ,
238+ participant_attributes : options. participant_attributes . unwrap_or_default ( ) ,
239+ destination_country : options. destination_country . unwrap_or_default ( ) ,
240+ } ,
241+ self . base
242+ . auth_header ( VideoGrants { room_create : true , ..Default :: default ( ) } , None ) ?,
243+ )
244+ . await
245+ . map_err ( Into :: into)
246+ }
247+
248+ /// Connects a Twilio call
249+ ///
250+ /// # Arguments
251+ /// * `direction` - The direction of the call (inbound or outbound)
252+ /// * `room_name` - What LiveKit room should this call be connected to
253+ /// * `options` - Additional options for the call
254+ ///
255+ /// # Returns
256+ /// The WebSocket URL which Twilio media stream should connect to
257+ pub async fn connect_twilio_call (
258+ & self ,
259+ direction : proto:: connect_twilio_call_request:: TwilioCallDirection ,
260+ room_name : impl Into < String > ,
261+ options : ConnectTwilioCallOptions ,
262+ ) -> ServiceResult < proto:: ConnectTwilioCallResponse > {
263+ self . client
264+ . request (
265+ SVC ,
266+ "ConnectTwilioCall" ,
267+ proto:: ConnectTwilioCallRequest {
268+ twilio_call_direction : direction as i32 ,
269+ room_name : room_name. into ( ) ,
270+ agents : options. agents . unwrap_or_default ( ) ,
271+ participant_identity : options. participant_identity . unwrap_or_default ( ) ,
272+ participant_name : options. participant_name . unwrap_or_default ( ) ,
273+ participant_metadata : options. participant_metadata . unwrap_or_default ( ) ,
274+ participant_attributes : options. participant_attributes . unwrap_or_default ( ) ,
275+ destination_country : options. destination_country . unwrap_or_default ( ) ,
276+ } ,
277+ self . base
278+ . auth_header ( VideoGrants { room_create : true , ..Default :: default ( ) } , None ) ?,
279+ )
280+ . await
281+ . map_err ( Into :: into)
282+ }
283+ }
0 commit comments