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(Clone)]
30pub struct OutgoingMessageSender {
31 tx: mpsc::Sender<OutgoingEnvelope>,
32 connections: Arc<Mutex<HashMap<ConnectionId, ConnectionOutgoingState>>>,
33 pending_server_requests: Arc<Mutex<HashMap<RequestId, oneshot::Sender<ServerRequestResult>>>>,
34 next_server_request_id: Arc<AtomicI64>,
35}
36
37impl OutgoingMessageSender {
38 pub fn channel(capacity: usize) -> (Self, mpsc::Receiver<OutgoingEnvelope>) {
39 let (tx, rx) = mpsc::channel(capacity);
40 (
41 Self {
42 tx,
43 connections: Arc::new(Mutex::new(HashMap::new())),
44 pending_server_requests: Arc::new(Mutex::new(HashMap::new())),
45 next_server_request_id: Arc::new(AtomicI64::new(1)),
46 },
47 rx,
48 )
49 }
50
51 pub async fn register_connection(&self, connection_id: ConnectionId) {
52 self.connections
53 .lock()
54 .await
55 .entry(connection_id)
56 .or_default();
57 }
58
59 pub async fn configure_connection(
60 &self,
61 connection_id: ConnectionId,
62 opt_out_notification_methods: HashSet<String>,
63 ) {
64 let mut connections = self.connections.lock().await;
65 let state = connections.entry(connection_id).or_default();
66 state.opt_out_notification_methods = opt_out_notification_methods;
67 }
68
69 pub async fn mark_ready_for_notifications(&self, connection_id: ConnectionId) {
70 self.connections
71 .lock()
72 .await
73 .entry(connection_id)
74 .or_default()
75 .ready_for_notifications = true;
76 }
77
78 pub async fn send_response(
79 &self,
80 connection_id: ConnectionId,
81 id: RequestId,
82 result: Value,
83 ) -> Result<(), AppServerError> {
84 self.send_message(
85 connection_id,
86 JsonRpcMessage::Response(JsonRpcResponse { id, result }),
87 )
88 .await
89 }
90
91 pub async fn send_error(
92 &self,
93 connection_id: ConnectionId,
94 id: RequestId,
95 error: AppServerError,
96 ) -> Result<(), AppServerError> {
97 self.send_message(
98 connection_id,
99 JsonRpcMessage::Error(error.into_json_rpc_error(id)),
100 )
101 .await
102 }
103
104 pub async fn send_notification(
105 &self,
106 connection_id: ConnectionId,
107 notification: ServerNotification,
108 ) -> Result<(), AppServerError> {
109 if !self
110 .should_send_notification(connection_id, ¬ification)
111 .await
112 {
113 return Ok(());
114 }
115 let message = server_notification_message(notification)?;
116 self.send_message(connection_id, message).await
117 }
118
119 pub async fn broadcast_initialized_client_notification(
120 &self,
121 notification: ServerNotification,
122 ) -> Result<(), AppServerError> {
123 let connection_ids: Vec<ConnectionId> = {
124 self.connections
125 .lock()
126 .await
127 .iter()
128 .filter_map(|(connection_id, state)| {
129 if state.ready_for_notifications
130 && !state
131 .opt_out_notification_methods
132 .contains(notification_method(¬ification))
133 {
134 Some(*connection_id)
135 } else {
136 None
137 }
138 })
139 .collect()
140 };
141
142 for connection_id in connection_ids {
143 self.send_notification(connection_id, notification.clone())
144 .await?;
145 }
146 Ok(())
147 }
148
149 pub async fn send_server_request(
150 &self,
151 connection_id: ConnectionId,
152 request: ServerRequest,
153 ) -> Result<(RequestId, oneshot::Receiver<ServerRequestResult>), AppServerError> {
154 let id = RequestId::String(format!(
155 "srvreq_{}",
156 self.next_server_request_id.fetch_add(1, Ordering::Relaxed)
157 ));
158 let (callback_tx, callback_rx) = oneshot::channel();
159 self.pending_server_requests
160 .lock()
161 .await
162 .insert(id.clone(), callback_tx);
163 let (method, params) = tagged_method_params(&request)?;
164 if let Err(error) = self
165 .send_message(
166 connection_id,
167 JsonRpcMessage::Request(crate::app_server::protocol::JsonRpcRequest {
168 id: id.clone(),
169 method,
170 params,
171 }),
172 )
173 .await
174 {
175 self.pending_server_requests.lock().await.remove(&id);
176 return Err(error);
177 }
178 Ok((id, callback_rx))
179 }
180
181 pub async fn send_server_request_with_timeout(
182 &self,
183 connection_id: ConnectionId,
184 request: ServerRequest,
185 timeout: std::time::Duration,
186 ) -> Result<Value, AppServerError> {
187 let (id, callback_rx) = self.send_server_request(connection_id, request).await?;
188 match tokio::time::timeout(timeout, callback_rx).await {
189 Ok(Ok(Ok(value))) => Ok(value),
190 Ok(Ok(Err(error))) => Err(AppServerError::new(
191 AppServerErrorCode::InternalError,
192 error.message,
193 )
194 .with_data(error.data.unwrap_or(Value::Null))),
195 Ok(Err(_)) => Err(AppServerError::internal("server request callback dropped")),
196 Err(_) => {
197 self.pending_server_requests.lock().await.remove(&id);
198 Err(AppServerError::internal("server request timed out"))
199 }
200 }
201 }
202
203 pub async fn pending_server_request_count(&self) -> usize {
204 self.pending_server_requests.lock().await.len()
205 }
206
207 pub async fn send_server_request_with_id(
208 &self,
209 connection_id: ConnectionId,
210 id: RequestId,
211 request: ServerRequest,
212 ) -> Result<oneshot::Receiver<ServerRequestResult>, AppServerError> {
213 let (callback_tx, callback_rx) = oneshot::channel();
214 self.pending_server_requests
215 .lock()
216 .await
217 .insert(id.clone(), callback_tx);
218 let (method, params) = tagged_method_params(&request)?;
219 if let Err(error) = self
220 .send_message(
221 connection_id,
222 JsonRpcMessage::Request(crate::app_server::protocol::JsonRpcRequest {
223 id: id.clone(),
224 method,
225 params,
226 }),
227 )
228 .await
229 {
230 self.pending_server_requests.lock().await.remove(&id);
231 return Err(error);
232 }
233 Ok(callback_rx)
234 }
235
236 pub async fn send_server_request_with_id_and_timeout(
237 &self,
238 connection_id: ConnectionId,
239 id: RequestId,
240 request: ServerRequest,
241 timeout: std::time::Duration,
242 ) -> Result<Value, AppServerError> {
243 let callback_rx = self
244 .send_server_request_with_id(connection_id, id.clone(), request)
245 .await?;
246 match tokio::time::timeout(timeout, callback_rx).await {
247 Ok(Ok(Ok(value))) => Ok(value),
248 Ok(Ok(Err(error))) => Err(AppServerError::new(
249 AppServerErrorCode::InternalError,
250 error.message,
251 )
252 .with_data(error.data.unwrap_or(Value::Null))),
253 Ok(Err(_)) => Err(AppServerError::internal("server request callback dropped")),
254 Err(_) => {
255 self.pending_server_requests.lock().await.remove(&id);
256 Err(AppServerError::internal("server request timed out"))
257 }
258 }
259 }
260
261 pub async fn send_json_rpc_request(
262 &self,
263 connection_id: ConnectionId,
264 id: RequestId,
265 method: String,
266 params: Option<Value>,
267 ) -> Result<(), AppServerError> {
268 self.send_message(
269 connection_id,
270 JsonRpcMessage::Request(crate::app_server::protocol::JsonRpcRequest {
271 id,
272 method,
273 params,
274 }),
275 )
276 .await
277 }
278
279 pub async fn resolve_server_response(&self, response: JsonRpcResponse) -> bool {
280 self.pending_server_requests
281 .lock()
282 .await
283 .remove(&response.id)
284 .is_some_and(|callback| callback.send(Ok(response.result)).is_ok())
285 }
286
287 pub async fn resolve_server_error(&self, error: JsonRpcError) -> bool {
288 self.pending_server_requests
289 .lock()
290 .await
291 .remove(&error.id)
292 .is_some_and(|callback| callback.send(Err(error.error)).is_ok())
293 }
294
295 async fn send_message(
296 &self,
297 connection_id: ConnectionId,
298 message: JsonRpcMessage,
299 ) -> Result<(), AppServerError> {
300 self.tx
301 .send(OutgoingEnvelope {
302 connection_id,
303 message,
304 })
305 .await
306 .map_err(|_| AppServerError::internal("outgoing channel closed"))
307 }
308
309 async fn should_send_notification(
310 &self,
311 connection_id: ConnectionId,
312 notification: &ServerNotification,
313 ) -> bool {
314 self.connections
315 .lock()
316 .await
317 .get(&connection_id)
318 .is_some_and(|state| {
319 state.ready_for_notifications
320 && !state
321 .opt_out_notification_methods
322 .contains(notification_method(notification))
323 })
324 }
325}
326
327fn server_notification_message(
328 notification: ServerNotification,
329) -> Result<JsonRpcMessage, AppServerError> {
330 let (method, params) = tagged_method_params(¬ification)?;
331 Ok(JsonRpcMessage::Notification(JsonRpcNotification {
332 method,
333 params,
334 }))
335}
336
337fn tagged_method_params<T: Serialize>(
338 value: &T,
339) -> Result<(String, Option<Value>), AppServerError> {
340 let mut value =
341 serde_json::to_value(value).map_err(|error| AppServerError::internal(error.to_string()))?;
342 let object = value
343 .as_object_mut()
344 .ok_or_else(|| AppServerError::internal("tagged protocol value is not an object"))?;
345 let method = object
346 .remove("method")
347 .and_then(|method| method.as_str().map(str::to_string))
348 .ok_or_else(|| AppServerError::internal("tagged protocol value is missing method"))?;
349 let params = object.remove("params");
350 Ok((method, params))
351}
352
353fn notification_method(notification: &ServerNotification) -> &'static str {
354 match notification {
355 ServerNotification::ThreadStarted(_) => "thread/started",
356 ServerNotification::ThreadArchived(_) => "thread/archived",
357 ServerNotification::TurnStarted(_) => "turn/started",
358 ServerNotification::TurnCompleted(_) => "turn/completed",
359 ServerNotification::ItemStarted(_) => "item/started",
360 ServerNotification::AgentMessageDelta(_) => "item/agentMessage/delta",
361 ServerNotification::ToolCallDelta(_) => "item/toolCall/delta",
362 ServerNotification::ItemCompleted(_) => "item/completed",
363 ServerNotification::ApprovalRequested(_) => "approval/requested",
364 ServerNotification::ApprovalResolved(_) => "approval/resolved",
365 ServerNotification::ErrorWarning(_) => "error/warning",
366 }
367}