use std::fmt; use tokio::sync::{mpsc, oneshot}; use crate::{ common::{AcpChannelFailure, AcpResult, acp_channel_failure_error}, message::{AcpAgentMessage, AcpArgs, AcpClientMessage, AcpMethod, AcpRequest}, }; /// Receiver/sender pair, either for client/agent or agent/client message types. pub struct AcpChannel { pub rx: mpsc::UnboundedReceiver, pub tx: mpsc::UnboundedSender, } impl AcpChannel { pub fn new(rx: mpsc::UnboundedReceiver, tx: mpsc::UnboundedSender) -> Self { Self { rx, tx } } } /// Client channel: receive client messages from agent, send agent messages to agent. pub type AcpClientChannel = AcpChannel; /// Agent channel: receive agent messages from client, send client messages to client. pub type AcpAgentChannel = AcpChannel; /// Create a linked pair of client/agent channels. pub fn acp_channels() -> (AcpClientChannel, AcpAgentChannel) { let (tx1, rx1) = mpsc::unbounded_channel(); let (tx2, rx2) = mpsc::unbounded_channel(); (AcpChannel::new(rx1, tx2), AcpChannel::new(rx2, tx1)) } pub async fn acp_send(request: T, tx: &mpsc::UnboundedSender) -> AcpResult where T: AcpRequest, R: From> + fmt::Debug, { let (response_tx, response_rx) = oneshot::channel(); let method = request.method_name(); let args = AcpArgs { request, response_tx, }; tx.send(args.into()).map_err(|_| { acp_channel_failure_error( format!("unable to send '{method}' request, channel closed"), AcpChannelFailure::SendFailed, ) })?; response_rx.await.map_err(|_| { acp_channel_failure_error( format!("unable to receive '{method}' response, channel closed"), AcpChannelFailure::RecvFailed, ) })? } #[cfg(test)] mod acp_send_failure_tests { use super::acp_send; use crate::common::{AcpChannelFailure, acp_channel_failure}; use crate::message::AcpAgentMessage; use agent_client_protocol as acp; use tokio::sync::mpsc; fn ext_request() -> acp::ExtRequest { acp::ExtRequest::new( "x.ai/test", serde_json::value::to_raw_value(&serde_json::json!({})) .unwrap() .into(), ) } #[tokio::test] async fn send_failed_when_receiver_dropped_before_send() { let (tx, rx) = mpsc::unbounded_channel::(); drop(rx); // no peer listening -> enqueue fails let err = acp_send(ext_request(), &tx).await.unwrap_err(); assert_eq!( acp_channel_failure(&err), Some(AcpChannelFailure::SendFailed) ); } #[tokio::test] async fn recv_failed_when_response_channel_dropped_after_send() { let (tx, mut rx) = mpsc::unbounded_channel::(); let mut send_fut = Box::pin(acp_send(ext_request(), &tx)); // First poll enqueues the request, then parks on the response channel. assert!(futures::poll!(send_fut.as_mut()).is_pending()); // The peer "receives" the request then drops it (dropping response_tx). drop(rx.try_recv().expect("request should be enqueued")); let err = send_fut.await.unwrap_err(); assert_eq!( acp_channel_failure(&err), Some(AcpChannelFailure::RecvFailed) ); } }