Skip to main content

vv_agent/app_server/
outgoing.rs

1use std::collections::{HashMap, HashSet};
2use std::sync::atomic::{AtomicI64, Ordering};
3use std::sync::Arc;
4
5use serde::Serialize;
6use serde_json::Value;
7use tokio::sync::{mpsc, oneshot, Mutex};
8
9use crate::app_server::protocol::{
10    AppServerError, AppServerErrorCode, JsonRpcError, JsonRpcErrorBody, JsonRpcMessage,
11    JsonRpcNotification, JsonRpcResponse, RequestId, ServerNotification, ServerRequest,
12};
13use crate::app_server::transport::ConnectionId;
14
15pub type ServerRequestResult = Result<Value, JsonRpcErrorBody>;
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct OutgoingEnvelope {
19    pub connection_id: ConnectionId,
20    pub message: JsonRpcMessage,
21}
22
23#[derive(Debug, Clone, Default)]
24struct ConnectionOutgoingState {
25    ready_for_notifications: bool,
26    opt_out_notification_methods: HashSet<String>,
27}
28
29#[derive(Debug)]
30struct PendingServerRequest {
31    connection_id: ConnectionId,
32    method: String,
33    thread_id: Option<String>,
34    turn_id: Option<String>,
35    callback: oneshot::Sender<ServerRequestResult>,
36}
37
38#[derive(Clone)]
39pub struct OutgoingMessageSender {
40    tx: mpsc::Sender<OutgoingEnvelope>,
41    connections: Arc<Mutex<HashMap<ConnectionId, ConnectionOutgoingState>>>,
42    pending_server_requests: Arc<Mutex<HashMap<RequestId, PendingServerRequest>>>,
43    next_server_request_id: Arc<AtomicI64>,
44}
45
46impl OutgoingMessageSender {
47    pub fn channel(capacity: usize) -> (Self, mpsc::Receiver<OutgoingEnvelope>) {
48        let (tx, rx) = mpsc::channel(capacity);
49        (
50            Self {
51                tx,
52                connections: Arc::new(Mutex::new(HashMap::new())),
53                pending_server_requests: Arc::new(Mutex::new(HashMap::new())),
54                next_server_request_id: Arc::new(AtomicI64::new(1)),
55            },
56            rx,
57        )
58    }
59
60    pub async fn register_connection(&self, connection_id: ConnectionId) {
61        self.connections
62            .lock()
63            .await
64            .entry(connection_id)
65            .or_default();
66    }
67
68    pub async fn is_connection_registered(&self, connection_id: ConnectionId) -> bool {
69        self.connections.lock().await.contains_key(&connection_id)
70    }
71
72    pub async fn unregister_connection(&self, connection_id: ConnectionId) {
73        self.connections.lock().await.remove(&connection_id);
74        let pending = {
75            let mut requests = self.pending_server_requests.lock().await;
76            let request_ids = requests
77                .iter()
78                .filter_map(|(request_id, pending)| {
79                    (pending.connection_id == connection_id).then_some(request_id.clone())
80                })
81                .collect::<Vec<_>>();
82            request_ids
83                .into_iter()
84                .filter_map(|request_id| requests.remove(&request_id))
85                .collect::<Vec<_>>()
86        };
87        let error = JsonRpcErrorBody {
88            code: AppServerErrorCode::InternalError.code(),
89            message: "client_disconnected".to_string(),
90            data: None,
91        };
92        for pending in pending {
93            let _ = pending.callback.send(Err(error.clone()));
94        }
95    }
96
97    pub async fn configure_connection(
98        &self,
99        connection_id: ConnectionId,
100        opt_out_notification_methods: HashSet<String>,
101    ) {
102        let mut connections = self.connections.lock().await;
103        let state = connections.entry(connection_id).or_default();
104        state.opt_out_notification_methods = opt_out_notification_methods;
105    }
106
107    pub async fn mark_ready_for_notifications(&self, connection_id: ConnectionId) {
108        self.connections
109            .lock()
110            .await
111            .entry(connection_id)
112            .or_default()
113            .ready_for_notifications = true;
114    }
115
116    pub async fn send_response(
117        &self,
118        connection_id: ConnectionId,
119        id: RequestId,
120        result: Value,
121    ) -> Result<(), AppServerError> {
122        self.send_message(
123            connection_id,
124            JsonRpcMessage::Response(JsonRpcResponse { id, result }),
125        )
126        .await
127    }
128
129    pub async fn send_error(
130        &self,
131        connection_id: ConnectionId,
132        id: RequestId,
133        error: AppServerError,
134    ) -> Result<(), AppServerError> {
135        self.send_message(
136            connection_id,
137            JsonRpcMessage::Error(error.into_json_rpc_error(id)),
138        )
139        .await
140    }
141
142    pub async fn send_notification(
143        &self,
144        connection_id: ConnectionId,
145        notification: ServerNotification,
146    ) -> Result<(), AppServerError> {
147        if !self
148            .should_send_notification(connection_id, &notification)
149            .await
150        {
151            return Ok(());
152        }
153        let message = server_notification_message(notification)?;
154        self.send_message(connection_id, message).await
155    }
156
157    pub async fn broadcast_initialized_client_notification(
158        &self,
159        notification: ServerNotification,
160    ) -> Result<(), AppServerError> {
161        let connection_ids: Vec<ConnectionId> = {
162            self.connections
163                .lock()
164                .await
165                .iter()
166                .filter_map(|(connection_id, state)| {
167                    if state.ready_for_notifications
168                        && !state
169                            .opt_out_notification_methods
170                            .contains(notification_method(&notification))
171                    {
172                        Some(*connection_id)
173                    } else {
174                        None
175                    }
176                })
177                .collect()
178        };
179
180        for connection_id in connection_ids {
181            self.send_notification(connection_id, notification.clone())
182                .await?;
183        }
184        Ok(())
185    }
186
187    pub async fn send_server_request(
188        &self,
189        connection_id: ConnectionId,
190        request: ServerRequest,
191    ) -> Result<(RequestId, oneshot::Receiver<ServerRequestResult>), AppServerError> {
192        let id = RequestId::String(format!(
193            "srvreq_{}",
194            self.next_server_request_id.fetch_add(1, Ordering::Relaxed)
195        ));
196        let callback_rx = self
197            .send_server_request_with_id(connection_id, id.clone(), request)
198            .await?;
199        Ok((id, callback_rx))
200    }
201
202    pub async fn send_server_request_with_timeout(
203        &self,
204        connection_id: ConnectionId,
205        request: ServerRequest,
206        timeout: std::time::Duration,
207    ) -> Result<Value, AppServerError> {
208        let (id, callback_rx) = self.send_server_request(connection_id, request).await?;
209        match tokio::time::timeout(timeout, callback_rx).await {
210            Ok(Ok(Ok(value))) => Ok(value),
211            Ok(Ok(Err(error))) => Err(AppServerError::new(
212                AppServerErrorCode::InternalError,
213                error.message,
214            )
215            .with_data(error.data.unwrap_or(Value::Null))),
216            Ok(Err(_)) => Err(AppServerError::internal("server request callback dropped")),
217            Err(_) => {
218                self.pending_server_requests.lock().await.remove(&id);
219                Err(AppServerError::internal("server request timed out"))
220            }
221        }
222    }
223
224    pub async fn pending_server_request_count(&self) -> usize {
225        self.pending_server_requests.lock().await.len()
226    }
227
228    pub async fn send_server_request_with_id(
229        &self,
230        connection_id: ConnectionId,
231        id: RequestId,
232        request: ServerRequest,
233    ) -> Result<oneshot::Receiver<ServerRequestResult>, AppServerError> {
234        if matches!(id, RequestId::Null) {
235            return Err(AppServerError::invalid_params(
236                "Server request id cannot be null",
237            ));
238        }
239        let (callback_tx, callback_rx) = oneshot::channel();
240        let (method, params) = tagged_method_params(&request)?;
241        let (thread_id, turn_id) = server_request_scope(&request);
242        {
243            let mut requests = self.pending_server_requests.lock().await;
244            if requests.contains_key(&id) {
245                return Err(AppServerError::invalid_params(
246                    "Duplicate server request id",
247                ));
248            }
249            requests.insert(
250                id.clone(),
251                PendingServerRequest {
252                    connection_id,
253                    method: method.clone(),
254                    thread_id,
255                    turn_id,
256                    callback: callback_tx,
257                },
258            );
259        }
260        if let Err(error) = self
261            .send_message(
262                connection_id,
263                JsonRpcMessage::Request(crate::app_server::protocol::JsonRpcRequest {
264                    id: id.clone(),
265                    method,
266                    params,
267                }),
268            )
269            .await
270        {
271            self.pending_server_requests.lock().await.remove(&id);
272            return Err(error);
273        }
274        Ok(callback_rx)
275    }
276
277    pub async fn send_server_request_with_id_and_timeout(
278        &self,
279        connection_id: ConnectionId,
280        id: RequestId,
281        request: ServerRequest,
282        timeout: std::time::Duration,
283    ) -> Result<Value, AppServerError> {
284        let callback_rx = self
285            .send_server_request_with_id(connection_id, id.clone(), request)
286            .await?;
287        match tokio::time::timeout(timeout, callback_rx).await {
288            Ok(Ok(Ok(value))) => Ok(value),
289            Ok(Ok(Err(error))) => Err(AppServerError::new(
290                AppServerErrorCode::InternalError,
291                error.message,
292            )
293            .with_data(error.data.unwrap_or(Value::Null))),
294            Ok(Err(_)) => Err(AppServerError::internal("server request callback dropped")),
295            Err(_) => {
296                self.pending_server_requests.lock().await.remove(&id);
297                Err(AppServerError::internal("server request timed out"))
298            }
299        }
300    }
301
302    pub async fn send_json_rpc_request(
303        &self,
304        connection_id: ConnectionId,
305        id: RequestId,
306        method: String,
307        params: Option<Value>,
308    ) -> Result<(), AppServerError> {
309        self.send_message(
310            connection_id,
311            JsonRpcMessage::Request(crate::app_server::protocol::JsonRpcRequest {
312                id,
313                method,
314                params,
315            }),
316        )
317        .await
318    }
319
320    pub async fn resolve_server_response(
321        &self,
322        connection_id: ConnectionId,
323        response: JsonRpcResponse,
324    ) -> bool {
325        self.resolve_server_response_bound(connection_id, None, None, None, response)
326            .await
327    }
328
329    pub async fn resolve_server_response_bound(
330        &self,
331        connection_id: ConnectionId,
332        method: Option<&str>,
333        thread_id: Option<&str>,
334        turn_id: Option<&str>,
335        response: JsonRpcResponse,
336    ) -> bool {
337        let pending = {
338            let mut requests = self.pending_server_requests.lock().await;
339            let matches = requests.get(&response.id).is_some_and(|pending| {
340                pending.connection_id == connection_id
341                    && method.is_none_or(|method| pending.method == method)
342                    && thread_id
343                        .is_none_or(|thread_id| pending.thread_id.as_deref() == Some(thread_id))
344                    && turn_id.is_none_or(|turn_id| pending.turn_id.as_deref() == Some(turn_id))
345            });
346            matches.then(|| requests.remove(&response.id)).flatten()
347        };
348        pending.is_some_and(|pending| pending.callback.send(Ok(response.result)).is_ok())
349    }
350
351    pub async fn resolve_server_error(
352        &self,
353        connection_id: ConnectionId,
354        error: JsonRpcError,
355    ) -> bool {
356        let pending = {
357            let mut requests = self.pending_server_requests.lock().await;
358            let matches = requests
359                .get(&error.id)
360                .is_some_and(|pending| pending.connection_id == connection_id);
361            matches.then(|| requests.remove(&error.id)).flatten()
362        };
363        pending.is_some_and(|pending| pending.callback.send(Err(error.error)).is_ok())
364    }
365
366    async fn send_message(
367        &self,
368        connection_id: ConnectionId,
369        message: JsonRpcMessage,
370    ) -> Result<(), AppServerError> {
371        if !self.is_connection_registered(connection_id).await {
372            return Err(AppServerError::internal("client_disconnected"));
373        }
374        self.tx
375            .send(OutgoingEnvelope {
376                connection_id,
377                message,
378            })
379            .await
380            .map_err(|_| AppServerError::internal("outgoing channel closed"))
381    }
382
383    async fn should_send_notification(
384        &self,
385        connection_id: ConnectionId,
386        notification: &ServerNotification,
387    ) -> bool {
388        self.connections
389            .lock()
390            .await
391            .get(&connection_id)
392            .is_some_and(|state| {
393                state.ready_for_notifications
394                    && !state
395                        .opt_out_notification_methods
396                        .contains(notification_method(notification))
397            })
398    }
399}
400
401fn server_request_scope(request: &ServerRequest) -> (Option<String>, Option<String>) {
402    match request {
403        ServerRequest::ApprovalRequest(params) => {
404            (Some(params.thread_id.clone()), Some(params.turn_id.clone()))
405        }
406    }
407}
408
409fn server_notification_message(
410    notification: ServerNotification,
411) -> Result<JsonRpcMessage, AppServerError> {
412    let (method, params) = tagged_method_params(&notification)?;
413    Ok(JsonRpcMessage::Notification(JsonRpcNotification {
414        method,
415        params,
416    }))
417}
418
419fn tagged_method_params<T: Serialize>(
420    value: &T,
421) -> Result<(String, Option<Value>), AppServerError> {
422    let mut value =
423        serde_json::to_value(value).map_err(|error| AppServerError::internal(error.to_string()))?;
424    let object = value
425        .as_object_mut()
426        .ok_or_else(|| AppServerError::internal("tagged protocol value is not an object"))?;
427    let method = object
428        .remove("method")
429        .and_then(|method| method.as_str().map(str::to_string))
430        .ok_or_else(|| AppServerError::internal("tagged protocol value is missing method"))?;
431    let params = object.remove("params");
432    Ok((method, params))
433}
434
435fn notification_method(notification: &ServerNotification) -> &'static str {
436    match notification {
437        ServerNotification::ThreadStarted(_) => "thread/started",
438        ServerNotification::ThreadArchived(_) => "thread/archived",
439        ServerNotification::ThreadClosed(_) => "thread/closed",
440        ServerNotification::ThreadStatusChanged(_) => "thread/status/changed",
441        ServerNotification::TurnStarted(_) => "turn/started",
442        ServerNotification::TurnCompleted(_) => "turn/completed",
443        ServerNotification::ItemStarted(_) => "item/started",
444        ServerNotification::AgentMessageDelta(_) => "item/agentMessage/delta",
445        ServerNotification::ToolCallDelta(_) => "item/toolCall/delta",
446        ServerNotification::ItemCompleted(_) => "item/completed",
447        ServerNotification::ApprovalRequested(_) => "approval/requested",
448        ServerNotification::ApprovalResolved(_) => "approval/resolved",
449        ServerNotification::ErrorWarning(_) => "error/warning",
450    }
451}