1use std::fmt;
4use std::sync::{Arc, Mutex as StdMutex, Weak};
5
6use rvoip_core::adapter::{ConnectionAdapter, EndReason, OriginateRequest};
7use rvoip_core::connection::{Direction, Transport};
8use rvoip_core::error::{Result as RvoipResult, RvoipError};
9use rvoip_core::events::Event;
10use rvoip_core::ids::{BridgeId, ConnectionId};
11use rvoip_core::Orchestrator;
12use tokio::sync::{broadcast, watch};
13
14use crate::adapter::VapiAdapter;
15use crate::error::Result;
16use crate::events::VapiEvent;
17use crate::types::{VapiCallOptions, VapiPeerFailurePolicy};
18
19#[derive(Clone, Copy, Debug, Eq, PartialEq)]
20#[non_exhaustive]
21pub enum VapiAgentOutcome {
22 CallerEnded,
23 AgentEnded,
24 EventStreamClosed,
25}
26
27pub struct VapiAgentCall {
32 orchestrator: Weak<Orchestrator>,
33 adapter: Arc<VapiAdapter>,
34 caller_connection_id: ConnectionId,
35 vapi_connection_id: ConnectionId,
36 bridge_id: BridgeId,
37 events: broadcast::Sender<VapiEvent>,
38 initial_events: StdMutex<Option<broadcast::Receiver<VapiEvent>>>,
39 completion: watch::Receiver<Option<VapiAgentOutcome>>,
40}
41
42impl VapiAgentCall {
43 pub fn caller_connection_id(&self) -> &ConnectionId {
44 &self.caller_connection_id
45 }
46
47 pub fn vapi_connection_id(&self) -> &ConnectionId {
48 &self.vapi_connection_id
49 }
50
51 pub fn bridge_id(&self) -> &BridgeId {
52 &self.bridge_id
53 }
54
55 pub fn subscribe_events(&self) -> broadcast::Receiver<VapiEvent> {
56 self.initial_events
57 .lock()
58 .unwrap_or_else(|poisoned| poisoned.into_inner())
59 .take()
60 .unwrap_or_else(|| self.events.subscribe())
61 }
62
63 pub async fn say(
64 &self,
65 content: impl Into<String>,
66 end_call_after_spoken: bool,
67 interrupt_assistant: bool,
68 ) -> Result<()> {
69 self.adapter
70 .say(
71 &self.vapi_connection_id,
72 content,
73 end_call_after_spoken,
74 interrupt_assistant,
75 )
76 .await
77 }
78
79 pub async fn add_message(
80 &self,
81 role: impl Into<String>,
82 content: impl Into<String>,
83 trigger_response: bool,
84 ) -> Result<()> {
85 self.adapter
86 .add_message(&self.vapi_connection_id, role, content, trigger_response)
87 .await
88 }
89
90 pub async fn mute_assistant(&self) -> Result<()> {
91 self.adapter.mute_assistant(&self.vapi_connection_id).await
92 }
93
94 pub async fn unmute_assistant(&self) -> Result<()> {
95 self.adapter
96 .unmute_assistant(&self.vapi_connection_id)
97 .await
98 }
99
100 pub async fn end(&self) -> RvoipResult<()> {
103 if let Some(orchestrator) = self.orchestrator.upgrade() {
104 orchestrator
105 .end_connection(self.vapi_connection_id.clone(), EndReason::Normal)
106 .await
107 } else {
108 self.adapter
109 .end(self.vapi_connection_id.clone(), EndReason::Normal)
110 .await
111 }
112 }
113
114 pub async fn wait(&mut self) -> VapiAgentOutcome {
115 loop {
116 if let Some(outcome) = *self.completion.borrow() {
117 return outcome;
118 }
119 if self.completion.changed().await.is_err() {
120 return VapiAgentOutcome::EventStreamClosed;
121 }
122 }
123 }
124}
125
126impl fmt::Debug for VapiAgentCall {
127 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128 formatter
129 .debug_struct("VapiAgentCall")
130 .field("caller_connection_id", &self.caller_connection_id)
131 .field("vapi_connection_id", &self.vapi_connection_id)
132 .field("bridge_id", &self.bridge_id)
133 .finish()
134 }
135}
136
137impl VapiAdapter {
138 pub async fn attach_agent(
144 self: &Arc<Self>,
145 orchestrator: &Arc<Orchestrator>,
146 caller_connection_id: ConnectionId,
147 options: VapiCallOptions,
148 ) -> RvoipResult<VapiAgentCall> {
149 options.validate().map_err(RvoipError::from)?;
150 self.ensure_registered(orchestrator)?;
151
152 let session_id = orchestrator
153 .session_of(&caller_connection_id)
154 .ok_or_else(|| RvoipError::ConnectionNotFound(caller_connection_id.clone()))?;
155 let participant_id = {
156 let session = orchestrator
157 .session(&session_id)
158 .ok_or_else(|| RvoipError::SessionNotFound(session_id.clone()))?;
159 let session = session
160 .read()
161 .map_err(|_| RvoipError::InvalidState("session lock is poisoned"))?;
162 session
163 .connections
164 .get(&caller_connection_id)
165 .map(|connection| connection.participant_id.clone())
166 .ok_or_else(|| RvoipError::ConnectionNotFound(caller_connection_id.clone()))?
167 };
168
169 let mut core_events = orchestrator.subscribe_events();
170 let request = OriginateRequest::new(
171 session_id,
172 participant_id,
173 "vapi.websocket",
174 Direction::Outbound,
175 options.audio_format.capabilities(),
176 )
177 .with_transport(Transport::Vapi)
178 .with_context(options.clone());
179 let handle = orchestrator.originate_connection(request).await?;
180 let vapi_connection_id = handle.connection.id.clone();
181 let initial_events = self.subscribe_call_events(&vapi_connection_id)?;
182 let call_events = self.call_event_sender(&vapi_connection_id)?;
183
184 let bridge_id = match orchestrator
185 .bridge_connections(caller_connection_id.clone(), vapi_connection_id.clone())
186 .await
187 {
188 Ok(bridge_id) => bridge_id,
189 Err(error) => {
190 let _ = orchestrator
191 .end_connection(
192 vapi_connection_id,
193 EndReason::Failed {
194 detail: "Vapi attachment bridge failed".into(),
195 },
196 )
197 .await;
198 return Err(error);
199 }
200 };
201
202 let (completion_tx, completion) = watch::channel(None);
203 let supervisor_orchestrator = Arc::clone(orchestrator);
204 let supervisor_adapter = Arc::clone(self);
205 let supervisor_caller = caller_connection_id.clone();
206 let supervisor_vapi = vapi_connection_id.clone();
207 let supervisor_bridge = bridge_id.clone();
208 tokio::spawn(async move {
209 let outcome = supervise_pair(
210 &supervisor_orchestrator,
211 &supervisor_adapter,
212 &mut core_events,
213 &supervisor_caller,
214 &supervisor_vapi,
215 &supervisor_bridge,
216 options.peer_failure_policy,
217 )
218 .await;
219 let _ = completion_tx.send(Some(outcome));
220 });
221
222 Ok(VapiAgentCall {
223 orchestrator: Arc::downgrade(orchestrator),
224 adapter: Arc::clone(self),
225 caller_connection_id,
226 vapi_connection_id,
227 bridge_id,
228 events: call_events,
229 initial_events: StdMutex::new(Some(initial_events)),
230 completion,
231 })
232 }
233
234 fn ensure_registered(self: &Arc<Self>, orchestrator: &Arc<Orchestrator>) -> RvoipResult<()> {
235 match orchestrator.adapter(Transport::Vapi) {
236 Ok(registered) => {
237 let this: Arc<dyn ConnectionAdapter> =
238 Arc::clone(self) as Arc<dyn ConnectionAdapter>;
239 if Arc::ptr_eq(®istered, &this) {
240 Ok(())
241 } else {
242 Err(RvoipError::AdapterAlreadyRegistered(Transport::Vapi))
243 }
244 }
245 Err(RvoipError::NoAdapterForTransport(Transport::Vapi)) => {
246 orchestrator.register(Arc::clone(self) as Arc<dyn ConnectionAdapter>)
247 }
248 Err(error) => Err(error),
249 }
250 }
251}
252
253async fn supervise_pair(
254 orchestrator: &Arc<Orchestrator>,
255 adapter: &Arc<VapiAdapter>,
256 events: &mut broadcast::Receiver<Event>,
257 caller: &ConnectionId,
258 vapi: &ConnectionId,
259 bridge: &BridgeId,
260 peer_policy: VapiPeerFailurePolicy,
261) -> VapiAgentOutcome {
262 loop {
263 let event = match events.recv().await {
264 Ok(event) => event,
265 Err(broadcast::error::RecvError::Lagged(_)) => {
266 if orchestrator.session_of(caller).is_none() {
267 let _ = orchestrator.unbridge_connections(bridge.clone()).await;
268 let _ = adapter.end(vapi.clone(), EndReason::BridgeTorn).await;
269 return VapiAgentOutcome::CallerEnded;
270 }
271 if !adapter.is_connection_live(vapi) {
272 let _ = orchestrator.unbridge_connections(bridge.clone()).await;
273 if peer_policy == VapiPeerFailurePolicy::EndCaller {
274 let _ = orchestrator
275 .end_connection(caller.clone(), EndReason::BridgeTorn)
276 .await;
277 }
278 return VapiAgentOutcome::AgentEnded;
279 }
280 continue;
281 }
282 Err(broadcast::error::RecvError::Closed) => {
283 let _ = orchestrator.unbridge_connections(bridge.clone()).await;
284 let _ = adapter.end(vapi.clone(), EndReason::BridgeTorn).await;
285 if peer_policy == VapiPeerFailurePolicy::EndCaller {
286 let _ = orchestrator
287 .end_connection(caller.clone(), EndReason::BridgeTorn)
288 .await;
289 }
290 return VapiAgentOutcome::EventStreamClosed;
291 }
292 };
293 let terminal_connection = match event {
294 Event::ConnectionEnded { connection_id, .. }
295 | Event::ConnectionFailed { connection_id, .. } => Some(connection_id),
296 _ => None,
297 };
298 let Some(terminal_connection) = terminal_connection else {
299 continue;
300 };
301 if terminal_connection == *caller {
302 let _ = orchestrator.unbridge_connections(bridge.clone()).await;
303 let _ = orchestrator
304 .end_connection(vapi.clone(), EndReason::BridgeTorn)
305 .await;
306 return VapiAgentOutcome::CallerEnded;
307 }
308 if terminal_connection == *vapi {
309 let _ = orchestrator.unbridge_connections(bridge.clone()).await;
310 if peer_policy == VapiPeerFailurePolicy::EndCaller {
311 let _ = orchestrator
312 .end_connection(caller.clone(), EndReason::BridgeTorn)
313 .await;
314 }
315 return VapiAgentOutcome::AgentEnded;
316 }
317 }
318}