agent_client_protocol/jsonrpc.rs
1//! Core JSON-RPC server support.
2
3use agent_client_protocol_schema::v1::{
4 JsonRpcMessage as VersionedJsonRpcMessage, Notification as RpcNotification,
5 Request as RpcRequest, RequestId, Response as RpcResponse, SessionId,
6};
7
8// Types re-exported from crate root
9use serde::{Deserialize, Serialize};
10use std::any::TypeId;
11use std::collections::HashMap;
12use std::fmt::Debug;
13use std::panic::Location;
14use std::pin::pin;
15use std::sync::{
16 Arc, Mutex, Weak,
17 atomic::{AtomicBool, Ordering},
18};
19use uuid::Uuid;
20
21use futures::FutureExt;
22use futures::channel::{mpsc, oneshot};
23use futures::future::{self, BoxFuture, Either};
24use futures::{AsyncRead, AsyncWrite, StreamExt};
25
26pub(crate) mod close;
27mod dynamic_handler;
28pub(crate) mod handlers;
29mod incoming_actor;
30mod outgoing_actor;
31mod protocol_compat;
32pub(crate) mod run;
33mod task_actor;
34mod transport_actor;
35
36use crate::jsonrpc::close::{ChainedClose, CloseCallback};
37pub use crate::jsonrpc::close::{HandleConnectionClose, NullClose};
38use crate::jsonrpc::dynamic_handler::DynamicHandlerMessage;
39pub use crate::jsonrpc::handlers::NullHandler;
40use crate::jsonrpc::handlers::{ChainedHandler, NamedHandler};
41use crate::jsonrpc::handlers::{MessageHandler, NotificationHandler, RequestHandler};
42use crate::jsonrpc::outgoing_actor::{OutgoingMessageTx, send_raw_message};
43use crate::jsonrpc::protocol_compat::{ProtocolCompat, ProtocolMode};
44use crate::jsonrpc::run::SpawnedRun;
45use crate::jsonrpc::run::{ChainRun, NullRun, RunWithConnectionTo};
46use crate::jsonrpc::task_actor::{Task, TaskTx};
47use crate::mcp_server::McpServer;
48use crate::role::HasPeer;
49use crate::role::Role;
50use crate::{Agent, Client, ConnectTo, RoleId};
51
52/// Raw JSON-RPC message transported by [`Channel`].
53///
54/// This uses the JSON-RPC envelope types from `agent-client-protocol-schema`
55/// while keeping method params as raw, JSON-RPC-valid params at the transport boundary.
56#[derive(Debug, Clone)]
57pub enum RawJsonRpcMessage {
58 /// A JSON-RPC request with an id and expected response.
59 Request(RpcRequest<RawJsonRpcParams>),
60 /// A JSON-RPC notification without a response.
61 Notification(RpcNotification<RawJsonRpcParams>),
62 /// A JSON-RPC response to a prior request.
63 Response(RpcResponse<serde_json::Value>),
64}
65
66/// Raw JSON-RPC request or notification parameters.
67///
68/// JSON-RPC params, when present, must be either an array or an object.
69#[derive(Debug, Clone, PartialEq)]
70pub enum RawJsonRpcParams {
71 /// Positional JSON-RPC params.
72 Array(Vec<serde_json::Value>),
73 /// Named JSON-RPC params.
74 Object(serde_json::Map<String, serde_json::Value>),
75}
76
77impl RawJsonRpcParams {
78 /// Convert a JSON value into JSON-RPC params.
79 pub fn from_value(value: serde_json::Value) -> Result<Option<Self>, crate::Error> {
80 match value {
81 serde_json::Value::Null => Ok(None),
82 serde_json::Value::Array(array) => Ok(Some(Self::Array(array))),
83 serde_json::Value::Object(object) => Ok(Some(Self::Object(object))),
84 _ => {
85 Err(crate::Error::invalid_params()
86 .data("JSON-RPC params must be an object or array"))
87 }
88 }
89 }
90
91 /// Convert params back into a JSON value.
92 #[must_use]
93 pub fn into_value(self) -> serde_json::Value {
94 match self {
95 Self::Array(array) => serde_json::Value::Array(array),
96 Self::Object(object) => serde_json::Value::Object(object),
97 }
98 }
99}
100
101impl Serialize for RawJsonRpcParams {
102 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
103 where
104 S: serde::Serializer,
105 {
106 match self {
107 Self::Array(array) => array.serialize(serializer),
108 Self::Object(object) => object.serialize(serializer),
109 }
110 }
111}
112
113impl<'de> Deserialize<'de> for RawJsonRpcParams {
114 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
115 where
116 D: serde::Deserializer<'de>,
117 {
118 let value = serde_json::Value::deserialize(deserializer)?;
119 match value {
120 serde_json::Value::Array(array) => Ok(Self::Array(array)),
121 serde_json::Value::Object(object) => Ok(Self::Object(object)),
122 _ => Err(serde::de::Error::custom(
123 "JSON-RPC params must be an object or array",
124 )),
125 }
126 }
127}
128
129impl RawJsonRpcMessage {
130 /// Build a raw JSON-RPC request message.
131 pub fn request(
132 method: String,
133 params: serde_json::Value,
134 id: RequestId,
135 ) -> Result<Self, crate::Error> {
136 Ok(Self::Request(RpcRequest {
137 id,
138 method: Arc::from(method),
139 params: RawJsonRpcParams::from_value(params)?,
140 }))
141 }
142
143 /// Build a raw JSON-RPC notification message.
144 pub fn notification(method: String, params: serde_json::Value) -> Result<Self, crate::Error> {
145 Ok(Self::Notification(RpcNotification {
146 method: Arc::from(method),
147 params: RawJsonRpcParams::from_value(params)?,
148 }))
149 }
150
151 /// Build a raw JSON-RPC response message.
152 #[must_use]
153 pub fn response(id: RequestId, response: Result<serde_json::Value, crate::Error>) -> Self {
154 Self::Response(RpcResponse::new(id, response))
155 }
156
157 /// The response id, if this is a response.
158 #[must_use]
159 pub fn response_id(&self) -> Option<&RequestId> {
160 match self {
161 Self::Response(RpcResponse::Result { id, .. } | RpcResponse::Error { id, .. }) => {
162 Some(id)
163 }
164 Self::Request(_) | Self::Notification(_) => None,
165 }
166 }
167}
168
169impl Serialize for RawJsonRpcMessage {
170 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171 where
172 S: serde::Serializer,
173 {
174 match self {
175 Self::Request(request) => {
176 VersionedJsonRpcMessage::wrap(request.clone()).serialize(serializer)
177 }
178 Self::Notification(notification) => {
179 VersionedJsonRpcMessage::wrap(notification.clone()).serialize(serializer)
180 }
181 Self::Response(response) => {
182 VersionedJsonRpcMessage::wrap(response.clone()).serialize(serializer)
183 }
184 }
185 }
186}
187
188impl<'de> Deserialize<'de> for RawJsonRpcMessage {
189 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
190 where
191 D: serde::Deserializer<'de>,
192 {
193 let value = serde_json::Value::deserialize(deserializer)?;
194 if value.get("method").is_some() {
195 if value.get("id").is_some() {
196 let request = serde_json::from_value::<
197 VersionedJsonRpcMessage<RpcRequest<RawJsonRpcParams>>,
198 >(value)
199 .map_err(serde::de::Error::custom)?
200 .into_inner();
201 Ok(Self::Request(request))
202 } else {
203 let notification = serde_json::from_value::<
204 VersionedJsonRpcMessage<RpcNotification<RawJsonRpcParams>>,
205 >(value)
206 .map_err(serde::de::Error::custom)?
207 .into_inner();
208 Ok(Self::Notification(notification))
209 }
210 } else if value.get("result").is_some() || value.get("error").is_some() {
211 let response = serde_json::from_value::<
212 VersionedJsonRpcMessage<RpcResponse<serde_json::Value>>,
213 >(value)
214 .map_err(serde::de::Error::custom)?
215 .into_inner();
216 Ok(Self::Response(response))
217 } else {
218 Err(serde::de::Error::custom("invalid JSON-RPC message"))
219 }
220 }
221}
222
223fn params_from_transport(params: Option<RawJsonRpcParams>) -> serde_json::Value {
224 params.map_or(serde_json::Value::Null, RawJsonRpcParams::into_value)
225}
226
227/// Handlers process incoming JSON-RPC messages on a connection.
228///
229/// When messages arrive, they flow through a chain of handlers. Each handler can
230/// either **claim** the message (handle it) or **decline** it (pass to the next handler).
231///
232/// # Message Flow
233///
234/// Messages flow through three layers of handlers in order:
235///
236/// ```text
237/// ┌─────────────────────────────────────────────────────────────────┐
238/// │ Incoming Message │
239/// └─────────────────────────────────────────────────────────────────┘
240/// │
241/// ▼
242/// ┌─────────────────────────────────────────────────────────────────┐
243/// │ 1. User Handlers (registered via on_receive_request, etc.) │
244/// │ - Tried in registration order │
245/// │ - First handler to return Handled::Yes claims the message │
246/// └─────────────────────────────────────────────────────────────────┘
247/// │ Handled::No
248/// ▼
249/// ┌─────────────────────────────────────────────────────────────────┐
250/// │ 2. Dynamic Handlers (added at runtime) │
251/// │ - Used for session-specific message handling │
252/// │ - Added via ConnectionTo::add_dynamic_handler │
253/// └─────────────────────────────────────────────────────────────────┘
254/// │ Handled::No
255/// ▼
256/// ┌─────────────────────────────────────────────────────────────────┐
257/// │ 3. Role Default Handler │
258/// │ - Fallback based on the connection's Role │
259/// │ - Handles protocol-level messages (e.g., proxy forwarding) │
260/// └─────────────────────────────────────────────────────────────────┘
261/// │ Handled::No
262/// ▼
263/// ┌─────────────────────────────────────────────────────────────────┐
264/// │ Unhandled: requests error, notifications ignored │
265/// └─────────────────────────────────────────────────────────────────┘
266/// ```
267///
268/// # The `Handled` Return Value
269///
270/// Each handler returns [`Handled`] to indicate whether it processed the message:
271///
272/// - **`Handled::Yes`** - Message was handled. No further handlers are invoked.
273/// - **`Handled::No { message, retry }`** - Message was not handled. The message
274/// (possibly modified) is passed to the next handler in the chain.
275///
276/// For convenience, handlers can return `()` which is equivalent to `Handled::Yes`.
277///
278/// # The Retry Mechanism
279///
280/// The `retry` flag in `Handled::No` controls what happens when no handler claims a message:
281///
282/// - **`retry: false`** (default) - Send a "method not found" error
283/// response immediately for requests, or ignore notifications.
284/// - **`retry: true`** - Queue the message and retry it when new dynamic handlers are added.
285///
286/// This mechanism exists because of a timing issue with sessions: when a `session/new`
287/// response is being processed, the dynamic handler for that session hasn't been registered
288/// yet, but `session/update` notifications for that session may already be arriving.
289/// By setting `retry: true`, these early notifications are queued until the session's
290/// dynamic handler is added.
291///
292/// # Handler Registration
293///
294/// Most users register handlers using the builder methods on [`Builder`]:
295///
296/// ```
297/// # use agent_client_protocol::{Agent, Client, ConnectTo};
298/// # use agent_client_protocol::schema::v1::{AgentCapabilities, InitializeRequest, InitializeResponse};
299/// # use agent_client_protocol_test::StatusUpdate;
300/// # async fn example(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
301/// Agent.builder()
302/// .on_receive_request(async |req: InitializeRequest, responder, cx| {
303/// responder.respond(
304/// InitializeResponse::new(req.protocol_version)
305/// .agent_capabilities(AgentCapabilities::new()),
306/// )
307/// }, agent_client_protocol::on_receive_request!())
308/// .on_receive_notification(async |notif: StatusUpdate, cx| {
309/// // Process notification
310/// Ok(())
311/// }, agent_client_protocol::on_receive_notification!())
312/// .connect_to(transport)
313/// .await?;
314/// # Ok(())
315/// # }
316/// ```
317///
318/// The type parameter on the closure determines which messages are dispatched to it.
319/// Messages that don't match the type are automatically passed to the next handler.
320///
321/// # Implementing Custom Handlers
322///
323/// For advanced use cases, you can implement `HandleMessageAs` directly:
324///
325/// ```ignore
326/// struct MyHandler;
327///
328/// impl HandleMessageAs<Agent> for MyHandler {
329///
330/// async fn handle_dispatch(
331/// &mut self,
332/// message: Dispatch,
333/// cx: ConnectionTo<Self::Role>,
334/// ) -> Result<Handled<Dispatch>, Error> {
335/// if message.method() == "my/custom/method" {
336/// // Handle it
337/// Ok(Handled::Yes)
338/// } else {
339/// // Pass to next handler
340/// Ok(Handled::No { message, retry: false })
341/// }
342/// }
343///
344/// fn describe_chain(&self) -> impl std::fmt::Debug {
345/// "MyHandler"
346/// }
347/// }
348/// ```
349///
350/// # Important: Handlers Must Not Block
351///
352/// The connection processes messages on a single async task. While a handler is running,
353/// no other messages can be processed. For expensive operations, use [`ConnectionTo::spawn`]
354/// to run work concurrently:
355///
356/// ```
357/// # use agent_client_protocol::{Client, Agent, ConnectTo};
358/// # use agent_client_protocol_test::{expensive_operation, ProcessComplete};
359/// # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
360/// # Client.builder().connect_with(transport, async |cx| {
361/// cx.spawn({
362/// let connection = cx.clone();
363/// async move {
364/// let result = expensive_operation("data").await?;
365/// connection.send_notification(ProcessComplete { result })?;
366/// Ok(())
367/// }
368/// })?;
369/// # Ok(())
370/// # }).await?;
371/// # Ok(())
372/// # }
373/// ```
374#[allow(async_fn_in_trait)]
375/// A handler for incoming JSON-RPC messages.
376///
377/// This trait is implemented by types that can process incoming messages on a connection.
378/// Handlers are registered with a [`Builder`] and are called in order until
379/// one claims the message.
380///
381/// The type parameter `R` is the role this handler plays - who I am.
382/// For an agent handler, `R = Agent` (I handle messages as an agent).
383/// For a client handler, `R = Client` (I handle messages as a client).
384pub trait HandleDispatchFrom<Counterpart: Role>: Send {
385 /// Attempt to claim an incoming message (request or notification).
386 ///
387 /// # Important: do not block
388 ///
389 /// The server will not process new messages until this handler returns.
390 /// You should avoid blocking in this callback unless you wish to block the server (e.g., for rate limiting).
391 /// The recommended approach to manage expensive operations is to the [`ConnectionTo::spawn`] method available on the message context.
392 ///
393 /// # Parameters
394 ///
395 /// * `message` - The incoming message to handle.
396 /// * `connection` - The connection, used to send messages and access connection state.
397 ///
398 /// # Returns
399 ///
400 /// * `Ok(Handled::Yes)` if the message was claimed. It will not be propagated further.
401 /// * `Ok(Handled::No(message))` if not; the (possibly changed) message will be passed to the remaining handlers.
402 /// * `Err` if an internal error occurs (this will bring down the server).
403 fn handle_dispatch_from(
404 &mut self,
405 message: Dispatch,
406 connection: ConnectionTo<Counterpart>,
407 ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send;
408
409 /// Returns a debug description of the registered handlers for diagnostics.
410 fn describe_chain(&self) -> impl std::fmt::Debug;
411}
412
413impl<Counterpart: Role, H> HandleDispatchFrom<Counterpart> for &mut H
414where
415 H: HandleDispatchFrom<Counterpart>,
416{
417 fn handle_dispatch_from(
418 &mut self,
419 message: Dispatch,
420 cx: ConnectionTo<Counterpart>,
421 ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
422 H::handle_dispatch_from(self, message, cx)
423 }
424
425 fn describe_chain(&self) -> impl std::fmt::Debug {
426 H::describe_chain(self)
427 }
428}
429
430/// A JSON-RPC connection that can act as either a server, client, or both.
431///
432/// [`Builder`] provides a builder-style API for creating JSON-RPC servers and clients.
433/// You start by calling `Role.builder()` (e.g., `Client.builder()`), then add message
434/// handlers, and finally drive the connection with either [`connect_to`](Builder::connect_to)
435/// or [`connect_with`](Builder::connect_with), providing a component implementation
436/// (e.g., [`ByteStreams`] for byte streams).
437///
438/// # JSON-RPC Primer
439///
440/// JSON-RPC 2.0 has two fundamental message types:
441///
442/// * **Requests** - Messages that expect a response. They have an `id` field that gets
443/// echoed back in the response so the sender can correlate them.
444/// * **Notifications** - Fire-and-forget messages with no `id` field. The sender doesn't
445/// expect or receive a response.
446///
447/// # Type-Driven Message Dispatch
448///
449/// The handler registration methods use Rust's type system to determine which messages
450/// to handle. The type parameter you provide controls what gets dispatched to your handler:
451///
452/// ## Single Message Types
453///
454/// The simplest case - handle one specific message type:
455///
456/// ```no_run
457/// # use agent_client_protocol_test::*;
458/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, SessionNotification};
459/// # async fn example() -> Result<(), agent_client_protocol::Error> {
460/// # let connection = mock_connection();
461/// connection
462/// .on_receive_request(async |req: InitializeRequest, responder, cx| {
463/// // Handle only InitializeRequest messages
464/// responder.respond(InitializeResponse::make())
465/// }, agent_client_protocol::on_receive_request!())
466/// .on_receive_notification(async |notif: SessionNotification, cx| {
467/// // Handle only SessionUpdate notifications
468/// Ok(())
469/// }, agent_client_protocol::on_receive_notification!())
470/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
471/// # Ok(())
472/// # }
473/// ```
474///
475/// ## Enum Message Types
476///
477/// You can also handle multiple related messages with a single handler by defining an enum
478/// that implements the appropriate trait ([`JsonRpcRequest`] or [`JsonRpcNotification`]):
479///
480/// ```no_run
481/// # use agent_client_protocol_test::*;
482/// # use agent_client_protocol::{JsonRpcRequest, JsonRpcMessage, UntypedMessage};
483/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse};
484/// # async fn example() -> Result<(), agent_client_protocol::Error> {
485/// # let connection = mock_connection();
486/// // Define an enum for multiple request types
487/// #[derive(Debug, Clone)]
488/// enum MyRequests {
489/// Initialize(InitializeRequest),
490/// Prompt(PromptRequest),
491/// }
492///
493/// // Implement JsonRpcRequest for your enum
494/// # impl JsonRpcMessage for MyRequests {
495/// # fn matches_method(_method: &str) -> bool { false }
496/// # fn method(&self) -> &str { "myRequests" }
497/// # fn to_untyped_message(&self) -> Result<UntypedMessage, agent_client_protocol::Error> { todo!() }
498/// # fn parse_message(_method: &str, _params: &impl serde::Serialize) -> Result<Self, agent_client_protocol::Error> { Err(agent_client_protocol::Error::method_not_found()) }
499/// # }
500/// impl JsonRpcRequest for MyRequests { type Response = serde_json::Value; }
501///
502/// // Handle all variants in one place
503/// connection.on_receive_request(async |req: MyRequests, responder, cx| {
504/// match req {
505/// MyRequests::Initialize(init) => { responder.respond(serde_json::json!({})) }
506/// MyRequests::Prompt(prompt) => { responder.respond(serde_json::json!({})) }
507/// }
508/// }, agent_client_protocol::on_receive_request!())
509/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
510/// # Ok(())
511/// # }
512/// ```
513///
514/// ## Mixed Message Types
515///
516/// For enums containing both requests AND notifications, use [`on_receive_dispatch`](Self::on_receive_dispatch):
517///
518/// ```no_run
519/// # use agent_client_protocol_test::*;
520/// # use agent_client_protocol::Dispatch;
521/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, SessionNotification};
522/// # async fn example() -> Result<(), agent_client_protocol::Error> {
523/// # let connection = mock_connection();
524/// // on_receive_dispatch receives Dispatch which can be either a request or notification
525/// connection.on_receive_dispatch(async |msg: Dispatch<InitializeRequest, SessionNotification>, _cx| {
526/// match msg {
527/// Dispatch::Request(req, responder) => {
528/// responder.respond(InitializeResponse::make())
529/// }
530/// Dispatch::Notification(notif) => {
531/// Ok(())
532/// }
533/// Dispatch::Response(result, router) => {
534/// // Forward response to its destination
535/// router.respond_with_result(result)
536/// }
537/// }
538/// }, agent_client_protocol::on_receive_dispatch!())
539/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
540/// # Ok(())
541/// # }
542/// ```
543///
544/// # Handler Registration
545///
546/// Register handlers using these methods (listed from most common to most flexible):
547///
548/// * [`on_receive_request`](Self::on_receive_request) - Handle JSON-RPC requests (messages expecting responses)
549/// * [`on_receive_notification`](Self::on_receive_notification) - Handle JSON-RPC notifications (fire-and-forget)
550/// * [`on_receive_dispatch`](Self::on_receive_dispatch) - Handle enums containing both requests and notifications
551/// * [`with_handler`](Self::with_handler) - Low-level primitive for maximum flexibility
552///
553/// ## Handler Ordering
554///
555/// Handlers are tried in the order you register them. The first handler that claims a message
556/// (by matching its type) will process it. Subsequent handlers won't see that message:
557///
558/// ```no_run
559/// # use agent_client_protocol_test::*;
560/// # use agent_client_protocol::{Dispatch, UntypedMessage};
561/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse};
562/// # async fn example() -> Result<(), agent_client_protocol::Error> {
563/// # let connection = mock_connection();
564/// connection
565/// .on_receive_request(async |req: InitializeRequest, responder, cx| {
566/// // This runs first for InitializeRequest
567/// responder.respond(InitializeResponse::make())
568/// }, agent_client_protocol::on_receive_request!())
569/// .on_receive_request(async |req: PromptRequest, responder, cx| {
570/// // This runs first for PromptRequest
571/// responder.respond(PromptResponse::make())
572/// }, agent_client_protocol::on_receive_request!())
573/// .on_receive_dispatch(async |msg: Dispatch, cx| {
574/// // This runs for any message not handled above
575/// msg.respond_with_error(agent_client_protocol::util::internal_error("unknown method"), cx)
576/// }, agent_client_protocol::on_receive_dispatch!())
577/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
578/// # Ok(())
579/// # }
580/// ```
581///
582/// # Event Loop and Concurrency
583///
584/// Understanding the event loop is critical for writing correct handlers.
585///
586/// ## The Event Loop
587///
588/// [`Builder`] runs all handler callbacks on a single async task - the event loop.
589/// While a handler is running, **the server cannot receive new messages**. This means
590/// any blocking or expensive work in your handlers will stall the entire connection.
591///
592/// To avoid blocking the event loop, use [`ConnectionTo::spawn`] to offload serious
593/// work to concurrent tasks:
594///
595/// ```no_run
596/// # use agent_client_protocol_test::*;
597/// # async fn example() -> Result<(), agent_client_protocol::Error> {
598/// # let connection = mock_connection();
599/// connection.on_receive_request(async |req: AnalyzeRequest, responder, cx| {
600/// // Clone cx for the spawned task
601/// cx.spawn({
602/// let connection = cx.clone();
603/// async move {
604/// let result = expensive_analysis(&req.data).await?;
605/// connection.send_notification(AnalysisComplete { result })?;
606/// Ok(())
607/// }
608/// })?;
609///
610/// // Respond immediately without blocking
611/// responder.respond(AnalysisStarted { job_id: 42 })
612/// }, agent_client_protocol::on_receive_request!())
613/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
614/// # Ok(())
615/// # }
616/// ```
617///
618/// Note that the entire connection runs within one async task, so parallelism must be
619/// managed explicitly using [`spawn`](ConnectionTo::spawn).
620///
621/// ## The Connection Context
622///
623/// Handler callbacks receive a context object (`cx`) for interacting with the connection:
624///
625/// * **For request handlers** - [`Responder<R>`] provides [`respond`](Responder::respond)
626/// to send the response, plus methods to send other messages
627/// * **For notification handlers** - [`ConnectionTo`] provides methods to send messages
628/// and spawn tasks
629///
630/// Both context types support:
631/// * [`send_request`](ConnectionTo::send_request) - Send requests to the other side
632/// * [`send_notification`](ConnectionTo::send_notification) - Send notifications
633/// * [`spawn`](ConnectionTo::spawn) - Run tasks concurrently without blocking the event loop
634///
635/// The [`SentRequest`] returned by `send_request` provides methods like
636/// [`on_receiving_result`](SentRequest::on_receiving_result) that help you
637/// avoid accidentally blocking the event loop while waiting for responses.
638///
639/// # Driving the Connection
640///
641/// After adding handlers, you must drive the connection using one of two modes:
642///
643/// ## Server Mode: `connect_to()`
644///
645/// Use [`connect_to`](Self::connect_to) when you only need to respond to incoming messages:
646///
647/// ```no_run
648/// # use agent_client_protocol_test::*;
649/// # async fn example() -> Result<(), agent_client_protocol::Error> {
650/// # let connection = mock_connection();
651/// connection
652/// .on_receive_request(async |req: MyRequest, responder, cx| {
653/// responder.respond(MyResponse { status: "ok".into() })
654/// }, agent_client_protocol::on_receive_request!())
655/// .connect_to(MockTransport) // Runs until connection closes or error occurs
656/// .await?;
657/// # Ok(())
658/// # }
659/// ```
660///
661/// The connection will process incoming messages and invoke your handlers until the
662/// connection is closed or an error occurs.
663///
664/// ## Client Mode: `connect_with()`
665///
666/// Use [`connect_with`](Self::connect_with) when you need to both handle incoming messages
667/// AND send your own requests/notifications:
668///
669/// ```no_run
670/// # use agent_client_protocol_test::*;
671/// # use agent_client_protocol::schema::v1::InitializeRequest;
672/// # async fn example() -> Result<(), agent_client_protocol::Error> {
673/// # let connection = mock_connection();
674/// connection
675/// .on_receive_request(async |req: MyRequest, responder, cx| {
676/// responder.respond(MyResponse { status: "ok".into() })
677/// }, agent_client_protocol::on_receive_request!())
678/// .connect_with(MockTransport, async |cx| {
679/// // You can send requests to the other side
680/// let response = cx.send_request(InitializeRequest::make())
681/// .block_task()
682/// .await?;
683///
684/// // And send notifications
685/// cx.send_notification(StatusUpdate { message: "ready".into() })?;
686///
687/// Ok(())
688/// })
689/// .await?;
690/// # Ok(())
691/// # }
692/// ```
693///
694/// The connection will serve incoming messages in the background while your client closure
695/// runs. When the closure returns, the connection shuts down.
696///
697/// # Example: Complete Agent
698///
699/// ```no_run
700/// # use agent_client_protocol::UntypedRole;
701/// # use agent_client_protocol::{Builder};
702/// # use agent_client_protocol::Stdio;
703/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse, SessionNotification};
704/// # async fn example() -> Result<(), agent_client_protocol::Error> {
705/// let transport = Stdio::new();
706///
707/// UntypedRole.builder()
708/// .name("my-agent") // Optional: for debugging logs
709/// .on_receive_request(async |init: InitializeRequest, responder, cx| {
710/// let response: InitializeResponse = todo!();
711/// responder.respond(response)
712/// }, agent_client_protocol::on_receive_request!())
713/// .on_receive_request(async |prompt: PromptRequest, responder, cx| {
714/// // You can send notifications while processing a request
715/// let notif: SessionNotification = todo!();
716/// cx.send_notification(notif)?;
717///
718/// // Then respond to the request
719/// let response: PromptResponse = todo!();
720/// responder.respond(response)
721/// }, agent_client_protocol::on_receive_request!())
722/// .connect_to(transport)
723/// .await?;
724/// # Ok(())
725/// # }
726/// ```
727#[must_use]
728#[derive(Debug)]
729pub struct Builder<Host: Role, Handler = NullHandler, Runner = NullRun, Close = NullClose>
730where
731 Handler: HandleDispatchFrom<Host::Counterpart>,
732 Runner: RunWithConnectionTo<Host::Counterpart>,
733 Close: HandleConnectionClose<Host::Counterpart>,
734{
735 /// My role.
736 host: Host,
737
738 /// Name of the connection, used in tracing logs.
739 name: Option<String>,
740
741 /// Handler for incoming messages.
742 handler: Handler,
743
744 /// Responder for background tasks.
745 responder: Runner,
746
747 /// Protocol version mode for the public API and wire compatibility layer.
748 protocol_mode: ProtocolMode,
749
750 /// Handler run when the incoming transport reaches clean EOF.
751 on_close: Close,
752}
753
754fn default_protocol_mode<Host: Role>() -> ProtocolMode {
755 let role = TypeId::of::<Host>();
756
757 if role == TypeId::of::<Agent>() {
758 ProtocolMode::v1_agent()
759 } else if role == TypeId::of::<Client>() {
760 ProtocolMode::v1_client()
761 } else {
762 ProtocolMode::disabled()
763 }
764}
765
766impl<Host: Role> Builder<Host, NullHandler, NullRun, NullClose> {
767 /// Create a new connection builder for the given role.
768 /// This type follows a builder pattern; use other methods to configure and then invoke
769 /// [`Self::connect_to`] (to use as a server) or [`Self::connect_with`] to use as a client.
770 pub fn new(role: Host) -> Self {
771 Self {
772 host: role,
773 name: None,
774 handler: NullHandler,
775 responder: NullRun,
776 protocol_mode: default_protocol_mode::<Host>(),
777 on_close: NullClose,
778 }
779 }
780}
781
782impl<Host: Role, Handler> Builder<Host, Handler, NullRun, NullClose>
783where
784 Handler: HandleDispatchFrom<Host::Counterpart>,
785{
786 /// Create a new connection builder with the given handler.
787 pub fn new_with(role: Host, handler: Handler) -> Self {
788 Self {
789 host: role,
790 name: None,
791 handler,
792 responder: NullRun,
793 protocol_mode: default_protocol_mode::<Host>(),
794 on_close: NullClose,
795 }
796 }
797}
798
799impl<
800 Host: Role,
801 Handler: HandleDispatchFrom<Host::Counterpart>,
802 Runner: RunWithConnectionTo<Host::Counterpart>,
803 Close: HandleConnectionClose<Host::Counterpart>,
804> Builder<Host, Handler, Runner, Close>
805{
806 /// Set the "name" of this connection -- used only for debugging logs.
807 pub fn name(mut self, name: impl ToString) -> Self {
808 self.name = Some(name.to_string());
809 self
810 }
811
812 pub(crate) fn v1_agent(mut self) -> Self {
813 self.protocol_mode = ProtocolMode::v1_agent();
814 self
815 }
816
817 pub(crate) fn v1_client(mut self) -> Self {
818 self.protocol_mode = ProtocolMode::v1_client();
819 self
820 }
821
822 #[cfg(feature = "unstable_protocol_v2")]
823 pub(crate) fn v2_agent(mut self) -> Self {
824 self.protocol_mode = ProtocolMode::v2_agent();
825 self
826 }
827
828 #[cfg(feature = "unstable_protocol_v2")]
829 pub(crate) fn v2_client(mut self) -> Self {
830 self.protocol_mode = ProtocolMode::v2_client();
831 self
832 }
833
834 /// Merge another [`Builder`] into this one.
835 ///
836 /// Prefer [`Self::on_receive_request`] or [`Self::on_receive_notification`].
837 /// This is a low-level method that is not intended for general use.
838 pub fn with_connection_builder(
839 self,
840 other: Builder<
841 Host,
842 impl HandleDispatchFrom<Host::Counterpart>,
843 impl RunWithConnectionTo<Host::Counterpart>,
844 impl HandleConnectionClose<Host::Counterpart>,
845 >,
846 ) -> Builder<
847 Host,
848 impl HandleDispatchFrom<Host::Counterpart>,
849 impl RunWithConnectionTo<Host::Counterpart>,
850 impl HandleConnectionClose<Host::Counterpart>,
851 > {
852 let Builder {
853 name: other_name,
854 handler: other_handler,
855 responder: other_responder,
856 protocol_mode: other_protocol_mode,
857 on_close: other_on_close,
858 host: _,
859 } = other;
860 Builder {
861 host: self.host,
862 name: self.name,
863 handler: ChainedHandler::new(
864 self.handler,
865 NamedHandler::new(other_name, other_handler),
866 ),
867 responder: ChainRun::new(self.responder, other_responder),
868 protocol_mode: self.protocol_mode.merge(other_protocol_mode),
869 on_close: ChainedClose::new(self.on_close, other_on_close),
870 }
871 }
872
873 /// Add a new [`HandleDispatchFrom`] to the chain.
874 ///
875 /// Prefer [`Self::on_receive_request`] or [`Self::on_receive_notification`].
876 /// This is a low-level method that is not intended for general use.
877 pub fn with_handler(
878 self,
879 handler: impl HandleDispatchFrom<Host::Counterpart>,
880 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close> {
881 Builder {
882 host: self.host,
883 name: self.name,
884 handler: ChainedHandler::new(self.handler, handler),
885 responder: self.responder,
886 protocol_mode: self.protocol_mode,
887 on_close: self.on_close,
888 }
889 }
890
891 /// Add a new [`RunWithConnectionTo`] to the chain.
892 pub fn with_responder<Run1>(
893 self,
894 responder: Run1,
895 ) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close>
896 where
897 Run1: RunWithConnectionTo<Host::Counterpart>,
898 {
899 Builder {
900 host: self.host,
901 name: self.name,
902 handler: self.handler,
903 responder: ChainRun::new(self.responder, responder),
904 protocol_mode: self.protocol_mode,
905 on_close: self.on_close,
906 }
907 }
908
909 /// Enqueue a task to run once the connection is actively serving traffic.
910 #[track_caller]
911 pub fn with_spawned<F, Fut>(
912 self,
913 task: F,
914 ) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close>
915 where
916 F: FnOnce(ConnectionTo<Host::Counterpart>) -> Fut + Send,
917 Fut: Future<Output = Result<(), crate::Error>> + Send,
918 {
919 let location = Location::caller();
920 self.with_responder(SpawnedRun::new(location, task))
921 }
922
923 /// Run a callback when the incoming transport reaches clean EOF.
924 ///
925 /// Each callback runs at most once and receives the connection context. A
926 /// successful callback observes the close without otherwise changing the
927 /// lifetime of [`connect_with`](Self::connect_with). Returning an error
928 /// shuts down the connection and cancels a still-running `connect_with`
929 /// future.
930 ///
931 /// Multiple callbacks run sequentially in registration order. All of them
932 /// run even if an earlier callback fails, after which the first error is
933 /// returned. Pending requests are failed before callbacks begin, while
934 /// [`ConnectionTo::incoming_closed`] completes only after they finish. A
935 /// callback must therefore not await that close future itself.
936 ///
937 /// This separation lets applications choose their cancellation policy. A
938 /// callback can notify application-owned tasks and return `Ok(())` for
939 /// graceful cleanup, or return an error to stop them immediately.
940 ///
941 /// ```
942 /// # use agent_client_protocol::{Client, ConnectTo, Error};
943 /// # async fn example(transport: impl ConnectTo<Client>) -> Result<(), Error> {
944 /// Client.builder()
945 /// .on_close(async |_cx| {
946 /// Err(Error::internal_error().data("agent transport closed"))
947 /// })
948 /// .connect_with(transport, async |_cx| {
949 /// std::future::pending().await
950 /// })
951 /// .await?;
952 /// # Ok(())
953 /// # }
954 /// ```
955 pub fn on_close<F, Fut>(
956 self,
957 callback: F,
958 ) -> Builder<Host, Handler, Runner, impl HandleConnectionClose<Host::Counterpart>>
959 where
960 F: FnOnce(ConnectionTo<Host::Counterpart>) -> Fut + Send,
961 Fut: Future<Output = Result<(), crate::Error>> + Send,
962 {
963 Builder {
964 host: self.host,
965 name: self.name,
966 handler: self.handler,
967 responder: self.responder,
968 protocol_mode: self.protocol_mode,
969 on_close: ChainedClose::new(self.on_close, CloseCallback::new(callback)),
970 }
971 }
972
973 /// Register a handler for messages that can be either requests OR notifications.
974 ///
975 /// Use this when you want to handle an enum type that contains both request and
976 /// notification variants. Your handler receives a [`Dispatch<Req, Notif>`] which
977 /// is an enum with two variants:
978 ///
979 /// - `Dispatch::Request(request, responder)` - A request with its response context
980 /// - `Dispatch::Notification(notification)` - A notification
981 /// - `Dispatch::Response(result, router)` - A response to a request we sent
982 ///
983 /// # Example
984 ///
985 /// ```no_run
986 /// # use agent_client_protocol_test::*;
987 /// # use agent_client_protocol::Dispatch;
988 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
989 /// # let connection = mock_connection();
990 /// connection.on_receive_dispatch(async |message: Dispatch<MyRequest, StatusUpdate>, _cx| {
991 /// match message {
992 /// Dispatch::Request(req, responder) => {
993 /// // Handle request and send response
994 /// responder.respond(MyResponse { status: "ok".into() })
995 /// }
996 /// Dispatch::Notification(notif) => {
997 /// // Handle notification (no response needed)
998 /// Ok(())
999 /// }
1000 /// Dispatch::Response(result, router) => {
1001 /// // Forward response to its destination
1002 /// router.respond_with_result(result)
1003 /// }
1004 /// }
1005 /// }, agent_client_protocol::on_receive_dispatch!())
1006 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
1007 /// # Ok(())
1008 /// # }
1009 /// ```
1010 ///
1011 /// For most use cases, prefer [`on_receive_request`](Self::on_receive_request) or
1012 /// [`on_receive_notification`](Self::on_receive_notification) which provide cleaner APIs
1013 /// for handling requests or notifications separately.
1014 ///
1015 /// # Ordering
1016 ///
1017 /// This callback runs inside the dispatch loop and blocks further message processing
1018 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1019 /// ordering guarantees and how to avoid deadlocks.
1020 pub fn on_receive_dispatch<Req, Notif, F, T, ToFut>(
1021 self,
1022 op: F,
1023 to_future_hack: ToFut,
1024 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close>
1025 where
1026 Host::Counterpart: HasPeer<Host::Counterpart>,
1027 Req: JsonRpcRequest,
1028 Notif: JsonRpcNotification,
1029 F: AsyncFnMut(
1030 Dispatch<Req, Notif>,
1031 ConnectionTo<Host::Counterpart>,
1032 ) -> Result<T, crate::Error>
1033 + Send,
1034 T: IntoHandled<Dispatch<Req, Notif>>,
1035 ToFut: Fn(
1036 &mut F,
1037 Dispatch<Req, Notif>,
1038 ConnectionTo<Host::Counterpart>,
1039 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1040 + Send
1041 + Sync,
1042 {
1043 let handler = MessageHandler::new(
1044 self.host.counterpart(),
1045 self.host.counterpart(),
1046 op,
1047 to_future_hack,
1048 );
1049 self.with_handler(handler)
1050 }
1051
1052 /// Register a handler for JSON-RPC requests of type `Req`.
1053 ///
1054 /// Your handler receives two arguments:
1055 /// 1. The request (type `Req`)
1056 /// 2. A [`Responder<R, Req::Response>`] for sending the response
1057 ///
1058 /// The request context allows you to:
1059 /// - Send the response with [`Responder::respond`]
1060 /// - Send notifications to the client with [`ConnectionTo::send_notification`]
1061 /// - Send requests to the client with [`ConnectionTo::send_request`]
1062 ///
1063 /// # Example
1064 ///
1065 /// ```ignore
1066 /// # use agent_client_protocol::UntypedRole;
1067 /// # use agent_client_protocol::{Builder};
1068 /// # use agent_client_protocol::schema::v1::{PromptRequest, PromptResponse, SessionNotification};
1069 /// # fn example<R: agent_client_protocol::Role>(connection: Builder<R, impl agent_client_protocol::HandleMessageAs<R>>) {
1070 /// connection.on_receive_request(async |request: PromptRequest, responder, cx| {
1071 /// // Send a notification while processing
1072 /// let notif: SessionNotification = todo!();
1073 /// cx.send_notification(notif)?;
1074 ///
1075 /// // Do some work...
1076 /// let result = todo!("process the prompt");
1077 ///
1078 /// // Send the response
1079 /// let response: PromptResponse = todo!();
1080 /// responder.respond(response)
1081 /// }, agent_client_protocol::on_receive_request!());
1082 /// # }
1083 /// ```
1084 ///
1085 /// # Type Parameter
1086 ///
1087 /// `Req` can be either a single request type or an enum of multiple request types.
1088 /// See the [type-driven dispatch](Self#type-driven-message-dispatch) section for details.
1089 ///
1090 /// # Ordering
1091 ///
1092 /// This callback runs inside the dispatch loop and blocks further message processing
1093 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1094 /// ordering guarantees and how to avoid deadlocks.
1095 pub fn on_receive_request<Req: JsonRpcRequest, F, T, ToFut>(
1096 self,
1097 op: F,
1098 to_future_hack: ToFut,
1099 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close>
1100 where
1101 Host::Counterpart: HasPeer<Host::Counterpart>,
1102 F: AsyncFnMut(
1103 Req,
1104 Responder<Req::Response>,
1105 ConnectionTo<Host::Counterpart>,
1106 ) -> Result<T, crate::Error>
1107 + Send,
1108 T: IntoHandled<(Req, Responder<Req::Response>)>,
1109 ToFut: Fn(
1110 &mut F,
1111 Req,
1112 Responder<Req::Response>,
1113 ConnectionTo<Host::Counterpart>,
1114 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1115 + Send
1116 + Sync,
1117 {
1118 let handler = RequestHandler::new(
1119 self.host.counterpart(),
1120 self.host.counterpart(),
1121 op,
1122 to_future_hack,
1123 );
1124 self.with_handler(handler)
1125 }
1126
1127 /// Register a handler for JSON-RPC notifications of type `Notif`.
1128 ///
1129 /// Notifications are fire-and-forget messages that don't expect a response.
1130 /// Your handler receives:
1131 /// 1. The notification (type `Notif`)
1132 /// 2. A [`ConnectionTo<R>`] for sending messages to the other side
1133 ///
1134 /// Unlike request handlers, you cannot send a response (notifications don't have IDs),
1135 /// but you can still send your own requests and notifications using the context.
1136 ///
1137 /// # Example
1138 ///
1139 /// ```no_run
1140 /// # use agent_client_protocol_test::*;
1141 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1142 /// # let connection = mock_connection();
1143 /// connection.on_receive_notification(async |notif: SessionUpdate, cx| {
1144 /// // Process the notification
1145 /// update_session_state(¬if)?;
1146 ///
1147 /// // Optionally send a notification back
1148 /// cx.send_notification(StatusUpdate {
1149 /// message: "Acknowledged".into(),
1150 /// })?;
1151 ///
1152 /// Ok(())
1153 /// }, agent_client_protocol::on_receive_notification!())
1154 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
1155 /// # Ok(())
1156 /// # }
1157 /// ```
1158 ///
1159 /// # Type Parameter
1160 ///
1161 /// `Notif` can be either a single notification type or an enum of multiple notification types.
1162 /// See the [type-driven dispatch](Self#type-driven-message-dispatch) section for details.
1163 ///
1164 /// # Ordering
1165 ///
1166 /// This callback runs inside the dispatch loop and blocks further message processing
1167 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1168 /// ordering guarantees and how to avoid deadlocks.
1169 pub fn on_receive_notification<Notif, F, T, ToFut>(
1170 self,
1171 op: F,
1172 to_future_hack: ToFut,
1173 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close>
1174 where
1175 Host::Counterpart: HasPeer<Host::Counterpart>,
1176 Notif: JsonRpcNotification,
1177 F: AsyncFnMut(Notif, ConnectionTo<Host::Counterpart>) -> Result<T, crate::Error> + Send,
1178 T: IntoHandled<(Notif, ConnectionTo<Host::Counterpart>)>,
1179 ToFut: Fn(
1180 &mut F,
1181 Notif,
1182 ConnectionTo<Host::Counterpart>,
1183 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1184 + Send
1185 + Sync,
1186 {
1187 let handler = NotificationHandler::new(
1188 self.host.counterpart(),
1189 self.host.counterpart(),
1190 op,
1191 to_future_hack,
1192 );
1193 self.with_handler(handler)
1194 }
1195
1196 /// Register a handler for messages from a specific peer.
1197 ///
1198 /// This is similar to [`on_receive_dispatch`](Self::on_receive_dispatch), but allows
1199 /// specifying the source peer explicitly. This is useful when receiving messages
1200 /// from a peer that requires message transformation (e.g., unwrapping `SuccessorMessage`
1201 /// envelopes when receiving from an agent via a proxy).
1202 ///
1203 /// For the common case of receiving from the default counterpart, use
1204 /// [`on_receive_dispatch`](Self::on_receive_dispatch) instead.
1205 ///
1206 /// # Ordering
1207 ///
1208 /// This callback runs inside the dispatch loop and blocks further message processing
1209 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1210 /// ordering guarantees and how to avoid deadlocks.
1211 pub fn on_receive_dispatch_from<
1212 Req: JsonRpcRequest,
1213 Notif: JsonRpcNotification,
1214 Peer: Role,
1215 F,
1216 T,
1217 ToFut,
1218 >(
1219 self,
1220 peer: Peer,
1221 op: F,
1222 to_future_hack: ToFut,
1223 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close>
1224 where
1225 Host::Counterpart: HasPeer<Peer>,
1226 F: AsyncFnMut(
1227 Dispatch<Req, Notif>,
1228 ConnectionTo<Host::Counterpart>,
1229 ) -> Result<T, crate::Error>
1230 + Send,
1231 T: IntoHandled<Dispatch<Req, Notif>>,
1232 ToFut: Fn(
1233 &mut F,
1234 Dispatch<Req, Notif>,
1235 ConnectionTo<Host::Counterpart>,
1236 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1237 + Send
1238 + Sync,
1239 {
1240 let handler = MessageHandler::new(self.host.counterpart(), peer, op, to_future_hack);
1241 self.with_handler(handler)
1242 }
1243
1244 /// Register a handler for JSON-RPC requests from a specific peer.
1245 ///
1246 /// This is similar to [`on_receive_request`](Self::on_receive_request), but allows
1247 /// specifying the source peer explicitly. This is useful when receiving messages
1248 /// from a peer that requires message transformation (e.g., unwrapping `SuccessorRequest`
1249 /// envelopes when receiving from an agent via a proxy).
1250 ///
1251 /// For the common case of receiving from the default counterpart, use
1252 /// [`on_receive_request`](Self::on_receive_request) instead.
1253 ///
1254 /// # Example
1255 ///
1256 /// ```ignore
1257 /// use agent_client_protocol::Agent;
1258 /// use agent_client_protocol::schema::v1::InitializeRequest;
1259 ///
1260 /// // Conductor receiving from agent direction - messages will be unwrapped from SuccessorMessage
1261 /// connection.on_receive_request_from(Agent, async |req: InitializeRequest, responder, cx| {
1262 /// // Handle the request
1263 /// responder.respond(InitializeResponse::make())
1264 /// })
1265 /// ```
1266 ///
1267 /// # Ordering
1268 ///
1269 /// This callback runs inside the dispatch loop and blocks further message processing
1270 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1271 /// ordering guarantees and how to avoid deadlocks.
1272 pub fn on_receive_request_from<Req: JsonRpcRequest, Peer: Role, F, T, ToFut>(
1273 self,
1274 peer: Peer,
1275 op: F,
1276 to_future_hack: ToFut,
1277 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close>
1278 where
1279 Host::Counterpart: HasPeer<Peer>,
1280 F: AsyncFnMut(
1281 Req,
1282 Responder<Req::Response>,
1283 ConnectionTo<Host::Counterpart>,
1284 ) -> Result<T, crate::Error>
1285 + Send,
1286 T: IntoHandled<(Req, Responder<Req::Response>)>,
1287 ToFut: Fn(
1288 &mut F,
1289 Req,
1290 Responder<Req::Response>,
1291 ConnectionTo<Host::Counterpart>,
1292 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1293 + Send
1294 + Sync,
1295 {
1296 let handler = RequestHandler::new(self.host.counterpart(), peer, op, to_future_hack);
1297 self.with_handler(handler)
1298 }
1299
1300 /// Register a handler for JSON-RPC notifications from a specific peer.
1301 ///
1302 /// This is similar to [`on_receive_notification`](Self::on_receive_notification), but allows
1303 /// specifying the source peer explicitly. This is useful when receiving messages
1304 /// from a peer that requires message transformation (e.g., unwrapping `SuccessorNotification`
1305 /// envelopes when receiving from an agent via a proxy).
1306 ///
1307 /// For the common case of receiving from the default counterpart, use
1308 /// [`on_receive_notification`](Self::on_receive_notification) instead.
1309 ///
1310 /// # Ordering
1311 ///
1312 /// This callback runs inside the dispatch loop and blocks further message processing
1313 /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1314 /// ordering guarantees and how to avoid deadlocks.
1315 pub fn on_receive_notification_from<Notif: JsonRpcNotification, Peer: Role, F, T, ToFut>(
1316 self,
1317 peer: Peer,
1318 op: F,
1319 to_future_hack: ToFut,
1320 ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close>
1321 where
1322 Host::Counterpart: HasPeer<Peer>,
1323 F: AsyncFnMut(Notif, ConnectionTo<Host::Counterpart>) -> Result<T, crate::Error> + Send,
1324 T: IntoHandled<(Notif, ConnectionTo<Host::Counterpart>)>,
1325 ToFut: Fn(
1326 &mut F,
1327 Notif,
1328 ConnectionTo<Host::Counterpart>,
1329 ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1330 + Send
1331 + Sync,
1332 {
1333 let handler = NotificationHandler::new(self.host.counterpart(), peer, op, to_future_hack);
1334 self.with_handler(handler)
1335 }
1336
1337 /// Add an MCP server that will be added to all new sessions that are proxied through this connection.
1338 ///
1339 /// Only applicable to proxies.
1340 pub fn with_mcp_server(
1341 self,
1342 mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
1343 ) -> Builder<
1344 Host,
1345 impl HandleDispatchFrom<Host::Counterpart>,
1346 impl RunWithConnectionTo<Host::Counterpart>,
1347 Close,
1348 >
1349 where
1350 Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
1351 {
1352 let (handler, responder) = mcp_server.into_handler_and_responder();
1353 self.with_handler(handler).with_responder(responder)
1354 }
1355
1356 /// Run in server mode with the provided transport.
1357 ///
1358 /// This drives the connection by continuously processing messages from the transport
1359 /// and dispatching them to your registered handlers. The connection will run until:
1360 /// - The transport closes (e.g., EOF on byte streams)
1361 /// - An error occurs
1362 /// - One of your handlers returns an error
1363 ///
1364 /// On clean EOF, messages already accepted by the outgoing queue—including
1365 /// handler responses and close-callback notifications—are drained through
1366 /// the transport sink before this returns `Ok(())`.
1367 ///
1368 /// The transport is responsible for serializing and deserializing [`RawJsonRpcMessage`]
1369 /// values to/from the underlying I/O mechanism (byte streams, channels, etc.).
1370 ///
1371 /// Use this mode when you only need to respond to incoming messages and don't need
1372 /// to initiate your own requests. If you need to send requests to the other side,
1373 /// use [`connect_with`](Self::connect_with) instead.
1374 ///
1375 /// # Example: Byte Stream Transport
1376 ///
1377 /// ```no_run
1378 /// # use agent_client_protocol::UntypedRole;
1379 /// # use agent_client_protocol::{Builder};
1380 /// # use agent_client_protocol::Stdio;
1381 /// # use agent_client_protocol_test::*;
1382 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1383 /// let transport = Stdio::new();
1384 ///
1385 /// UntypedRole.builder()
1386 /// .on_receive_request(async |req: MyRequest, responder, cx| {
1387 /// responder.respond(MyResponse { status: "ok".into() })
1388 /// }, agent_client_protocol::on_receive_request!())
1389 /// .connect_to(transport)
1390 /// .await?;
1391 /// # Ok(())
1392 /// # }
1393 /// ```
1394 pub async fn connect_to(
1395 self,
1396 transport: impl ConnectTo<Host> + 'static,
1397 ) -> Result<(), crate::Error> {
1398 self.connect_with(transport, async move |cx| {
1399 cx.incoming_closed().await;
1400 cx.drain_outgoing().await
1401 })
1402 .await
1403 }
1404
1405 /// Run the connection until the provided closure completes.
1406 ///
1407 /// This drives the connection by:
1408 /// 1. Running your registered handlers in the background to process incoming messages
1409 /// 2. Executing your `main_fn` closure with a [`ConnectionTo<R>`] for sending requests/notifications
1410 ///
1411 /// The connection stays active until your `main_fn` returns, then shuts down.
1412 /// Clean incoming EOF fails every pending request and makes future
1413 /// requests fail immediately. It does not cancel unrelated work in
1414 /// `main_fn`: that future may observe [`ConnectionTo::incoming_closed`], or
1415 /// the builder can use [`on_close`](Self::on_close) to notify it or return
1416 /// an error and stop it.
1417 ///
1418 /// Use this mode when you need to initiate communication (send requests/notifications)
1419 /// in addition to responding to incoming messages. For server-only mode where you just
1420 /// respond to messages, use [`connect_to`](Self::connect_to) instead.
1421 ///
1422 /// # Example
1423 ///
1424 /// ```no_run
1425 /// # use agent_client_protocol::UntypedRole;
1426 /// # use agent_client_protocol::{Builder};
1427 /// # use agent_client_protocol::ByteStreams;
1428 /// # use agent_client_protocol::schema::v1::InitializeRequest;
1429 /// # use agent_client_protocol::Stdio;
1430 /// # use agent_client_protocol_test::*;
1431 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1432 /// let transport = Stdio::new();
1433 ///
1434 /// UntypedRole.builder()
1435 /// .on_receive_request(async |req: MyRequest, responder, cx| {
1436 /// // Handle incoming requests in the background
1437 /// responder.respond(MyResponse { status: "ok".into() })
1438 /// }, agent_client_protocol::on_receive_request!())
1439 /// .connect_with(transport, async |cx| {
1440 /// // Initialize the protocol
1441 /// let init_response = cx.send_request(InitializeRequest::make())
1442 /// .block_task()
1443 /// .await?;
1444 ///
1445 /// // Send more requests...
1446 /// let result = cx.send_request(MyRequest {})
1447 /// .block_task()
1448 /// .await?;
1449 ///
1450 /// // When this closure returns, the connection shuts down
1451 /// Ok(())
1452 /// })
1453 /// .await?;
1454 /// # Ok(())
1455 /// # }
1456 /// ```
1457 ///
1458 /// # Parameters
1459 ///
1460 /// - `main_fn`: Your client logic. Receives a [`ConnectionTo<R>`] for sending messages.
1461 ///
1462 /// # Errors
1463 ///
1464 /// Returns an error if a handler, background task, transport, or close
1465 /// callback fails, or if `main_fn` returns an error. Clean incoming EOF is
1466 /// observable through [`ConnectionTo::incoming_closed`] and is not itself
1467 /// an error in this mode.
1468 pub async fn connect_with<R>(
1469 self,
1470 transport: impl ConnectTo<Host> + 'static,
1471 main_fn: impl AsyncFnOnce(ConnectionTo<Host::Counterpart>) -> Result<R, crate::Error>,
1472 ) -> Result<R, crate::Error> {
1473 let (_, future) = self.into_connection_and_future(transport, main_fn);
1474 future.await
1475 }
1476
1477 /// Helper that returns a [`ConnectionTo<R>`] and a future that runs this connection until `main_fn` returns.
1478 fn into_connection_and_future<R>(
1479 self,
1480 transport: impl ConnectTo<Host> + 'static,
1481 main_fn: impl AsyncFnOnce(ConnectionTo<Host::Counterpart>) -> Result<R, crate::Error>,
1482 ) -> (
1483 ConnectionTo<Host::Counterpart>,
1484 impl Future<Output = Result<R, crate::Error>>,
1485 ) {
1486 let Self {
1487 name,
1488 handler,
1489 responder,
1490 host: me,
1491 protocol_mode,
1492 on_close,
1493 } = self;
1494
1495 let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
1496 let (new_task_tx, new_task_rx) = mpsc::unbounded();
1497 let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
1498 let pending_replies = PendingReplies::default();
1499
1500 // Convert transport into server - this returns a channel for us to use
1501 // and a future that runs the transport.
1502 let transport_component = crate::DynConnectTo::new(transport);
1503 let (transport_channel, transport_future) = transport_component.into_channel_and_future();
1504 let (transport_completion_tx, transport_completion_rx) = oneshot::channel();
1505 let transport_completion = transport_completion_rx
1506 .map(|result| {
1507 result.unwrap_or_else(|error| {
1508 Err(crate::util::internal_error(format!(
1509 "transport task dropped before reporting completion: {error}"
1510 )))
1511 })
1512 })
1513 .boxed()
1514 .shared();
1515
1516 let connection = ConnectionTo::new(
1517 me.counterpart(),
1518 outgoing_tx,
1519 new_task_tx,
1520 dynamic_handler_tx,
1521 transport_completion,
1522 pending_replies.registrar(),
1523 );
1524 let spawn_result = connection.spawn(async move {
1525 let result = transport_future.await;
1526 drop(transport_completion_tx.send(result.clone()));
1527 result
1528 });
1529
1530 // Destructure the channel endpoints
1531 let Channel {
1532 rx: transport_incoming_rx,
1533 tx: transport_outgoing_tx,
1534 } = transport_channel;
1535
1536 let protocol_compat = ProtocolCompat::new(protocol_mode);
1537
1538 let future = crate::util::instrument_with_connection_name(name, {
1539 let connection = connection.clone();
1540 async move {
1541 let () = spawn_result?;
1542
1543 let background = async {
1544 let incoming = incoming_actor::incoming_protocol_actor(
1545 me.counterpart(),
1546 &connection,
1547 transport_incoming_rx,
1548 dynamic_handler_rx,
1549 pending_replies.clone(),
1550 incoming_actor::IncomingHandlers::new(handler, on_close),
1551 protocol_compat.clone(),
1552 );
1553 let other_actors = async {
1554 futures::try_join!(
1555 // Protocol layer: OutgoingMessage -> RawJsonRpcMessage
1556 outgoing_actor::outgoing_protocol_actor(
1557 outgoing_rx,
1558 pending_replies,
1559 transport_outgoing_tx,
1560 protocol_compat,
1561 ),
1562 task_actor::task_actor(new_task_rx, &connection),
1563 responder.run_with_connection_to(connection.clone()),
1564 )?;
1565 Ok(())
1566 };
1567
1568 // EOF can wake a pending request consumer, which may make
1569 // the task actor fail while close callbacks are running.
1570 // Keep the incoming actor alive until those callbacks have
1571 // all finished, just as we do when the foreground wakes.
1572 run_until_connection_close(
1573 incoming,
1574 other_actors,
1575 connection.incoming_closed.clone(),
1576 )
1577 .await
1578 };
1579
1580 run_until_connection_close(
1581 background,
1582 main_fn(connection.clone()),
1583 connection.incoming_closed.clone(),
1584 )
1585 .await
1586 }
1587 });
1588
1589 (connection, future)
1590 }
1591}
1592
1593impl<R, H, Run, Close> ConnectTo<R::Counterpart> for Builder<R, H, Run, Close>
1594where
1595 R: Role,
1596 H: HandleDispatchFrom<R::Counterpart> + 'static,
1597 Run: RunWithConnectionTo<R::Counterpart> + 'static,
1598 Close: HandleConnectionClose<R::Counterpart> + 'static,
1599{
1600 async fn connect_to(self, client: impl ConnectTo<R>) -> Result<(), crate::Error> {
1601 Builder::connect_to(self, client).await
1602 }
1603}
1604
1605/// The payload sent through the response oneshot channel.
1606///
1607/// Includes the response value and an optional ack channel for dispatch loop
1608/// synchronization.
1609pub(crate) struct ResponsePayload {
1610 /// The response result - either the JSON value or an error.
1611 pub(crate) result: Result<serde_json::Value, crate::Error>,
1612
1613 /// Optional acknowledgment channel for dispatch loop synchronization.
1614 ///
1615 /// When present, the receiver must send on this channel to signal that
1616 /// response processing is complete, allowing the dispatch loop to continue
1617 /// to the next message.
1618 ///
1619 /// This is `None` for error paths where the response is sent directly
1620 /// (e.g., when the outgoing channel is broken) rather than through the
1621 /// normal dispatch loop flow.
1622 pub(crate) ack_tx: Option<oneshot::Sender<()>>,
1623}
1624
1625impl std::fmt::Debug for ResponsePayload {
1626 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1627 f.debug_struct("ResponsePayload")
1628 .field("result", &self.result)
1629 .field("ack_tx", &self.ack_tx.as_ref().map(|_| "..."))
1630 .finish()
1631 }
1632}
1633
1634struct PendingReply {
1635 method: String,
1636 role_id: RoleId,
1637 sender: oneshot::Sender<ResponsePayload>,
1638 cancellation_disarm: SentRequestCancellationDisarm,
1639}
1640
1641impl PendingReply {
1642 fn fail(self, error: crate::Error) {
1643 self.cancellation_disarm.disarm();
1644 if self
1645 .sender
1646 .send(ResponsePayload {
1647 result: Err(error),
1648 ack_tx: None,
1649 })
1650 .is_err()
1651 {
1652 tracing::trace!(method = %self.method, "Pending request was already dropped");
1653 }
1654 }
1655
1656 fn fail_incoming_closed(self) {
1657 let error = incoming_transport_closed_error(&self.method);
1658 self.fail(error);
1659 }
1660}
1661
1662#[derive(Default)]
1663struct PendingRepliesInner {
1664 incoming_closed: bool,
1665 replies: HashMap<RequestId, PendingReply>,
1666}
1667
1668#[derive(Clone, Default)]
1669struct PendingReplies {
1670 inner: Arc<Mutex<PendingRepliesInner>>,
1671}
1672
1673impl PendingReplies {
1674 fn registrar(&self) -> PendingRepliesRegistrar {
1675 PendingRepliesRegistrar {
1676 inner: Arc::downgrade(&self.inner),
1677 }
1678 }
1679
1680 fn contains(&self, id: &RequestId) -> bool {
1681 self.inner
1682 .lock()
1683 .expect("pending replies mutex poisoned")
1684 .replies
1685 .contains_key(id)
1686 }
1687
1688 fn remove(&self, id: &RequestId) -> Option<PendingReply> {
1689 self.inner
1690 .lock()
1691 .expect("pending replies mutex poisoned")
1692 .replies
1693 .remove(id)
1694 }
1695
1696 /// Atomically reject new subscriptions and fail every existing one.
1697 fn close_incoming(&self) -> usize {
1698 let replies = {
1699 let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
1700 inner.incoming_closed = true;
1701 std::mem::take(&mut inner.replies)
1702 };
1703 let count = replies.len();
1704 for (_, reply) in replies {
1705 reply.fail_incoming_closed();
1706 }
1707 count
1708 }
1709}
1710
1711/// A non-owning handle used to register a request before it enters the
1712/// outgoing queue. Keeping this weak prevents escaped [`ConnectionTo`] clones
1713/// from extending the lifetime of response senders after the driver stops.
1714#[derive(Clone)]
1715struct PendingRepliesRegistrar {
1716 inner: Weak<Mutex<PendingRepliesInner>>,
1717}
1718
1719impl PendingRepliesRegistrar {
1720 /// Register a response destination before the request becomes observable.
1721 ///
1722 /// Returns `false` after failing `reply` when EOF has already made a
1723 /// response impossible or the connection driver is no longer running.
1724 fn subscribe(
1725 &self,
1726 id: RequestId,
1727 reply: PendingReply,
1728 incoming_closed: &IncomingClosed,
1729 ) -> bool {
1730 let Some(inner) = self.inner.upgrade() else {
1731 if incoming_closed.is_closing() {
1732 reply.fail_incoming_closed();
1733 } else {
1734 let method = reply.method.clone();
1735 reply.fail(crate::util::internal_error(format!(
1736 "failed to send outgoing request `{method}`: connection is no longer running"
1737 )));
1738 }
1739 return false;
1740 };
1741
1742 let result = {
1743 let mut inner = inner.lock().expect("pending replies mutex poisoned");
1744 if inner.incoming_closed {
1745 Err(reply)
1746 } else {
1747 Ok(inner.replies.insert(id, reply))
1748 }
1749 };
1750
1751 match result {
1752 Err(rejected) => {
1753 rejected.fail_incoming_closed();
1754 false
1755 }
1756 Ok(replaced) => {
1757 if let Some(replaced) = replaced {
1758 replaced.fail(
1759 crate::Error::internal_error()
1760 .data("outgoing request ID was reused before its response arrived"),
1761 );
1762 }
1763 true
1764 }
1765 }
1766 }
1767
1768 fn remove(&self, id: &RequestId) -> Option<PendingReply> {
1769 self.inner
1770 .upgrade()?
1771 .lock()
1772 .expect("pending replies mutex poisoned")
1773 .replies
1774 .remove(id)
1775 }
1776}
1777
1778impl Debug for PendingRepliesRegistrar {
1779 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1780 formatter
1781 .debug_struct("PendingRepliesRegistrar")
1782 .field("is_connected", &(self.inner.strong_count() > 0))
1783 .finish()
1784 }
1785}
1786
1787/// A request-local marker that is set when the peer asks to cancel the request.
1788///
1789/// Request handlers can get this handle from [`Responder::cancellation`] and
1790/// use it from spawned work to stop long-running request processing
1791/// cooperatively.
1792#[derive(Clone)]
1793pub struct RequestCancellation {
1794 state: Arc<RequestCancellationState>,
1795}
1796
1797struct RequestCancellationState {
1798 cancelled: AtomicBool,
1799 signal_tx: Mutex<Option<oneshot::Sender<()>>>,
1800 signal_rx: future::Shared<BoxFuture<'static, ()>>,
1801}
1802
1803impl RequestCancellation {
1804 fn new() -> Self {
1805 let (signal_tx, signal_rx) = oneshot::channel();
1806 let signal_rx = signal_rx.map(|_| ()).boxed().shared();
1807 Self {
1808 state: Arc::new(RequestCancellationState {
1809 cancelled: AtomicBool::new(false),
1810 signal_tx: Mutex::new(Some(signal_tx)),
1811 signal_rx,
1812 }),
1813 }
1814 }
1815
1816 /// Wait until the peer sends `$/cancel_request` for this request.
1817 ///
1818 /// If cancellation was already requested, this returns immediately.
1819 pub async fn cancelled(&self) {
1820 self.state.signal_rx.clone().await;
1821 }
1822
1823 /// Run request work until it completes or the peer asks to cancel it.
1824 ///
1825 /// If cancellation is requested first, this returns
1826 /// [`Error::request_cancelled`]. This is a convenience for request handlers
1827 /// that want to respond with the normal result or the standard
1828 /// cancellation error.
1829 ///
1830 /// When cancellation wins, `future` is dropped: work stops at its next
1831 /// await point, partial results are lost, and any cleanup must happen in
1832 /// `Drop` implementations. Handlers that need to flush partial results or
1833 /// run async cleanup should instead watch [`cancelled`](Self::cancelled)
1834 /// or poll [`is_cancelled`](Self::is_cancelled) from inside the work.
1835 ///
1836 /// [`Error::request_cancelled`]: crate::Error::request_cancelled
1837 pub async fn run_until_cancelled<T>(
1838 &self,
1839 future: impl std::future::Future<Output = Result<T, crate::Error>>,
1840 ) -> Result<T, crate::Error> {
1841 if self.is_cancelled() {
1842 return Err(crate::Error::request_cancelled());
1843 }
1844
1845 match future::select(pin!(future), pin!(self.cancelled())).await {
1846 Either::Left((result, _)) => result,
1847 Either::Right(((), _)) => Err(crate::Error::request_cancelled()),
1848 }
1849 }
1850
1851 /// Returns whether the peer has already requested cancellation.
1852 #[must_use]
1853 pub fn is_cancelled(&self) -> bool {
1854 self.state.cancelled.load(Ordering::Acquire)
1855 }
1856
1857 fn cancel(&self) {
1858 if self.state.cancelled.swap(true, Ordering::AcqRel) {
1859 return;
1860 }
1861
1862 let signal_tx = self
1863 .state
1864 .signal_tx
1865 .lock()
1866 .expect("request cancellation signal mutex poisoned")
1867 .take();
1868
1869 // Complete the oneshot outside the lock: it wakes waiters, and
1870 // arbitrary waker code must not observe the lock held.
1871 if let Some(signal_tx) = signal_tx {
1872 let _ = signal_tx.send(());
1873 }
1874 }
1875}
1876
1877impl Debug for RequestCancellation {
1878 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1879 formatter
1880 .debug_struct("RequestCancellation")
1881 .field("is_cancelled", &self.is_cancelled())
1882 .finish_non_exhaustive()
1883 }
1884}
1885
1886/// Per-request cancellation state tracked by [`RequestCancellationRegistry`].
1887///
1888/// The full [`RequestCancellation`] marker (with its wakeup machinery) is only
1889/// allocated once a handler asks for it via [`Responder::cancellation`]; until
1890/// then an incoming `$/cancel_request` just flips the entry to `Cancelled`.
1891/// This keeps the per-request cost of the registry to a single map entry.
1892#[derive(Debug)]
1893enum RequestCancellationEntry {
1894 /// The request is in flight; no marker handed out, no cancellation yet.
1895 Armed,
1896 /// `$/cancel_request` arrived before a marker was handed out.
1897 Cancelled,
1898 /// A marker was handed out via [`Responder::cancellation`].
1899 Marker(RequestCancellation),
1900}
1901
1902/// A registered request's cancellation state, tagged with the generation of
1903/// its registration.
1904///
1905/// The generation distinguishes a registration from earlier ones that used
1906/// the same request ID, so that when a (protocol-violating) peer reuses the
1907/// ID of a request that is still in flight, the stale request's responder can
1908/// neither remove nor observe the cancellation state of the newer request.
1909#[derive(Debug)]
1910struct RequestCancellationSlot {
1911 generation: u64,
1912 entry: RequestCancellationEntry,
1913}
1914
1915#[derive(Debug, Default)]
1916struct RequestCancellationRegistryInner {
1917 slots: HashMap<RequestId, RequestCancellationSlot>,
1918 next_generation: u64,
1919}
1920
1921#[derive(Clone, Debug, Default)]
1922struct RequestCancellationRegistry {
1923 inner: Arc<Mutex<RequestCancellationRegistryInner>>,
1924}
1925
1926#[derive(Debug)]
1927struct ResponderCancellation {
1928 id: RequestId,
1929 generation: u64,
1930 registry: RequestCancellationRegistry,
1931}
1932
1933impl RequestCancellationRegistry {
1934 fn new() -> Self {
1935 Self::default()
1936 }
1937
1938 fn register(&self, id: &RequestId) -> ResponderCancellation {
1939 let generation = {
1940 let mut inner = self
1941 .inner
1942 .lock()
1943 .expect("request cancellation registry mutex poisoned");
1944 let generation = inner.next_generation;
1945 inner.next_generation += 1;
1946 if inner
1947 .slots
1948 .insert(
1949 id.clone(),
1950 RequestCancellationSlot {
1951 generation,
1952 entry: RequestCancellationEntry::Armed,
1953 },
1954 )
1955 .is_some()
1956 {
1957 tracing::debug!(
1958 ?id,
1959 "peer reused the ID of a request that is still in flight"
1960 );
1961 }
1962 generation
1963 };
1964 ResponderCancellation {
1965 id: id.clone(),
1966 generation,
1967 registry: self.clone(),
1968 }
1969 }
1970
1971 /// Get the cancellation marker for a registered request, creating it on
1972 /// first use. Repeated calls return markers that share the same state.
1973 ///
1974 /// Exception: when the registration is stale (a protocol-violating peer
1975 /// reused this request ID and the slot now belongs to a newer request, or
1976 /// was already removed by it), every call returns a fresh *detached*
1977 /// marker. Detached markers can never fire, and detached markers from
1978 /// repeated calls do not share state with each other.
1979 fn marker(&self, id: &RequestId, generation: u64) -> RequestCancellation {
1980 let mut inner = self
1981 .inner
1982 .lock()
1983 .expect("request cancellation registry mutex poisoned");
1984 let Some(slot) = inner.slots.get_mut(id) else {
1985 // The slot lives as long as the responder that owns it, so this
1986 // is only reachable if the peer reused this request ID and the
1987 // newer request's responder already removed the replacement slot.
1988 // Hand out a detached marker rather than panicking.
1989 return RequestCancellation::new();
1990 };
1991 if slot.generation != generation {
1992 // The peer reused this request ID while the request was still in
1993 // flight, and the slot now belongs to the newer request. Hand the
1994 // stale responder a detached marker instead of cross-wiring the
1995 // two requests' cancellation states.
1996 return RequestCancellation::new();
1997 }
1998 let entry = &mut slot.entry;
1999 match entry {
2000 RequestCancellationEntry::Marker(marker) => marker.clone(),
2001 RequestCancellationEntry::Armed => {
2002 let marker = RequestCancellation::new();
2003 *entry = RequestCancellationEntry::Marker(marker.clone());
2004 marker
2005 }
2006 RequestCancellationEntry::Cancelled => {
2007 // No one can be waiting on a marker that did not exist yet,
2008 // so firing it while holding the registry lock is fine.
2009 let marker = RequestCancellation::new();
2010 marker.cancel();
2011 *entry = RequestCancellationEntry::Marker(marker.clone());
2012 marker
2013 }
2014 }
2015 }
2016
2017 fn cancel_if_requested(&self, dispatch: &Dispatch) -> Result<bool, crate::Error> {
2018 let Some(request_id) = cancellation_request_id(dispatch)? else {
2019 return Ok(false);
2020 };
2021 Ok(self.cancel(&request_id))
2022 }
2023
2024 /// Mark whichever request currently owns `request_id` as cancelled.
2025 fn cancel(&self, request_id: &RequestId) -> bool {
2026 let marker = {
2027 let mut inner = self
2028 .inner
2029 .lock()
2030 .expect("request cancellation registry mutex poisoned");
2031 let Some(slot) = inner.slots.get_mut(request_id) else {
2032 return false;
2033 };
2034 let entry = &mut slot.entry;
2035 match entry {
2036 RequestCancellationEntry::Marker(marker) => marker.clone(),
2037 RequestCancellationEntry::Cancelled => return true,
2038 RequestCancellationEntry::Armed => {
2039 *entry = RequestCancellationEntry::Cancelled;
2040 return true;
2041 }
2042 }
2043 };
2044
2045 // Fire the marker outside the registry lock: waking waiters runs
2046 // arbitrary waker code that must not observe the lock held.
2047 marker.cancel();
2048 true
2049 }
2050
2051 /// Remove the slot for `request_id`, but only if it still belongs to the
2052 /// registration identified by `generation`.
2053 fn remove(&self, request_id: &RequestId, generation: u64) {
2054 let mut inner = self
2055 .inner
2056 .lock()
2057 .expect("request cancellation registry mutex poisoned");
2058 if inner
2059 .slots
2060 .get(request_id)
2061 .is_some_and(|slot| slot.generation == generation)
2062 {
2063 inner.slots.remove(request_id);
2064 }
2065 }
2066}
2067
2068impl ResponderCancellation {
2069 fn cancellation(&self) -> RequestCancellation {
2070 self.registry.marker(&self.id, self.generation)
2071 }
2072}
2073
2074impl Drop for ResponderCancellation {
2075 fn drop(&mut self) {
2076 self.registry.remove(&self.id, self.generation);
2077 }
2078}
2079
2080fn cancellation_request_id(dispatch: &Dispatch) -> Result<Option<RequestId>, crate::Error> {
2081 let Dispatch::Notification(message) = dispatch else {
2082 return Ok(None);
2083 };
2084 cancellation_request_id_from_message(message)
2085}
2086
2087fn cancellation_request_id_from_message(
2088 message: &UntypedMessage,
2089) -> Result<Option<RequestId>, crate::Error> {
2090 let (method, params) = peel_successor_envelopes(&message.method, &message.params);
2091 if !crate::schema::v1::CancelRequestNotification::matches_method(method) {
2092 return Ok(None);
2093 }
2094
2095 let notification = crate::schema::v1::CancelRequestNotification::parse_message(method, params)?;
2096 Ok(Some(notification.request_id))
2097}
2098
2099/// Peel any [`SuccessorMessage`] envelopes off a notification by reference,
2100/// returning the innermost method and params.
2101///
2102/// This only peeks at the envelope's `method`/`params` fields instead of
2103/// deserializing the envelope, for two reasons:
2104///
2105/// - It avoids deep-cloning the params of every wrapped notification on the
2106/// hot dispatch path just to inspect the inner method name.
2107/// - It is deliberately lenient: a malformed envelope is left as-is here and
2108/// flows on to the handler chain, which is responsible for reporting it.
2109///
2110/// [`SuccessorMessage`]: crate::schema::SuccessorMessage
2111fn peel_successor_envelopes<'message>(
2112 mut method: &'message str,
2113 mut params: &'message serde_json::Value,
2114) -> (&'message str, &'message serde_json::Value) {
2115 while crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
2116 let Some(inner_method) = params.get("method").and_then(serde_json::Value::as_str) else {
2117 break;
2118 };
2119 method = inner_method;
2120 params = params.get("params").unwrap_or(&serde_json::Value::Null);
2121 }
2122 (method, params)
2123}
2124
2125/// Whether a notification is a `$/cancel_request`, even when it is still
2126/// wrapped in `_proxy/successor` envelopes.
2127///
2128/// `$/cancel_request` is connection-scoped: its `requestId` was allocated on
2129/// the connection the notification arrived over and means nothing on any
2130/// other connection. Generic forwarding code (such as
2131/// [`ConnectionTo::send_proxied_message_to`]) uses this check to drop the raw
2132/// notification instead of tunneling it across a hop; the cancellation still
2133/// propagates because [`forward_response_to`](SentRequest::forward_response_to)
2134/// re-issues it with the forwarded request's own ID.
2135///
2136/// Checking a notification whose method is not the successor envelope is a
2137/// plain method-name comparison. Only successor-wrapped notifications pay for
2138/// a serialization to peel the envelope.
2139#[must_use]
2140pub fn is_cancel_request_notification<N: JsonRpcNotification>(notification: &N) -> bool {
2141 let method = notification.method();
2142 if crate::schema::v1::CancelRequestNotification::matches_method(method) {
2143 return true;
2144 }
2145 if !crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
2146 return false;
2147 }
2148
2149 match notification.to_untyped_message() {
2150 Ok(untyped) => {
2151 let (method, _params) = peel_successor_envelopes(&untyped.method, &untyped.params);
2152 crate::schema::v1::CancelRequestNotification::matches_method(method)
2153 }
2154 Err(error) => {
2155 tracing::debug!(
2156 ?error,
2157 "failed to inspect successor-wrapped notification for cancellation"
2158 );
2159 false
2160 }
2161 }
2162}
2163
2164/// Messages send to be serialized over the transport.
2165#[derive(Debug)]
2166enum OutgoingMessage {
2167 /// Close the outgoing application queue and acknowledge after every
2168 /// already-accepted message has entered the raw transport queue.
2169 CloseAfterDraining { done: oneshot::Sender<()> },
2170
2171 /// Send a request to the server.
2172 Request {
2173 /// id assigned to this request (generated by sender)
2174 id: RequestId,
2175
2176 /// the original method
2177 method: String,
2178
2179 /// the message to send; this may have a distinct method
2180 /// depending on the peer
2181 untyped: UntypedMessage,
2182 },
2183
2184 /// Send a notification to the server.
2185 Notification {
2186 /// the message to send; this may have a distinct method
2187 /// depending on the peer
2188 untyped: UntypedMessage,
2189 },
2190
2191 /// Send a response to a message from the server
2192 Response {
2193 id: RequestId,
2194
2195 /// Method of the incoming request this response completes.
2196 method: String,
2197
2198 response: Result<serde_json::Value, crate::Error>,
2199 },
2200
2201 /// Send a generalized error message
2202 Error { error: crate::Error },
2203}
2204
2205/// Return type from JrHandler; indicates whether the request was handled or not.
2206#[must_use]
2207#[derive(Debug)]
2208pub enum Handled<T> {
2209 /// The message was handled
2210 Yes,
2211
2212 /// The message was not handled; returns the original value.
2213 ///
2214 /// If `retry` is true,
2215 No {
2216 /// The message to be passed to subsequent handlers
2217 /// (typically the original message, but it may have been
2218 /// mutated.)
2219 message: T,
2220
2221 /// If true, request the message to be queued and retried with
2222 /// dynamic handlers as they are added.
2223 ///
2224 /// This is used for managing session updates since the dynamic
2225 /// handler for a session cannot be added until the response to the
2226 /// new session request has been processed and there may be updates
2227 /// that get processed at the same time.
2228 retry: bool,
2229 },
2230}
2231
2232/// Trait for converting handler return values into [`Handled`].
2233///
2234/// This trait allows handlers to return either `()` (which becomes `Handled::Yes`)
2235/// or an explicit `Handled<T>` value for more control over handler propagation.
2236pub trait IntoHandled<T> {
2237 /// Convert this value into a `Handled<T>`.
2238 fn into_handled(self) -> Handled<T>;
2239}
2240
2241impl<T> IntoHandled<T> for () {
2242 fn into_handled(self) -> Handled<T> {
2243 Handled::Yes
2244 }
2245}
2246
2247impl<T> IntoHandled<T> for Handled<T> {
2248 fn into_handled(self) -> Handled<T> {
2249 self
2250 }
2251}
2252
2253/// Connection context for sending messages and spawning tasks.
2254///
2255/// This is the primary handle for interacting with the JSON-RPC connection from
2256/// within handler callbacks. You can use it to:
2257///
2258/// * Send requests and notifications to the other side
2259/// * Spawn concurrent tasks that run alongside the connection
2260/// * Respond to requests (via [`Responder`] which wraps this)
2261///
2262/// # Cloning
2263///
2264/// `ConnectionTo` is cheaply cloneable - all clones refer to the same underlying connection.
2265/// This makes it easy to share across async tasks.
2266///
2267/// # Event Loop and Concurrency
2268///
2269/// Handler callbacks run on the event loop, which means the connection cannot process new
2270/// messages while your handler is running. Use [`spawn`](Self::spawn) to offload any
2271/// expensive or blocking work to concurrent tasks.
2272///
2273/// See the [Event Loop and Concurrency](Builder#event-loop-and-concurrency) section
2274/// for more details.
2275#[derive(Clone, Debug)]
2276pub struct ConnectionTo<Counterpart: Role> {
2277 counterpart: Counterpart,
2278 message_tx: OutgoingMessageTx,
2279 task_tx: TaskTx,
2280 dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
2281 transport_completion: SharedTransportCompletion,
2282 pending_replies: PendingRepliesRegistrar,
2283 incoming_closed: IncomingClosed,
2284}
2285
2286type SharedTransportCompletion = future::Shared<BoxFuture<'static, Result<(), crate::Error>>>;
2287
2288#[derive(Clone)]
2289struct IncomingClosed {
2290 state: Arc<IncomingClosedState>,
2291}
2292
2293struct IncomingClosedState {
2294 closing: AtomicBool,
2295 closed: AtomicBool,
2296 signal_tx: Mutex<Option<oneshot::Sender<()>>>,
2297 signal_rx: future::Shared<BoxFuture<'static, ()>>,
2298}
2299
2300impl IncomingClosed {
2301 fn new() -> Self {
2302 let (signal_tx, signal_rx) = oneshot::channel();
2303 Self {
2304 state: Arc::new(IncomingClosedState {
2305 closing: AtomicBool::new(false),
2306 closed: AtomicBool::new(false),
2307 signal_tx: Mutex::new(Some(signal_tx)),
2308 signal_rx: signal_rx.map(|_| ()).boxed().shared(),
2309 }),
2310 }
2311 }
2312
2313 fn begin_close(&self) {
2314 self.state.closing.store(true, Ordering::Release);
2315 }
2316
2317 fn finish_close(&self) {
2318 self.state.closed.store(true, Ordering::Release);
2319 let signal_tx = self
2320 .state
2321 .signal_tx
2322 .lock()
2323 .expect("incoming-close signal mutex poisoned")
2324 .take();
2325
2326 if let Some(signal_tx) = signal_tx {
2327 let _ = signal_tx.send(());
2328 }
2329 }
2330
2331 async fn closed(&self) {
2332 self.state.signal_rx.clone().await;
2333 }
2334
2335 fn is_closed(&self) -> bool {
2336 self.state.closed.load(Ordering::Acquire)
2337 }
2338
2339 fn is_closing(&self) -> bool {
2340 self.state.closing.load(Ordering::Acquire)
2341 }
2342}
2343
2344impl Debug for IncomingClosed {
2345 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2346 formatter
2347 .debug_struct("IncomingClosed")
2348 .field("is_closing", &self.is_closing())
2349 .field("is_closed", &self.is_closed())
2350 .finish_non_exhaustive()
2351 }
2352}
2353
2354/// Stable discriminator stored in the `data.reason` field of errors produced
2355/// when the incoming transport reaches clean EOF before a request receives its
2356/// response.
2357pub const INCOMING_TRANSPORT_CLOSED_REASON: &str = "incoming_transport_closed";
2358
2359/// Return whether `error` reports that the incoming transport reached clean
2360/// EOF before a request received its response.
2361#[must_use]
2362pub fn is_incoming_transport_closed(error: &crate::Error) -> bool {
2363 error
2364 .data
2365 .as_ref()
2366 .and_then(|data| data.get("reason"))
2367 .and_then(serde_json::Value::as_str)
2368 == Some(INCOMING_TRANSPORT_CLOSED_REASON)
2369}
2370
2371fn incoming_transport_closed_error(method: &str) -> crate::Error {
2372 let mut error = crate::Error::internal_error();
2373 error.message = "Incoming transport closed".to_string();
2374 error.data(serde_json::json!({
2375 "reason": INCOMING_TRANSPORT_CLOSED_REASON,
2376 "method": method,
2377 }))
2378}
2379
2380/// Run the connection background alongside its foreground while ensuring that
2381/// a foreground woken by incoming EOF cannot cancel close callbacks midway.
2382fn run_until_connection_close<R>(
2383 background: impl Future<Output = Result<(), crate::Error>>,
2384 foreground: impl Future<Output = Result<R, crate::Error>>,
2385 incoming_closed: IncomingClosed,
2386) -> impl Future<Output = Result<R, crate::Error>> {
2387 // Box these before constructing the returned future. Keeping the generic
2388 // connection actors directly in this async state would substantially grow
2389 // every `connect_*` future.
2390 let background = Box::pin(background);
2391 let foreground = Box::pin(foreground);
2392
2393 async move {
2394 match future::select(background, foreground).await {
2395 Either::Left((background_result, foreground)) => {
2396 background_result?;
2397 foreground.await
2398 }
2399 Either::Right((foreground_result, background)) => {
2400 if !incoming_closed.is_closing() {
2401 return foreground_result;
2402 }
2403
2404 match future::select(background, Box::pin(incoming_closed.closed())).await {
2405 Either::Left((background_result, _)) => {
2406 background_result?;
2407 foreground_result
2408 }
2409 Either::Right(((), background)) => {
2410 // Poll the background first once more so an error returned
2411 // by the just-finished close callback wins over the ready
2412 // foreground result.
2413 crate::util::run_until(background, future::ready(foreground_result)).await
2414 }
2415 }
2416 }
2417 }
2418 }
2419}
2420
2421impl<Counterpart: Role> ConnectionTo<Counterpart> {
2422 fn new(
2423 counterpart: Counterpart,
2424 message_tx: mpsc::UnboundedSender<OutgoingMessage>,
2425 task_tx: mpsc::UnboundedSender<Task>,
2426 dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
2427 transport_completion: SharedTransportCompletion,
2428 pending_replies: PendingRepliesRegistrar,
2429 ) -> Self {
2430 Self {
2431 counterpart,
2432 message_tx,
2433 task_tx,
2434 dynamic_handler_tx,
2435 transport_completion,
2436 pending_replies,
2437 incoming_closed: IncomingClosed::new(),
2438 }
2439 }
2440
2441 /// Return the counterpart role this connection is talking to.
2442 pub fn counterpart(&self) -> Counterpart {
2443 self.counterpart.clone()
2444 }
2445
2446 /// Wait until the incoming transport reaches clean EOF.
2447 ///
2448 /// Transport closure means that no more messages or responses can arrive.
2449 /// Pending requests are failed first; this completes after registered
2450 /// [`Builder::on_close`] callbacks finish.
2451 /// It does not automatically cancel the future passed to
2452 /// [`Builder::connect_with`]; use [`Builder::on_close`] when the connection
2453 /// should run application-specific cleanup or terminate that future.
2454 pub async fn incoming_closed(&self) {
2455 self.incoming_closed.closed().await;
2456 }
2457
2458 /// Return whether clean incoming-EOF processing has completed.
2459 ///
2460 /// This remains `false` while [`Builder::on_close`] callbacks are running.
2461 #[must_use]
2462 pub fn is_incoming_closed(&self) -> bool {
2463 self.incoming_closed.is_closed()
2464 }
2465
2466 /// Stop accepting outgoing messages, drain those already accepted through
2467 /// the protocol actor, and wait for the transport sink to finish them.
2468 async fn drain_outgoing(&self) -> Result<(), crate::Error> {
2469 let (done_tx, done_rx) = oneshot::channel();
2470 let marker_result = send_raw_message(
2471 &self.message_tx,
2472 OutgoingMessage::CloseAfterDraining { done: done_tx },
2473 );
2474 let marker_result = match marker_result {
2475 Ok(()) => done_rx.await.map_err(|error| {
2476 crate::util::internal_error(format!(
2477 "outgoing drain marker was dropped before completion: {error}"
2478 ))
2479 }),
2480 Err(error) => Err(error),
2481 };
2482
2483 // The marker only proves that all accepted protocol messages entered
2484 // the raw transport queue. Transport completion is the sink-level
2485 // barrier that proves a backpressured writer finished them.
2486 self.transport_completion.clone().await?;
2487 marker_result
2488 }
2489
2490 fn is_incoming_closing(&self) -> bool {
2491 self.incoming_closed.is_closing()
2492 }
2493
2494 pub(super) fn begin_incoming_close(&self) {
2495 self.incoming_closed.begin_close();
2496 }
2497
2498 pub(super) fn finish_incoming_close(&self) {
2499 self.incoming_closed.finish_close();
2500 }
2501
2502 /// Spawns a task that will run so long as the JSON-RPC connection is being served.
2503 ///
2504 /// This is the primary mechanism for offloading expensive work from handler callbacks
2505 /// to avoid blocking the event loop. Spawned tasks run concurrently with the connection,
2506 /// allowing the server to continue processing messages.
2507 ///
2508 /// # Event Loop
2509 ///
2510 /// Handler callbacks run on the event loop, which cannot process new messages while
2511 /// your handler is running. Use `spawn` for any expensive operations:
2512 ///
2513 /// ```no_run
2514 /// # use agent_client_protocol_test::*;
2515 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
2516 /// # let connection = mock_connection();
2517 /// connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
2518 /// // Clone cx for the spawned task
2519 /// cx.spawn({
2520 /// let connection = cx.clone();
2521 /// async move {
2522 /// let result = expensive_operation(&req.data).await?;
2523 /// connection.send_notification(ProcessComplete { result })?;
2524 /// Ok(())
2525 /// }
2526 /// })?;
2527 ///
2528 /// // Respond immediately
2529 /// responder.respond(ProcessResponse { result: "started".into() })
2530 /// }, agent_client_protocol::on_receive_request!())
2531 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
2532 /// # Ok(())
2533 /// # }
2534 /// ```
2535 ///
2536 /// # Errors
2537 ///
2538 /// If the spawned task returns an error, the entire server will shut down.
2539 #[track_caller]
2540 pub fn spawn(
2541 &self,
2542 task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
2543 ) -> Result<(), crate::Error> {
2544 let location = std::panic::Location::caller();
2545 let task = task.into_future();
2546 Task::new(location, task).spawn(&self.task_tx)
2547 }
2548
2549 /// Spawn a JSON-RPC connection in the background and return a [`ConnectionTo`] for sending messages to it.
2550 ///
2551 /// This is useful for creating multiple connections that communicate with each other,
2552 /// such as implementing proxy patterns or connecting to multiple backend services.
2553 ///
2554 /// # Arguments
2555 ///
2556 /// - `builder`: The connection builder with handlers configured
2557 /// - `transport`: The transport component to connect to
2558 ///
2559 /// # Returns
2560 ///
2561 /// A `ConnectionTo` that you can use to send requests and notifications to the spawned connection.
2562 ///
2563 /// # Example: Proxying to a backend connection
2564 ///
2565 /// ```
2566 /// # use agent_client_protocol::UntypedRole;
2567 /// # use agent_client_protocol::{Builder, ConnectionTo};
2568 /// # use agent_client_protocol_test::*;
2569 /// # async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
2570 /// // Set up a backend connection builder
2571 /// let backend = UntypedRole.builder()
2572 /// .on_receive_request(async |req: MyRequest, responder, _cx| {
2573 /// responder.respond(MyResponse { status: "ok".into() })
2574 /// }, agent_client_protocol::on_receive_request!());
2575 ///
2576 /// // Spawn it and get a context to send requests to it
2577 /// let backend_connection = cx.spawn_connection(backend, MockTransport)?;
2578 ///
2579 /// // Now you can forward requests to the backend
2580 /// let response = backend_connection.send_request(MyRequest {}).block_task().await?;
2581 /// # Ok(())
2582 /// # }
2583 /// ```
2584 #[track_caller]
2585 pub fn spawn_connection<R: Role>(
2586 &self,
2587 builder: Builder<
2588 R,
2589 impl HandleDispatchFrom<R::Counterpart> + 'static,
2590 impl RunWithConnectionTo<R::Counterpart> + 'static,
2591 impl HandleConnectionClose<R::Counterpart> + 'static,
2592 >,
2593 transport: impl ConnectTo<R> + 'static,
2594 ) -> Result<ConnectionTo<R::Counterpart>, crate::Error> {
2595 let (connection, future) =
2596 builder.into_connection_and_future(transport, |_| std::future::pending());
2597 Task::new(std::panic::Location::caller(), future).spawn(&self.task_tx)?;
2598 Ok(connection)
2599 }
2600
2601 /// Send a request/notification and forward the response appropriately.
2602 ///
2603 /// The request context's response type matches the request's response type,
2604 /// enabling type-safe message forwarding.
2605 pub fn send_proxied_message<Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification>(
2606 &self,
2607 message: Dispatch<Req, Notif>,
2608 ) -> Result<(), crate::Error>
2609 where
2610 Counterpart: HasPeer<Counterpart>,
2611 {
2612 self.send_proxied_message_to(self.counterpart(), message)
2613 }
2614
2615 /// Send a request/notification and forward the response appropriately.
2616 ///
2617 /// The request context's response type matches the request's response type,
2618 /// enabling type-safe message forwarding.
2619 ///
2620 /// `$/cancel_request` notifications are *not* forwarded: their `requestId`
2621 /// refers to a request on the connection they arrived over and would be
2622 /// meaningless to `peer`. Cancellation instead propagates hop by hop,
2623 /// because the responders passed to
2624 /// [`forward_response_to`](SentRequest::forward_response_to) observe it
2625 /// and re-issue the cancellation with the forwarded request's own ID.
2626 pub fn send_proxied_message_to<
2627 Peer: Role,
2628 Req: JsonRpcRequest<Response: Send>,
2629 Notif: JsonRpcNotification,
2630 >(
2631 &self,
2632 peer: Peer,
2633 message: Dispatch<Req, Notif>,
2634 ) -> Result<(), crate::Error>
2635 where
2636 Counterpart: HasPeer<Peer>,
2637 {
2638 match message {
2639 Dispatch::Request(request, responder) => self
2640 .send_request_to(peer, request)
2641 .forward_response_to(responder),
2642 Dispatch::Notification(notification) => {
2643 // `$/cancel_request` is connection-scoped: its `requestId` was
2644 // allocated on the connection the notification arrived over
2645 // and means nothing to `peer`. The cancellation has already
2646 // been recorded on this connection's responder markers, and
2647 // `forward_response_to` re-issues it for the forwarded request
2648 // with the correct per-hop ID, so drop the raw notification
2649 // instead of tunneling a meaningless ID across the hop.
2650 if is_cancel_request_notification(¬ification) {
2651 tracing::debug!(
2652 "not forwarding hop-scoped `$/cancel_request` notification across proxy hop"
2653 );
2654 return Ok(());
2655 }
2656 self.send_notification_to(peer, notification)
2657 }
2658 Dispatch::Response(result, router) => {
2659 // Responses are forwarded directly to their destination
2660 router.respond_with_result(result)
2661 }
2662 }
2663 }
2664
2665 /// Send an outgoing request and return a [`SentRequest`] for handling the reply.
2666 ///
2667 /// The returned [`SentRequest`] provides methods for receiving the response without
2668 /// blocking the event loop:
2669 ///
2670 /// * [`on_receiving_result`](SentRequest::on_receiving_result) - Schedule
2671 /// a callback to run when the response arrives (doesn't block the event loop)
2672 /// * [`block_task`](SentRequest::block_task) - Block the current task until the response
2673 /// arrives (only safe in spawned tasks, not in handlers)
2674 ///
2675 /// # Anti-Footgun Design
2676 ///
2677 /// The API intentionally makes it difficult to block on the result directly to prevent
2678 /// the common mistake of blocking the event loop while waiting for a response:
2679 ///
2680 /// ```compile_fail
2681 /// # use agent_client_protocol_test::*;
2682 /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
2683 /// // ❌ This doesn't compile - prevents blocking the event loop
2684 /// let response = cx.send_request(MyRequest {}).await?;
2685 /// # Ok(())
2686 /// # }
2687 /// ```
2688 ///
2689 /// ```no_run
2690 /// # use agent_client_protocol_test::*;
2691 /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
2692 /// // ✅ Option 1: Schedule callback (safe in handlers)
2693 /// cx.send_request(MyRequest {})
2694 /// .on_receiving_result(async |result| {
2695 /// // Handle the response
2696 /// Ok(())
2697 /// })?;
2698 ///
2699 /// // ✅ Option 2: Block in spawned task (safe because task is concurrent)
2700 /// cx.spawn({
2701 /// let cx = cx.clone();
2702 /// async move {
2703 /// let response = cx.send_request(MyRequest {})
2704 /// .block_task()
2705 /// .await?;
2706 /// // Process response...
2707 /// Ok(())
2708 /// }
2709 /// })?;
2710 /// # Ok(())
2711 /// # }
2712 /// ```
2713 /// Send an outgoing request to the default counterpart peer.
2714 ///
2715 /// This is a convenience method that sends to the counterpart role `R`.
2716 /// For explicit control over the target peer, use [`send_request_to`](Self::send_request_to).
2717 pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response>
2718 where
2719 Counterpart: HasPeer<Counterpart>,
2720 {
2721 self.send_request_to(self.counterpart.clone(), request)
2722 }
2723
2724 /// Send an outgoing request to a specific peer.
2725 ///
2726 /// The message will be transformed according to the [`HasPeer`](crate::role::HasPeer)
2727 /// implementation before being sent.
2728 pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
2729 &self,
2730 peer: Peer,
2731 request: Req,
2732 ) -> SentRequest<Req::Response>
2733 where
2734 Counterpart: HasPeer<Peer>,
2735 {
2736 let method = request.method().to_string();
2737 let id = RequestId::Str(uuid::Uuid::new_v4().to_string());
2738 let (response_tx, response_rx) = oneshot::channel();
2739 let role_id = peer.role_id();
2740 let remote_style = self.counterpart.remote_style(peer);
2741 let cancellation =
2742 SentRequestCancellation::new(self.message_tx.clone(), remote_style, id.clone());
2743 if self.is_incoming_closing() {
2744 cancellation.disarm();
2745 drop(response_tx.send(ResponsePayload {
2746 result: Err(incoming_transport_closed_error(&method)),
2747 ack_tx: None,
2748 }));
2749 return SentRequest::new(
2750 id,
2751 method.clone(),
2752 self.task_tx.clone(),
2753 response_rx,
2754 cancellation,
2755 )
2756 .map(move |json| <Req::Response>::from_value(&method, json));
2757 }
2758
2759 match remote_style.transform_outgoing_message(request) {
2760 Ok(untyped) => {
2761 // Register before enqueueing so incoming EOF can fail every
2762 // observable request before close callbacks begin. The
2763 // outgoing actor checks that the registration still exists
2764 // before sending the request.
2765 let pending_reply = PendingReply {
2766 method: method.clone(),
2767 role_id,
2768 sender: response_tx,
2769 cancellation_disarm: cancellation.disarm_handle(),
2770 };
2771
2772 if self
2773 .pending_replies
2774 .subscribe(id.clone(), pending_reply, &self.incoming_closed)
2775 {
2776 let message = OutgoingMessage::Request {
2777 id: id.clone(),
2778 method: method.clone(),
2779 untyped,
2780 };
2781
2782 if let Err(error) = self.message_tx.unbounded_send(message) {
2783 cancellation.disarm();
2784
2785 let OutgoingMessage::Request { id, method, .. } = error.into_inner() else {
2786 unreachable!();
2787 };
2788
2789 if let Some(pending_reply) = self.pending_replies.remove(&id) {
2790 if self.is_incoming_closing() {
2791 pending_reply.fail_incoming_closed();
2792 } else {
2793 pending_reply.fail(crate::util::internal_error(format!(
2794 "failed to send outgoing request `{method}`"
2795 )));
2796 }
2797 }
2798 }
2799 }
2800 }
2801
2802 Err(err) => {
2803 cancellation.disarm();
2804
2805 response_tx
2806 .send(ResponsePayload {
2807 result: Err(crate::util::internal_error(format!(
2808 "failed to create untyped request for `{method}`: {err}"
2809 ))),
2810 ack_tx: None,
2811 })
2812 .unwrap();
2813 }
2814 }
2815
2816 SentRequest::new(
2817 id,
2818 method.clone(),
2819 self.task_tx.clone(),
2820 response_rx,
2821 cancellation,
2822 )
2823 .map(move |json| <Req::Response>::from_value(&method, json))
2824 }
2825
2826 /// Send an outgoing notification to the default counterpart peer (no reply expected).
2827 ///
2828 /// Notifications are fire-and-forget messages that don't have IDs and don't expect responses.
2829 /// This method sends the notification immediately and returns.
2830 ///
2831 /// This is a convenience method that sends to the counterpart role `R`.
2832 /// For explicit control over the target peer, use [`send_notification_to`](Self::send_notification_to).
2833 ///
2834 /// ```no_run
2835 /// # use agent_client_protocol_test::*;
2836 /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::Agent>) -> Result<(), agent_client_protocol::Error> {
2837 /// cx.send_notification(StatusUpdate {
2838 /// message: "Processing...".into(),
2839 /// })?;
2840 /// # Ok(())
2841 /// # }
2842 /// ```
2843 pub fn send_notification<N: JsonRpcNotification>(
2844 &self,
2845 notification: N,
2846 ) -> Result<(), crate::Error>
2847 where
2848 Counterpart: HasPeer<Counterpart>,
2849 {
2850 self.send_notification_to(self.counterpart.clone(), notification)
2851 }
2852
2853 /// Send an outgoing notification to a specific peer (no reply expected).
2854 ///
2855 /// The message will be transformed according to the [`HasPeer`](crate::role::HasPeer)
2856 /// implementation before being sent.
2857 pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
2858 &self,
2859 peer: Peer,
2860 notification: N,
2861 ) -> Result<(), crate::Error>
2862 where
2863 Counterpart: HasPeer<Peer>,
2864 {
2865 let remote_style = self.counterpart.remote_style(peer);
2866 tracing::debug!(
2867 role = std::any::type_name::<Counterpart>(),
2868 peer = std::any::type_name::<Peer>(),
2869 notification_type = std::any::type_name::<N>(),
2870 ?remote_style,
2871 original_method = notification.method(),
2872 "send_notification_to"
2873 );
2874 let transformed = remote_style.transform_outgoing_message(notification)?;
2875 tracing::debug!(
2876 transformed_method = %transformed.method,
2877 "send_notification_to transformed"
2878 );
2879 send_raw_message(
2880 &self.message_tx,
2881 OutgoingMessage::Notification {
2882 untyped: transformed,
2883 },
2884 )
2885 }
2886
2887 /// Send a `$/cancel_request` notification for an arbitrary request ID to
2888 /// the default counterpart peer.
2889 ///
2890 /// Prefer [`SentRequest::cancel`] when you have the request handle: it
2891 /// already knows the correct peer, request ID, and proxy wrapping. Use this
2892 /// low-level method only when implementing custom routing with a request ID
2893 /// that is valid on this connection.
2894 pub fn send_cancel_request(
2895 &self,
2896 request_id: impl Into<crate::schema::v1::RequestId>,
2897 ) -> Result<(), crate::Error>
2898 where
2899 Counterpart: HasPeer<Counterpart>,
2900 {
2901 self.send_cancel_request_to(self.counterpart.clone(), request_id)
2902 }
2903
2904 /// Send a `$/cancel_request` notification for an arbitrary request ID to a
2905 /// specific peer.
2906 ///
2907 /// Prefer [`SentRequest::cancel`] when you have the request handle: it
2908 /// already knows the correct peer, request ID, and proxy wrapping. Use this
2909 /// low-level method only when implementing custom routing with a request ID
2910 /// that is valid on the target peer's connection.
2911 pub fn send_cancel_request_to<Peer: Role>(
2912 &self,
2913 peer: Peer,
2914 request_id: impl Into<crate::schema::v1::RequestId>,
2915 ) -> Result<(), crate::Error>
2916 where
2917 Counterpart: HasPeer<Peer>,
2918 {
2919 self.send_notification_to(
2920 peer,
2921 crate::schema::v1::CancelRequestNotification::new(request_id),
2922 )
2923 }
2924
2925 /// Send an error notification (no reply expected).
2926 pub fn send_error_notification(&self, error: crate::Error) -> Result<(), crate::Error> {
2927 send_raw_message(&self.message_tx, OutgoingMessage::Error { error })
2928 }
2929
2930 /// Register a dynamic message handler, used to intercept messages specific to a particular session
2931 /// or some similar modal thing.
2932 ///
2933 /// Dynamic message handlers are called first for every incoming message.
2934 ///
2935 /// If they decline to handle the message, then the message is passed to the regular registered handlers.
2936 ///
2937 /// The handler will stay registered until the returned registration guard is dropped.
2938 pub fn add_dynamic_handler(
2939 &self,
2940 handler: impl HandleDispatchFrom<Counterpart> + 'static,
2941 ) -> Result<DynamicHandlerRegistration<Counterpart>, crate::Error> {
2942 let uuid = Uuid::new_v4();
2943 self.dynamic_handler_tx
2944 .unbounded_send(DynamicHandlerMessage::AddDynamicHandler(
2945 uuid,
2946 Box::new(handler),
2947 ))
2948 .map_err(crate::util::internal_error)?;
2949
2950 Ok(DynamicHandlerRegistration::new(uuid, self.clone()))
2951 }
2952
2953 fn remove_dynamic_handler(&self, uuid: Uuid) {
2954 // Ignore errors
2955 drop(
2956 self.dynamic_handler_tx
2957 .unbounded_send(DynamicHandlerMessage::RemoveDynamicHandler(uuid)),
2958 );
2959 }
2960}
2961
2962#[derive(Clone, Debug)]
2963pub struct DynamicHandlerRegistration<R: Role> {
2964 uuid: Uuid,
2965 cx: ConnectionTo<R>,
2966}
2967
2968impl<R: Role> DynamicHandlerRegistration<R> {
2969 fn new(uuid: Uuid, cx: ConnectionTo<R>) -> Self {
2970 Self { uuid, cx }
2971 }
2972
2973 /// Prevents the dynamic handler from being removed when dropped.
2974 pub fn run_indefinitely(self) {
2975 std::mem::forget(self);
2976 }
2977}
2978
2979impl<R: Role> Drop for DynamicHandlerRegistration<R> {
2980 fn drop(&mut self) {
2981 self.cx.remove_dynamic_handler(self.uuid);
2982 }
2983}
2984
2985/// The context to respond to an incoming request.
2986///
2987/// This context is provided to request handlers and serves a dual role:
2988///
2989/// 1. **Respond to the request** - Use [`respond`](Self::respond) or
2990/// [`respond_with_result`](Self::respond_with_result) to send the response
2991/// 2. **Send other messages** - Use the [`ConnectionTo`] parameter passed to your
2992/// handler, which provides [`send_request`](`ConnectionTo::send_request`),
2993/// [`send_notification`](`ConnectionTo::send_notification`), and
2994/// [`spawn`](`ConnectionTo::spawn`)
2995///
2996/// # Example
2997///
2998/// ```no_run
2999/// # use agent_client_protocol_test::*;
3000/// # async fn example() -> Result<(), agent_client_protocol::Error> {
3001/// # let connection = mock_connection();
3002/// connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
3003/// // Send a notification while processing
3004/// cx.send_notification(StatusUpdate {
3005/// message: "processing".into(),
3006/// })?;
3007///
3008/// // Do some work...
3009/// let result = process(&req.data)?;
3010///
3011/// // Respond to the request
3012/// responder.respond(ProcessResponse { result })
3013/// }, agent_client_protocol::on_receive_request!())
3014/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
3015/// # Ok(())
3016/// # }
3017/// ```
3018///
3019/// # Event Loop Considerations
3020///
3021/// Like all handlers, request handlers run on the event loop. Use
3022/// [`spawn`](ConnectionTo::spawn) for expensive operations to avoid blocking
3023/// the connection.
3024///
3025/// See the [Event Loop and Concurrency](Builder#event-loop-and-concurrency)
3026/// section for more details.
3027#[must_use]
3028pub struct Responder<T: JsonRpcResponse = serde_json::Value> {
3029 /// The method of the request.
3030 method: String,
3031
3032 /// The `id` of the message we are replying to.
3033 id: RequestId,
3034
3035 /// Request-local cancellation state.
3036 cancellation: ResponderCancellation,
3037
3038 /// Function to send the response to its destination.
3039 ///
3040 /// For incoming requests: serializes to JSON and sends over the wire.
3041 /// For incoming responses: sends to the waiting oneshot channel.
3042 send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
3043}
3044
3045impl<T: JsonRpcResponse> std::fmt::Debug for Responder<T> {
3046 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3047 f.debug_struct("Responder")
3048 .field("method", &self.method)
3049 .field("id", &self.id)
3050 .field("response_type", &std::any::type_name::<T>())
3051 .finish_non_exhaustive()
3052 }
3053}
3054
3055impl Responder<serde_json::Value> {
3056 /// Create a new request context for an incoming request.
3057 ///
3058 /// The response will be serialized to JSON and sent over the wire.
3059 fn new(
3060 message_tx: OutgoingMessageTx,
3061 method: String,
3062 id: RequestId,
3063 cancellation_registry: &RequestCancellationRegistry,
3064 ) -> Self {
3065 let id_clone = id.clone();
3066 let method_clone = method.clone();
3067 let cancellation = cancellation_registry.register(&id);
3068 Self {
3069 method,
3070 id,
3071 cancellation,
3072 send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
3073 send_raw_message(
3074 &message_tx,
3075 OutgoingMessage::Response {
3076 id: id_clone,
3077 method: method_clone,
3078 response,
3079 },
3080 )
3081 }),
3082 }
3083 }
3084
3085 /// Cast this request context to a different response type.
3086 ///
3087 /// The provided type `T` will be serialized to JSON before sending.
3088 pub fn cast<T: JsonRpcResponse>(self) -> Responder<T> {
3089 self.wrap_params(move |method, value| match value {
3090 Ok(value) => T::into_json(value, method),
3091 Err(e) => Err(e),
3092 })
3093 }
3094}
3095
3096impl<T: JsonRpcResponse> Responder<T> {
3097 /// Method of the incoming request
3098 #[must_use]
3099 pub fn method(&self) -> &str {
3100 &self.method
3101 }
3102
3103 /// ID of the incoming request/response as a JSON value
3104 #[must_use]
3105 pub fn id(&self) -> serde_json::Value {
3106 crate::util::id_to_json(&self.id)
3107 }
3108
3109 /// Returns the cancellation marker for this request.
3110 ///
3111 /// The marker is set when the peer sends `$/cancel_request` for this
3112 /// request's JSON-RPC ID. Cancellation is cooperative: handlers should use
3113 /// the marker to stop long-running work and then decide whether to respond
3114 /// with [`Error::request_cancelled`] or partial data.
3115 ///
3116 /// [`Error::request_cancelled`]: crate::Error::request_cancelled
3117 #[must_use]
3118 pub fn cancellation(&self) -> RequestCancellation {
3119 self.cancellation.cancellation()
3120 }
3121
3122 /// Convert to a `Responder` that expects a JSON value
3123 /// and which checks (dynamically) that the JSON value it receives
3124 /// can be converted to `T`.
3125 pub fn erase_to_json(self) -> Responder<serde_json::Value> {
3126 self.wrap_params(|method, value| T::from_value(method, value?))
3127 }
3128
3129 /// Return a new Responder with a different method name.
3130 pub fn wrap_method(self, method: String) -> Responder<T> {
3131 Responder {
3132 method,
3133 id: self.id,
3134 cancellation: self.cancellation,
3135 send_fn: self.send_fn,
3136 }
3137 }
3138
3139 /// Return a new Responder that expects a response of type U.
3140 ///
3141 /// `wrap_fn` will be invoked with the method name and the result to transform
3142 /// type `U` into type `T` before sending.
3143 pub fn wrap_params<U: JsonRpcResponse>(
3144 self,
3145 wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
3146 ) -> Responder<U> {
3147 let method = self.method.clone();
3148 Responder {
3149 method: self.method,
3150 id: self.id,
3151 cancellation: self.cancellation,
3152 send_fn: Box::new(move |input: Result<U, crate::Error>| {
3153 let t_value = wrap_fn(&method, input);
3154 (self.send_fn)(t_value)
3155 }),
3156 }
3157 }
3158
3159 /// Respond to the JSON-RPC request with either a value (`Ok`) or an error (`Err`).
3160 pub fn respond_with_result(
3161 self,
3162 response: Result<T, crate::Error>,
3163 ) -> Result<(), crate::Error> {
3164 tracing::debug!(id = ?self.id, "respond called");
3165 (self.send_fn)(response)
3166 }
3167
3168 /// Respond to the JSON-RPC request with a value.
3169 pub fn respond(self, response: T) -> Result<(), crate::Error> {
3170 self.respond_with_result(Ok(response))
3171 }
3172
3173 /// Respond to the JSON-RPC request with an internal error containing a message.
3174 pub fn respond_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
3175 self.respond_with_error(crate::util::internal_error(message))
3176 }
3177
3178 /// Respond to the JSON-RPC request with an error.
3179 pub fn respond_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
3180 tracing::debug!(id = ?self.id, ?error, "respond_with_error called");
3181 self.respond_with_result(Err(error))
3182 }
3183}
3184
3185/// Context for handling an incoming JSON-RPC response.
3186///
3187/// This is the response-side counterpart to [`Responder`]. While `Responder` handles
3188/// incoming requests (where you send a response over the wire), `ResponseRouter` handles
3189/// incoming responses (where you route the response to a local task waiting for it).
3190///
3191/// Both are fundamentally "sinks" that push the message through a `send_fn`, but they
3192/// represent different points in the message lifecycle and carry different metadata.
3193///
3194/// # Drop Behavior
3195///
3196/// Dropping a `ResponseRouter` without responding (for example, from a
3197/// dispatch handler that claims a [`Dispatch::Response`]) discards the
3198/// response: the local awaiter observes the response as never received. The
3199/// request still counts as settled: routing a response this far disarms the
3200/// originating [`SentRequest`]'s drop-time auto-cancellation even if the router
3201/// is never invoked, since the peer has already answered.
3202#[must_use]
3203pub struct ResponseRouter<T: JsonRpcResponse = serde_json::Value> {
3204 /// The method of the original request.
3205 method: String,
3206
3207 /// The `id` of the original request.
3208 id: RequestId,
3209
3210 /// The RoleId to which the original request was sent
3211 /// (and hence from which the reply is expected).
3212 role_id: RoleId,
3213
3214 /// Function to send the response to the waiting task.
3215 send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
3216}
3217
3218impl<T: JsonRpcResponse> std::fmt::Debug for ResponseRouter<T> {
3219 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3220 f.debug_struct("ResponseRouter")
3221 .field("method", &self.method)
3222 .field("id", &self.id)
3223 .field("response_type", &std::any::type_name::<T>())
3224 .finish_non_exhaustive()
3225 }
3226}
3227
3228impl ResponseRouter<serde_json::Value> {
3229 /// Create a new response context for routing a response to a local awaiter.
3230 ///
3231 /// When `respond_with_result` is called, the response is sent through the oneshot
3232 /// channel to the code that originally sent the request. If that receiver was
3233 /// dropped, the response is discarded because there is no local awaiter left.
3234 pub(crate) fn new(
3235 method: String,
3236 id: RequestId,
3237 role_id: RoleId,
3238 sender: oneshot::Sender<ResponsePayload>,
3239 cancellation_disarm: SentRequestCancellationDisarm,
3240 ) -> Self {
3241 let response_method = method.clone();
3242 let response_id = id.clone();
3243 // A response for the request reached this router, so the request is
3244 // settled from the peer's perspective and a `$/cancel_request` could
3245 // only ever be redundant. Disarm immediately so handlers may retain
3246 // the router without leaving auto-cancellation armed.
3247 cancellation_disarm.disarm();
3248 Self {
3249 method,
3250 id,
3251 role_id,
3252 send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
3253 if sender
3254 .send(ResponsePayload {
3255 result: response,
3256 ack_tx: None,
3257 })
3258 .is_err()
3259 {
3260 tracing::debug!(
3261 method = %response_method,
3262 id = ?response_id,
3263 "dropped response because local receiver was gone"
3264 );
3265 }
3266 Ok(())
3267 }),
3268 }
3269 }
3270
3271 /// Cast this response context to a different response type.
3272 ///
3273 /// The provided type `T` will be serialized to JSON before sending.
3274 pub fn cast<T: JsonRpcResponse>(self) -> ResponseRouter<T> {
3275 self.wrap_params(move |method, value| match value {
3276 Ok(value) => T::into_json(value, method),
3277 Err(e) => Err(e),
3278 })
3279 }
3280}
3281
3282impl<T: JsonRpcResponse> ResponseRouter<T> {
3283 /// Method of the original request
3284 #[must_use]
3285 pub fn method(&self) -> &str {
3286 &self.method
3287 }
3288
3289 /// ID of the original request as a JSON value
3290 #[must_use]
3291 pub fn id(&self) -> serde_json::Value {
3292 crate::util::id_to_json(&self.id)
3293 }
3294
3295 /// The peer to which the original request was sent.
3296 ///
3297 /// This is the peer from which we expect to receive the response.
3298 #[must_use]
3299 pub fn role_id(&self) -> RoleId {
3300 self.role_id.clone()
3301 }
3302
3303 /// Convert to a `ResponseRouter` that expects a JSON value
3304 /// and which checks (dynamically) that the JSON value it receives
3305 /// can be converted to `T`.
3306 pub fn erase_to_json(self) -> ResponseRouter<serde_json::Value> {
3307 self.wrap_params(|method, value| T::from_value(method, value?))
3308 }
3309
3310 /// Return a new ResponseRouter that expects a response of type U.
3311 ///
3312 /// `wrap_fn` will be invoked with the method name and the result to transform
3313 /// type `U` into type `T` before sending.
3314 fn wrap_params<U: JsonRpcResponse>(
3315 self,
3316 wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
3317 ) -> ResponseRouter<U> {
3318 let method = self.method.clone();
3319 ResponseRouter {
3320 method: self.method,
3321 id: self.id,
3322 role_id: self.role_id,
3323 send_fn: Box::new(move |input: Result<U, crate::Error>| {
3324 let t_value = wrap_fn(&method, input);
3325 (self.send_fn)(t_value)
3326 }),
3327 }
3328 }
3329
3330 /// Complete the response by sending the result to the waiting task.
3331 pub fn respond_with_result(
3332 self,
3333 response: Result<T, crate::Error>,
3334 ) -> Result<(), crate::Error> {
3335 tracing::debug!(id = ?self.id, "response routed to awaiter");
3336 (self.send_fn)(response)
3337 }
3338
3339 /// Complete the response by sending a value to the waiting task.
3340 pub fn respond(self, response: T) -> Result<(), crate::Error> {
3341 self.respond_with_result(Ok(response))
3342 }
3343
3344 /// Complete the response by sending an internal error to the waiting task.
3345 pub fn respond_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
3346 self.respond_with_error(crate::util::internal_error(message))
3347 }
3348
3349 /// Complete the response by sending an error to the waiting task.
3350 pub fn respond_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
3351 tracing::debug!(id = ?self.id, ?error, "error routed to awaiter");
3352 self.respond_with_result(Err(error))
3353 }
3354}
3355
3356/// Common bounds for any JSON-RPC message.
3357///
3358/// # Derive Macro
3359///
3360/// For simple message types, you can use the `JsonRpcRequest` or `JsonRpcNotification` derive macros
3361/// which will implement both `JsonRpcMessage` and the respective trait. See [`JsonRpcRequest`] and
3362/// [`JsonRpcNotification`] for examples.
3363pub trait JsonRpcMessage: 'static + Debug + Sized + Send + Clone {
3364 /// Check if this message type matches the given method name.
3365 fn matches_method(method: &str) -> bool;
3366
3367 /// The method name for the message.
3368 fn method(&self) -> &str;
3369
3370 /// Convert this message into an untyped message.
3371 fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error>;
3372
3373 /// Parse this type from a method name and parameters.
3374 ///
3375 /// Returns an error if the method doesn't match or deserialization fails.
3376 /// Callers should use `matches_method` first to check if this type handles the method.
3377 fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error>;
3378}
3379
3380/// Defines the "payload" of a successful response to a JSON-RPC request.
3381///
3382/// # Derive Macro
3383///
3384/// Use `#[derive(JsonRpcResponse)]` to automatically implement this trait:
3385///
3386/// ```ignore
3387/// use agent_client_protocol::JsonRpcResponse;
3388/// use serde::{Serialize, Deserialize};
3389///
3390/// #[derive(Debug, Serialize, Deserialize, JsonRpcResponse)]
3391/// #[response(method = "_hello")]
3392/// struct HelloResponse {
3393/// greeting: String,
3394/// }
3395/// ```
3396pub trait JsonRpcResponse: 'static + Debug + Sized + Send + Clone {
3397 /// Convert this message into a JSON value.
3398 fn into_json(self, method: &str) -> Result<serde_json::Value, crate::Error>;
3399
3400 /// Parse a JSON value into the response type.
3401 fn from_value(method: &str, value: serde_json::Value) -> Result<Self, crate::Error>;
3402}
3403
3404impl JsonRpcResponse for serde_json::Value {
3405 fn from_value(_method: &str, value: serde_json::Value) -> Result<Self, crate::Error> {
3406 Ok(value)
3407 }
3408
3409 fn into_json(self, _method: &str) -> Result<serde_json::Value, crate::Error> {
3410 Ok(self)
3411 }
3412}
3413
3414/// A struct that represents a notification (JSON-RPC message that does not expect a response).
3415///
3416/// # Derive Macro
3417///
3418/// Use `#[derive(JsonRpcNotification)]` to automatically implement both `JsonRpcMessage` and `JsonRpcNotification`:
3419///
3420/// ```ignore
3421/// use agent_client_protocol::JsonRpcNotification;
3422/// use serde::{Serialize, Deserialize};
3423///
3424/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
3425/// #[notification(method = "_ping")]
3426/// struct PingNotification {
3427/// timestamp: u64,
3428/// }
3429/// ```
3430pub trait JsonRpcNotification: JsonRpcMessage {}
3431
3432/// A struct that represents a request (JSON-RPC message expecting a response).
3433///
3434/// # Derive Macro
3435///
3436/// Use `#[derive(JsonRpcRequest)]` to automatically implement both `JsonRpcMessage` and `JsonRpcRequest`:
3437///
3438/// ```ignore
3439/// use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse};
3440/// use serde::{Serialize, Deserialize};
3441///
3442/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcRequest)]
3443/// #[request(method = "_hello", response = HelloResponse)]
3444/// struct HelloRequest {
3445/// name: String,
3446/// }
3447///
3448/// #[derive(Debug, Serialize, Deserialize, JsonRpcResponse)]
3449/// struct HelloResponse {
3450/// greeting: String,
3451/// }
3452/// ```
3453pub trait JsonRpcRequest: JsonRpcMessage {
3454 /// The type of data expected in response.
3455 type Response: JsonRpcResponse;
3456}
3457
3458/// An enum capturing an in-flight request or notification.
3459/// In the case of a request, also includes the context used to respond to the request.
3460///
3461/// Type parameters allow specifying the concrete request and notification types.
3462/// By default, both are `UntypedMessage` for dynamic dispatch.
3463/// The request context's response type matches the request's response type.
3464#[derive(Debug)]
3465pub enum Dispatch<Req: JsonRpcRequest = UntypedMessage, Notif: JsonRpcMessage = UntypedMessage> {
3466 /// Incoming request and the context where the response should be sent.
3467 Request(Req, Responder<Req::Response>),
3468
3469 /// Incoming notification.
3470 Notification(Notif),
3471
3472 /// Incoming response to a request we sent.
3473 ///
3474 /// The first field is the response result (success or error from the remote).
3475 /// The second field is the context for forwarding the response to its destination
3476 /// (typically a waiting oneshot channel).
3477 Response(
3478 Result<Req::Response, crate::Error>,
3479 ResponseRouter<Req::Response>,
3480 ),
3481}
3482
3483impl<Req: JsonRpcRequest, Notif: JsonRpcMessage> Dispatch<Req, Notif> {
3484 /// Map the request and notification types to new types.
3485 ///
3486 /// Note: Response variants are passed through unchanged since they don't
3487 /// contain a parseable message payload.
3488 pub fn map<Req1, Notif1>(
3489 self,
3490 map_request: impl FnOnce(Req, Responder<Req::Response>) -> (Req1, Responder<Req1::Response>),
3491 map_notification: impl FnOnce(Notif) -> Notif1,
3492 ) -> Dispatch<Req1, Notif1>
3493 where
3494 Req1: JsonRpcRequest<Response = Req::Response>,
3495 Notif1: JsonRpcMessage,
3496 {
3497 match self {
3498 Dispatch::Request(request, responder) => {
3499 let (new_request, new_responder) = map_request(request, responder);
3500 Dispatch::Request(new_request, new_responder)
3501 }
3502 Dispatch::Notification(notification) => {
3503 let new_notification = map_notification(notification);
3504 Dispatch::Notification(new_notification)
3505 }
3506 Dispatch::Response(result, router) => Dispatch::Response(result, router),
3507 }
3508 }
3509
3510 /// Respond to the message with an error.
3511 ///
3512 /// If this message is a request, this error becomes the reply to the request.
3513 ///
3514 /// If this message is a notification, the error is sent as a notification.
3515 ///
3516 /// If this message is a response, the error is forwarded to the waiting handler.
3517 pub fn respond_with_error<R: Role>(
3518 self,
3519 error: crate::Error,
3520 cx: ConnectionTo<R>,
3521 ) -> Result<(), crate::Error> {
3522 match self {
3523 Dispatch::Request(_, responder) => responder.respond_with_error(error),
3524 Dispatch::Notification(_) => cx.send_error_notification(error),
3525 Dispatch::Response(_, responder) => responder.respond_with_error(error),
3526 }
3527 }
3528
3529 /// Convert to a `Responder` that expects a JSON value
3530 /// and which checks (dynamically) that the JSON value it receives
3531 /// can be converted to `T`.
3532 ///
3533 /// Note: Response variants cannot be erased since their payload is already
3534 /// parsed. This returns an error for Response variants.
3535 pub fn erase_to_json(self) -> Result<Dispatch, crate::Error> {
3536 match self {
3537 Dispatch::Request(response, responder) => Ok(Dispatch::Request(
3538 response.to_untyped_message()?,
3539 responder.erase_to_json(),
3540 )),
3541 Dispatch::Notification(notification) => {
3542 Ok(Dispatch::Notification(notification.to_untyped_message()?))
3543 }
3544 Dispatch::Response(_, _) => Err(crate::util::internal_error(
3545 "cannot erase Response variant to JSON",
3546 )),
3547 }
3548 }
3549
3550 /// Convert the message in self to an untyped message.
3551 ///
3552 /// Note: Response variants don't have an untyped message representation.
3553 /// This returns an error for Response variants.
3554 pub fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
3555 match self {
3556 Dispatch::Request(request, _) => request.to_untyped_message(),
3557 Dispatch::Notification(notification) => notification.to_untyped_message(),
3558 Dispatch::Response(_, _) => Err(crate::util::internal_error(
3559 "Response variant has no untyped message representation",
3560 )),
3561 }
3562 }
3563
3564 /// Convert self to an untyped message context.
3565 ///
3566 /// Note: Response variants cannot be converted. This returns an error for Response variants.
3567 pub fn into_untyped_dispatch(self) -> Result<Dispatch, crate::Error> {
3568 match self {
3569 Dispatch::Request(request, responder) => Ok(Dispatch::Request(
3570 request.to_untyped_message()?,
3571 responder.erase_to_json(),
3572 )),
3573 Dispatch::Notification(notification) => {
3574 Ok(Dispatch::Notification(notification.to_untyped_message()?))
3575 }
3576 Dispatch::Response(_, _) => Err(crate::util::internal_error(
3577 "cannot convert Response variant to untyped message context",
3578 )),
3579 }
3580 }
3581
3582 /// Returns the request ID if this is a request or response, None if notification.
3583 pub fn id(&self) -> Option<serde_json::Value> {
3584 match self {
3585 Dispatch::Request(_, cx) => Some(cx.id()),
3586 Dispatch::Notification(_) => None,
3587 Dispatch::Response(_, cx) => Some(cx.id()),
3588 }
3589 }
3590
3591 /// Returns the method of the message.
3592 ///
3593 /// For requests and notifications, this is the method from the message payload.
3594 /// For responses, this is the method of the original request.
3595 pub fn method(&self) -> &str {
3596 match self {
3597 Dispatch::Request(msg, _) => msg.method(),
3598 Dispatch::Notification(msg) => msg.method(),
3599 Dispatch::Response(_, cx) => cx.method(),
3600 }
3601 }
3602}
3603
3604impl Dispatch {
3605 /// Attempts to parse `self` into a typed message context.
3606 ///
3607 /// # Returns
3608 ///
3609 /// * `Ok(Ok(typed))` if this is a request/notification of the given types
3610 /// * `Ok(Err(self))` if not
3611 /// * `Err` if has the correct method for the given types but parsing fails
3612 #[tracing::instrument(skip(self), fields(Request = ?std::any::type_name::<Req>(), Notif = ?std::any::type_name::<Notif>()), level = "trace", ret)]
3613 pub(crate) fn into_typed_dispatch<Req: JsonRpcRequest, Notif: JsonRpcNotification>(
3614 self,
3615 ) -> Result<Result<Dispatch<Req, Notif>, Dispatch>, crate::Error> {
3616 tracing::debug!(
3617 message = ?self,
3618 "into_typed_dispatch"
3619 );
3620 match self {
3621 Dispatch::Request(message, responder) => {
3622 if Req::matches_method(&message.method) {
3623 match Req::parse_message(&message.method, &message.params) {
3624 Ok(req) => {
3625 tracing::trace!(?req, "parsed ok");
3626 Ok(Ok(Dispatch::Request(req, responder.cast())))
3627 }
3628 Err(err) => {
3629 tracing::trace!(?err, "parse error");
3630 Err(err)
3631 }
3632 }
3633 } else {
3634 tracing::trace!("method doesn't match");
3635 Ok(Err(Dispatch::Request(message, responder)))
3636 }
3637 }
3638
3639 Dispatch::Notification(message) => {
3640 if Notif::matches_method(&message.method) {
3641 match Notif::parse_message(&message.method, &message.params) {
3642 Ok(notif) => {
3643 tracing::trace!(?notif, "parse ok");
3644 Ok(Ok(Dispatch::Notification(notif)))
3645 }
3646 Err(err) => {
3647 tracing::trace!(?err, "parse error");
3648 Err(err)
3649 }
3650 }
3651 } else {
3652 tracing::trace!("method doesn't match");
3653 Ok(Err(Dispatch::Notification(message)))
3654 }
3655 }
3656
3657 Dispatch::Response(result, cx) => {
3658 let method = cx.method();
3659 if Req::matches_method(method) {
3660 // Parse the response result
3661 let typed_result = match result {
3662 Ok(value) => {
3663 match <Req::Response as JsonRpcResponse>::from_value(method, value) {
3664 Ok(parsed) => {
3665 tracing::trace!(?parsed, "parse ok");
3666 Ok(parsed)
3667 }
3668 Err(err) => {
3669 tracing::trace!(?err, "parse error");
3670 return Err(err);
3671 }
3672 }
3673 }
3674 Err(err) => {
3675 tracing::trace!("error, passthrough");
3676 Err(err)
3677 }
3678 };
3679 Ok(Ok(Dispatch::Response(typed_result, cx.cast())))
3680 } else {
3681 tracing::trace!("method doesn't match");
3682 Ok(Err(Dispatch::Response(result, cx)))
3683 }
3684 }
3685 }
3686 }
3687
3688 /// True if this message has a field with the given name.
3689 ///
3690 /// Returns `false` for Response variants.
3691 #[must_use]
3692 pub fn has_field(&self, field_name: &str) -> bool {
3693 self.message()
3694 .and_then(|m| m.params().get(field_name))
3695 .is_some()
3696 }
3697
3698 /// Returns true if this message has a session-id field.
3699 ///
3700 /// Returns `false` for Response variants.
3701 pub(crate) fn has_session_id(&self) -> bool {
3702 self.has_field("sessionId")
3703 }
3704
3705 /// Extract the ACP session-id from this message (if any).
3706 ///
3707 /// Returns `Ok(None)` for Response variants.
3708 pub(crate) fn get_session_id(&self) -> Result<Option<SessionId>, crate::Error> {
3709 let Some(message) = self.message() else {
3710 return Ok(None);
3711 };
3712 let Some(value) = message.params().get("sessionId") else {
3713 return Ok(None);
3714 };
3715 let session_id = serde_json::from_value(value.clone())?;
3716 Ok(Some(session_id))
3717 }
3718
3719 /// Try to parse this as a notification of the given type.
3720 ///
3721 /// # Returns
3722 ///
3723 /// * `Ok(Ok(typed))` if this is a request/notification of the given types
3724 /// * `Ok(Err(self))` if not
3725 /// * `Err` if has the correct method for the given types but parsing fails
3726 pub fn into_notification<N: JsonRpcNotification>(
3727 self,
3728 ) -> Result<Result<N, Dispatch>, crate::Error> {
3729 match self {
3730 Dispatch::Notification(msg) => {
3731 if !N::matches_method(&msg.method) {
3732 return Ok(Err(Dispatch::Notification(msg)));
3733 }
3734 match N::parse_message(&msg.method, &msg.params) {
3735 Ok(n) => Ok(Ok(n)),
3736 Err(err) => Err(err),
3737 }
3738 }
3739 Dispatch::Request(..) | Dispatch::Response(..) => Ok(Err(self)),
3740 }
3741 }
3742
3743 /// Try to parse this as a request of the given type.
3744 ///
3745 /// # Returns
3746 ///
3747 /// * `Ok(Ok(typed))` if this is a request/notification of the given types
3748 /// * `Ok(Err(self))` if not
3749 /// * `Err` if has the correct method for the given types but parsing fails
3750 pub fn into_request<Req: JsonRpcRequest>(
3751 self,
3752 ) -> Result<Result<(Req, Responder<Req::Response>), Dispatch>, crate::Error> {
3753 match self {
3754 Dispatch::Request(msg, responder) => {
3755 if !Req::matches_method(&msg.method) {
3756 return Ok(Err(Dispatch::Request(msg, responder)));
3757 }
3758 match Req::parse_message(&msg.method, &msg.params) {
3759 Ok(req) => Ok(Ok((req, responder.cast()))),
3760 Err(err) => Err(err),
3761 }
3762 }
3763 Dispatch::Notification(..) | Dispatch::Response(..) => Ok(Err(self)),
3764 }
3765 }
3766}
3767
3768impl<M: JsonRpcRequest + JsonRpcNotification> Dispatch<M, M> {
3769 /// Returns the message payload for requests and notifications.
3770 ///
3771 /// Returns `None` for Response variants since they don't contain a message payload.
3772 pub fn message(&self) -> Option<&M> {
3773 match self {
3774 Dispatch::Request(msg, _) | Dispatch::Notification(msg) => Some(msg),
3775 Dispatch::Response(_, _) => None,
3776 }
3777 }
3778
3779 /// Map the request/notification message.
3780 ///
3781 /// Response variants pass through unchanged.
3782 pub(crate) fn try_map_message(
3783 self,
3784 map_message: impl FnOnce(M) -> Result<M, crate::Error>,
3785 ) -> Result<Dispatch<M, M>, crate::Error> {
3786 match self {
3787 Dispatch::Request(request, cx) => Ok(Dispatch::Request(map_message(request)?, cx)),
3788 Dispatch::Notification(notification) => {
3789 Ok(Dispatch::<M, M>::Notification(map_message(notification)?))
3790 }
3791 Dispatch::Response(result, cx) => Ok(Dispatch::Response(result, cx)),
3792 }
3793 }
3794}
3795
3796/// An incoming JSON message without any typing. Can be a request or a notification.
3797#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
3798pub struct UntypedMessage {
3799 /// The JSON-RPC method name
3800 pub method: String,
3801 /// The JSON-RPC parameters as a raw JSON value
3802 pub params: serde_json::Value,
3803}
3804
3805impl UntypedMessage {
3806 /// Returns an untyped message with the given method and parameters.
3807 pub fn new(method: &str, params: impl Serialize) -> Result<Self, crate::Error> {
3808 let params = serde_json::to_value(params)?;
3809 Ok(Self {
3810 method: method.to_string(),
3811 params,
3812 })
3813 }
3814
3815 /// Returns the method name
3816 #[must_use]
3817 pub fn method(&self) -> &str {
3818 &self.method
3819 }
3820
3821 /// Returns the parameters as a JSON value
3822 #[must_use]
3823 pub fn params(&self) -> &serde_json::Value {
3824 &self.params
3825 }
3826
3827 /// Consumes this message and returns the method and params
3828 #[must_use]
3829 pub fn into_parts(self) -> (String, serde_json::Value) {
3830 (self.method, self.params)
3831 }
3832
3833 /// Convert `self` to a raw JSON-RPC message.
3834 pub(crate) fn into_raw_jsonrpc_message(
3835 self,
3836 id: Option<RequestId>,
3837 ) -> Result<RawJsonRpcMessage, crate::Error> {
3838 let Self { method, params } = self;
3839 match id {
3840 Some(id) => RawJsonRpcMessage::request(method, params, id),
3841 None => RawJsonRpcMessage::notification(method, params),
3842 }
3843 }
3844}
3845
3846impl JsonRpcMessage for UntypedMessage {
3847 fn matches_method(_method: &str) -> bool {
3848 // UntypedMessage matches any method - it's the untyped fallback
3849 true
3850 }
3851
3852 fn method(&self) -> &str {
3853 &self.method
3854 }
3855
3856 fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
3857 Ok(self.clone())
3858 }
3859
3860 fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error> {
3861 UntypedMessage::new(method, params)
3862 }
3863}
3864
3865impl JsonRpcRequest for UntypedMessage {
3866 type Response = serde_json::Value;
3867}
3868
3869impl JsonRpcNotification for UntypedMessage {}
3870
3871/// Represents a pending response of type `R` from an outgoing request.
3872///
3873/// Returned by [`ConnectionTo::send_request`], this type provides methods for handling
3874/// the response without blocking the event loop. The API is intentionally designed to make
3875/// it difficult to accidentally block.
3876///
3877/// # Anti-Footgun Design
3878///
3879/// You cannot directly `.await` a `SentRequest`. Instead, you must choose how to handle
3880/// the response:
3881///
3882/// ## Option 1: Schedule a Callback (Safe in Handlers)
3883///
3884/// Use [`on_receiving_result`](Self::on_receiving_result) to schedule a task
3885/// that runs when the response arrives. This doesn't block the event loop:
3886///
3887/// ```no_run
3888/// # use agent_client_protocol_test::*;
3889/// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
3890/// cx.send_request(MyRequest {})
3891/// .on_receiving_result(async |result| {
3892/// match result {
3893/// Ok(response) => {
3894/// // Handle successful response
3895/// Ok(())
3896/// }
3897/// Err(error) => {
3898/// // Handle error
3899/// Err(error)
3900/// }
3901/// }
3902/// })?;
3903/// # Ok(())
3904/// # }
3905/// ```
3906///
3907/// ## Option 2: Block in a Spawned Task (Safe Only in `spawn`)
3908///
3909/// Use [`block_task`](Self::block_task) to block until the response arrives, but **only**
3910/// in a spawned task (never in a handler):
3911///
3912/// ```no_run
3913/// # use agent_client_protocol_test::*;
3914/// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
3915/// // ✅ Safe: Spawned task runs concurrently
3916/// cx.spawn({
3917/// let cx = cx.clone();
3918/// async move {
3919/// let response = cx.send_request(MyRequest {})
3920/// .block_task()
3921/// .await?;
3922/// // Process response...
3923/// Ok(())
3924/// }
3925/// })?;
3926/// # Ok(())
3927/// # }
3928/// ```
3929///
3930/// ```no_run
3931/// # use agent_client_protocol_test::*;
3932/// # async fn example() -> Result<(), agent_client_protocol::Error> {
3933/// # let connection = mock_connection();
3934/// // ❌ NEVER do this in a handler - blocks the event loop!
3935/// connection.on_receive_request(async |req: MyRequest, responder, cx| {
3936/// let response = cx.send_request(MyRequest {})
3937/// .block_task() // This will deadlock!
3938/// .await?;
3939/// responder.respond(response)
3940/// }, agent_client_protocol::on_receive_request!())
3941/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
3942/// # Ok(())
3943/// # }
3944/// ```
3945///
3946/// # Why This Design?
3947///
3948/// If you block the event loop while waiting for a response, the connection cannot process
3949/// the incoming response message, creating a deadlock. This API design prevents that footgun
3950/// by making blocking explicit and encouraging non-blocking patterns.
3951///
3952/// # Drop Behavior
3953///
3954/// By default, dropping a `SentRequest` before the SDK has received the
3955/// response sends a `$/cancel_request` notification asking the peer to cancel
3956/// the request, then discards the response when it arrives. Requests whose
3957/// eventual response should be ignored, but which should keep running on the
3958/// peer, should use [`detach`](Self::detach) instead.
3959///
3960/// # Incoming Transport EOF
3961///
3962/// If the incoming transport reaches clean EOF before the response arrives, every
3963/// consumption mode receives an error with the message `Incoming transport
3964/// closed` and data containing
3965/// `{"reason":"incoming_transport_closed","method":"..."}`. Requests made
3966/// after incoming EOF fail immediately with the same error. Use
3967/// [`is_incoming_transport_closed`] to identify it.
3968#[must_use = "dropping a SentRequest asks the peer to cancel the request and \
3969 discards the response; consume it with `block_task`, \
3970 `on_receiving_result`, `forward_response_to`, or `detach`"]
3971pub struct SentRequest<T> {
3972 id: RequestId,
3973 method: String,
3974 task_tx: TaskTx,
3975 response_rx: oneshot::Receiver<ResponsePayload>,
3976 to_result: Box<dyn Fn(serde_json::Value) -> Result<T, crate::Error> + Send>,
3977 cancellation: SentRequestCancellation,
3978 /// Cancellation markers of other (incoming) requests whose cancellation
3979 /// should be forwarded to this request. See
3980 /// [`forward_cancellation_from`](Self::forward_cancellation_from).
3981 cancellation_sources: Vec<RequestCancellation>,
3982}
3983
3984#[derive(Clone, Debug)]
3985pub(crate) struct SentRequestCancellationDisarm {
3986 armed: Arc<AtomicBool>,
3987}
3988
3989impl SentRequestCancellationDisarm {
3990 fn new() -> Self {
3991 Self {
3992 armed: Arc::new(AtomicBool::new(true)),
3993 }
3994 }
3995
3996 fn disarm(&self) {
3997 self.armed.store(false, Ordering::Release);
3998 }
3999}
4000
4001struct SentRequestCancellation {
4002 message_tx: OutgoingMessageTx,
4003 remote_style: crate::role::RemoteStyle,
4004 request_id: RequestId,
4005 disarm: SentRequestCancellationDisarm,
4006}
4007
4008impl SentRequestCancellation {
4009 fn new(
4010 message_tx: OutgoingMessageTx,
4011 remote_style: crate::role::RemoteStyle,
4012 request_id: RequestId,
4013 ) -> Self {
4014 Self {
4015 message_tx,
4016 remote_style,
4017 request_id,
4018 disarm: SentRequestCancellationDisarm::new(),
4019 }
4020 }
4021
4022 fn disarm(&self) {
4023 self.disarm.disarm();
4024 }
4025
4026 fn disarm_handle(&self) -> SentRequestCancellationDisarm {
4027 self.disarm.clone()
4028 }
4029
4030 fn send(&self) -> Result<(), crate::Error> {
4031 if !self.disarm.armed.swap(false, Ordering::AcqRel) {
4032 return Ok(());
4033 }
4034
4035 // Build the notification lazily: most requests are never cancelled,
4036 // so this avoids serializing a notification per outgoing request.
4037 let untyped = self.remote_style.transform_outgoing_message(
4038 crate::schema::v1::CancelRequestNotification::new(self.request_id.clone()),
4039 )?;
4040
4041 send_raw_message(&self.message_tx, OutgoingMessage::Notification { untyped })
4042 }
4043}
4044
4045impl Drop for SentRequestCancellation {
4046 fn drop(&mut self) {
4047 if let Err(error) = self.send() {
4048 tracing::debug!(?error, "failed to auto-cancel dropped request");
4049 }
4050 }
4051}
4052
4053impl Debug for SentRequestCancellation {
4054 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4055 f.debug_struct("SentRequestCancellation")
4056 .field("request_id", &self.request_id)
4057 .field("remote_style", &self.remote_style)
4058 .field("armed", &self.disarm.armed.load(Ordering::Acquire))
4059 .finish_non_exhaustive()
4060 }
4061}
4062
4063/// Await the response payload for an outgoing request, watching `sources` for
4064/// cancellation of the upstream requests it was registered with.
4065///
4066/// When any source reports cancellation, a `$/cancel_request` is forwarded to
4067/// the outgoing request (at most once, shared with [`SentRequest::cancel`] and
4068/// drop-time auto-cancellation), and the response is *still* awaited: the peer
4069/// always answers, with normal data or a cancellation error.
4070///
4071/// Watching is deliberately bounded by response arrival so that completed
4072/// requests do not leak waiters on markers that will never fire.
4073async fn await_response_forwarding_cancellation(
4074 response_rx: oneshot::Receiver<ResponsePayload>,
4075 cancellation: &SentRequestCancellation,
4076 sources: &[RequestCancellation],
4077) -> Result<ResponsePayload, oneshot::Canceled> {
4078 // Failing to forward the cancellation must not abort the wait: the
4079 // response (normal data or a cancellation error) may still arrive and
4080 // must still be processed.
4081 let forward_cancellation = || {
4082 if let Err(error) = cancellation.send() {
4083 tracing::debug!(
4084 ?error,
4085 "failed to forward cancellation to downstream request"
4086 );
4087 }
4088 };
4089
4090 let response = if sources.is_empty() {
4091 response_rx.await
4092 } else if sources.iter().any(RequestCancellation::is_cancelled) {
4093 forward_cancellation();
4094 response_rx.await
4095 } else {
4096 let cancelled = sources.iter().map(|source| source.state.signal_rx.clone());
4097 match future::select(future::select_all(cancelled), response_rx).await {
4098 Either::Left((_, response_rx)) => {
4099 forward_cancellation();
4100 response_rx.await
4101 }
4102 Either::Right((response, _)) => response,
4103 }
4104 };
4105
4106 cancellation.disarm();
4107 response
4108}
4109
4110impl<T: Debug> Debug for SentRequest<T> {
4111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4112 let mut debug = f.debug_struct("SentRequest");
4113 debug
4114 .field("id", &self.id)
4115 .field("method", &self.method)
4116 .field("task_tx", &self.task_tx)
4117 .field("response_rx", &self.response_rx);
4118 debug
4119 .field("cancellation", &self.cancellation)
4120 .field("cancellation_sources", &self.cancellation_sources);
4121 debug.finish_non_exhaustive()
4122 }
4123}
4124
4125impl SentRequest<serde_json::Value> {
4126 fn new(
4127 id: RequestId,
4128 method: String,
4129 task_tx: mpsc::UnboundedSender<Task>,
4130 response_rx: oneshot::Receiver<ResponsePayload>,
4131 cancellation: SentRequestCancellation,
4132 ) -> Self {
4133 Self {
4134 id,
4135 method,
4136 response_rx,
4137 task_tx,
4138 to_result: Box::new(Ok),
4139 cancellation,
4140 cancellation_sources: Vec::new(),
4141 }
4142 }
4143}
4144
4145impl<T> SentRequest<T> {
4146 /// Detach this request handle without waiting for its response.
4147 ///
4148 /// The response will be discarded when it arrives. This also disarms the
4149 /// drop-time automatic cancellation described in
4150 /// [Drop Behavior](Self#drop-behavior), so use it for requests whose
4151 /// eventual response should be ignored, but which should keep running on
4152 /// the peer. The peer is still expected to answer the JSON-RPC request
4153 /// eventually; use a notification instead when no response is expected at
4154 /// all.
4155 ///
4156 /// To ask the peer to stop the request, call `cancel` instead, or drop the
4157 /// handle while automatic cancellation is armed.
4158 pub fn detach(self) {
4159 self.cancellation.disarm();
4160 }
4161
4162 /// Send a `$/cancel_request` notification for this outgoing request.
4163 ///
4164 /// This uses the same peer and message wrapping that were used to send the
4165 /// original request, so it is the preferred way to cancel a [`SentRequest`]
4166 /// when the request handle is still available.
4167 ///
4168 /// At most one `$/cancel_request` is ever sent per request: the first
4169 /// `cancel` call sends it (and also prevents the drop-time automatic
4170 /// cancellation described in [Drop Behavior](Self#drop-behavior)), while
4171 /// later calls return `Ok(())` without sending anything. Likewise, once
4172 /// the SDK has routed the response to this handle, `cancel` becomes a
4173 /// no-op: there is nothing left to cancel.
4174 ///
4175 /// Errors are only reported by the call that attempts to send the
4176 /// notification.
4177 pub fn cancel(&self) -> Result<(), crate::Error> {
4178 self.cancellation.send()
4179 }
4180
4181 /// Forward cancellation of another request to this one.
4182 ///
4183 /// When the request that `source` belongs to is cancelled by its peer,
4184 /// a `$/cancel_request` for *this* request is sent to its peer, using the
4185 /// same wrapping as the original request. The response is still awaited
4186 /// and delivered as usual (normal data or a cancellation error), so this
4187 /// composes with [`block_task`](Self::block_task) and
4188 /// [`on_receiving_result`](Self::on_receiving_result).
4189 ///
4190 /// This is the building block for proxies that forward a request with
4191 /// custom logic instead of [`forward_response_to`](Self::forward_response_to)
4192 /// (which wires this up automatically from its responder). Without it,
4193 /// custom forwarding *absorbs* cancellation: the upstream marker is still
4194 /// set, but nothing is sent downstream.
4195 ///
4196 /// ```
4197 /// # use agent_client_protocol::{ConnectionTo, Error, Responder, UntypedRole};
4198 /// # use agent_client_protocol_test::{MyRequest, MyResponse};
4199 /// # async fn example(request: MyRequest, responder: Responder<MyResponse>, backend: ConnectionTo<UntypedRole>) -> Result<(), Error> {
4200 /// backend
4201 /// .send_request(request)
4202 /// .forward_cancellation_from(responder.cancellation())
4203 /// .on_receiving_result(async move |result| {
4204 /// // Custom result handling, e.g. bookkeeping or rewriting.
4205 /// responder.respond_with_result(result)
4206 /// })?;
4207 /// # Ok(())
4208 /// # }
4209 /// ```
4210 ///
4211 /// May be called multiple times; cancellation of any registered source
4212 /// triggers the forwarding (at most one `$/cancel_request` is ever sent
4213 /// per request). Sources are observed while the response is being
4214 /// awaited — that is, once the handle is consumed with
4215 /// [`block_task`](Self::block_task),
4216 /// [`on_receiving_result`](Self::on_receiving_result), or
4217 /// [`forward_response_to`](Self::forward_response_to); a source that was
4218 /// already cancelled by then is honored immediately.
4219 pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
4220 self.cancellation_sources.push(source);
4221 self
4222 }
4223}
4224
4225impl<T: JsonRpcResponse> SentRequest<T> {
4226 /// The id of the outgoing request.
4227 #[must_use]
4228 pub fn id(&self) -> serde_json::Value {
4229 crate::util::id_to_json(&self.id)
4230 }
4231
4232 /// The method of the request this is in response to.
4233 #[must_use]
4234 pub fn method(&self) -> &str {
4235 &self.method
4236 }
4237
4238 /// Create a new response that maps the result of the response to a new type.
4239 pub fn map<U>(
4240 self,
4241 map_fn: impl Fn(T) -> Result<U, crate::Error> + 'static + Send,
4242 ) -> SentRequest<U> {
4243 SentRequest {
4244 id: self.id,
4245 method: self.method,
4246 response_rx: self.response_rx,
4247 task_tx: self.task_tx,
4248 to_result: Box::new(move |value| map_fn((self.to_result)(value)?)),
4249 cancellation: self.cancellation,
4250 cancellation_sources: self.cancellation_sources,
4251 }
4252 }
4253
4254 /// Forward the response (success or error) to a request context when it arrives.
4255 ///
4256 /// This is a convenience method for proxying messages between connections. When the
4257 /// response arrives, it will be automatically sent to the provided request context,
4258 /// whether it's a successful response or an error.
4259 ///
4260 /// # Example: Proxying requests
4261 ///
4262 /// ```
4263 /// # use agent_client_protocol::UntypedRole;
4264 /// # use agent_client_protocol::{Builder, ConnectionTo};
4265 /// # use agent_client_protocol_test::*;
4266 /// # async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4267 /// // Set up backend connection builder
4268 /// let backend = UntypedRole.builder()
4269 /// .on_receive_request(async |req: MyRequest, responder, cx| {
4270 /// responder.respond(MyResponse { status: "ok".into() })
4271 /// }, agent_client_protocol::on_receive_request!());
4272 ///
4273 /// // Spawn backend and get a context to send to it
4274 /// let backend_connection = cx.spawn_connection(backend, MockTransport)?;
4275 ///
4276 /// // Set up proxy that forwards requests to backend
4277 /// UntypedRole.builder()
4278 /// .on_receive_request({
4279 /// let backend_connection = backend_connection.clone();
4280 /// async move |req: MyRequest, responder, cx| {
4281 /// // Forward the request to backend and proxy the response back
4282 /// backend_connection.send_request(req)
4283 /// .forward_response_to(responder)?;
4284 /// Ok(())
4285 /// }
4286 /// }, agent_client_protocol::on_receive_request!());
4287 /// # Ok(())
4288 /// # }
4289 /// ```
4290 ///
4291 /// # Type Safety
4292 ///
4293 /// The request context's response type must match the request's response type,
4294 /// ensuring type-safe message forwarding.
4295 ///
4296 /// # When to Use
4297 ///
4298 /// Use this when:
4299 /// - You're implementing a proxy or gateway pattern
4300 /// - You want to forward responses without processing them
4301 /// - The response types match between the outgoing request and incoming request
4302 ///
4303 /// This is equivalent to calling `on_receiving_result` and manually forwarding
4304 /// the result, with two proxy-specific additions:
4305 ///
4306 /// - If the pending response cannot be delivered, the incoming request is
4307 /// answered with an internal error instead of being left unanswered.
4308 /// Known clean incoming EOF is delivered like any other response
4309 /// error; an unexpected response-channel loss is forwarded as an outer
4310 /// consumption error.
4311 /// - When the peer cancels the incoming request, the cancellation is
4312 /// forwarded to the outgoing request, and the downstream response
4313 /// (normal data or a cancellation error) is still forwarded back. This is
4314 /// equivalent to registering the responder's marker with
4315 /// `forward_cancellation_from`.
4316 #[track_caller]
4317 pub fn forward_response_to(self, responder: Responder<T>) -> Result<(), crate::Error>
4318 where
4319 T: Send,
4320 {
4321 let this = self.forward_cancellation_from(responder.cancellation());
4322
4323 this.consume_with(async move |response| {
4324 // An unexpected response-channel loss (outer `Err`) is forwarded
4325 // as an error: the incoming request must not be left unanswered.
4326 responder.respond_with_result(response.unwrap_or_else(Err))
4327 })
4328 }
4329
4330 /// Spawn the response-consumption task shared by
4331 /// [`on_receiving_result`](Self::on_receiving_result) and
4332 /// [`forward_response_to`](Self::forward_response_to).
4333 ///
4334 /// The task awaits the response (forwarding cancellation from registered
4335 /// sources while waiting, converts the payload, and invokes `handle` with
4336 /// the typed result (`Ok(Result<T, _>)`). The dispatch loop's ack, if any,
4337 /// is sent after `handle` completes.
4338 ///
4339 /// Clean incoming EOF is delivered as `Ok(Err(error))`, just like
4340 /// a peer response error, so callback-style consumers still run. If the
4341 /// response channel disappears for another reason, `handle` receives an
4342 /// outer `Err` describing that unexpected loss; there is no ack then.
4343 #[track_caller]
4344 fn consume_with<F>(
4345 self,
4346 handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
4347 ) -> Result<(), crate::Error>
4348 where
4349 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
4350 T: Send,
4351 {
4352 let task_tx = self.task_tx.clone();
4353 let method = self.method;
4354 let response_rx = self.response_rx;
4355 let to_result = self.to_result;
4356 let cancellation = self.cancellation;
4357 let cancellation_sources = self.cancellation_sources;
4358 let location = Location::caller();
4359
4360 Task::new(location, async move {
4361 let response = await_response_forwarding_cancellation(
4362 response_rx,
4363 &cancellation,
4364 &cancellation_sources,
4365 )
4366 .await;
4367
4368 match response {
4369 Ok(ResponsePayload { result, ack_tx }) => {
4370 // Convert the result using to_result for Ok values
4371 let typed_result = match result {
4372 Ok(json_value) => to_result(json_value),
4373 Err(err) => Err(err),
4374 };
4375
4376 let outcome = handle(Ok(typed_result)).await;
4377
4378 // Ack AFTER the handler completes - this is the key
4379 // difference from block_task. The dispatch loop waits for
4380 // this ack.
4381 if let Some(tx) = ack_tx {
4382 let _ = tx.send(());
4383 }
4384
4385 outcome
4386 }
4387 Err(err) => {
4388 handle(Err(crate::util::internal_error(format!(
4389 "response to `{method}` never received: {err}"
4390 ))))
4391 .await
4392 }
4393 }
4394 })
4395 .spawn(&task_tx)
4396 }
4397
4398 /// Block the current task until the response is received.
4399 ///
4400 /// **Warning:** This method blocks the current async task. It is **only safe** to use
4401 /// in spawned tasks created with [`ConnectionTo::spawn`]. Using it directly in a
4402 /// handler callback will deadlock the connection.
4403 ///
4404 /// # Safe Usage (in spawned tasks)
4405 ///
4406 /// ```no_run
4407 /// # use agent_client_protocol_test::*;
4408 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
4409 /// # let connection = mock_connection();
4410 /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
4411 /// // Spawn a task to handle the request
4412 /// cx.spawn({
4413 /// let connection = cx.clone();
4414 /// async move {
4415 /// // Safe: We're in a spawned task, not blocking the event loop
4416 /// let response = connection.send_request(OtherRequest {})
4417 /// .block_task()
4418 /// .await?;
4419 ///
4420 /// // Process the response...
4421 /// Ok(())
4422 /// }
4423 /// })?;
4424 ///
4425 /// // Respond immediately
4426 /// responder.respond(MyResponse { status: "ok".into() })
4427 /// }, agent_client_protocol::on_receive_request!())
4428 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
4429 /// # Ok(())
4430 /// # }
4431 /// ```
4432 ///
4433 /// # Unsafe Usage (in handlers - will deadlock!)
4434 ///
4435 /// ```no_run
4436 /// # use agent_client_protocol_test::*;
4437 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
4438 /// # let connection = mock_connection();
4439 /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
4440 /// // ❌ DEADLOCK: Handler blocks event loop, which can't process the response
4441 /// let response = cx.send_request(OtherRequest {})
4442 /// .block_task()
4443 /// .await?;
4444 ///
4445 /// responder.respond(MyResponse { status: response.value })
4446 /// }, agent_client_protocol::on_receive_request!())
4447 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
4448 /// # Ok(())
4449 /// # }
4450 /// ```
4451 ///
4452 /// # When to Use
4453 ///
4454 /// Use this method when:
4455 /// - You're in a spawned task (via [`ConnectionTo::spawn`])
4456 /// - You need the response value to proceed with your logic
4457 /// - Linear control flow is more natural than callbacks
4458 ///
4459 /// For handler callbacks, use [`on_receiving_result`](Self::on_receiving_result) instead.
4460 pub async fn block_task(self) -> Result<T, crate::Error>
4461 where
4462 T: Send,
4463 {
4464 let response = await_response_forwarding_cancellation(
4465 self.response_rx,
4466 &self.cancellation,
4467 &self.cancellation_sources,
4468 )
4469 .await;
4470
4471 match response {
4472 Ok(ResponsePayload {
4473 result: Ok(json_value),
4474 ack_tx,
4475 }) => {
4476 // Ack immediately - we're in a spawned task, so the dispatch loop
4477 // can continue while we process the value.
4478 if let Some(tx) = ack_tx {
4479 let _ = tx.send(());
4480 }
4481 match (self.to_result)(json_value) {
4482 Ok(value) => Ok(value),
4483 Err(err) => Err(err),
4484 }
4485 }
4486 Ok(ResponsePayload {
4487 result: Err(err),
4488 ack_tx,
4489 }) => {
4490 if let Some(tx) = ack_tx {
4491 let _ = tx.send(());
4492 }
4493 Err(err)
4494 }
4495 Err(err) => Err(crate::util::internal_error(format!(
4496 "response to `{}` never received: {}",
4497 self.method, err
4498 ))),
4499 }
4500 }
4501
4502 /// Schedule an async task to run when a successful response is received.
4503 ///
4504 /// This is a convenience wrapper around [`on_receiving_result`](Self::on_receiving_result)
4505 /// for the common pattern of forwarding errors to a request context while only processing
4506 /// successful responses.
4507 ///
4508 /// # Behavior
4509 ///
4510 /// - If the response is `Ok(value)`, your task receives the value and the request context
4511 /// - If the response is `Err(error)`, the error is automatically sent to `responder`
4512 /// and your task is not called
4513 ///
4514 /// # Example: Chaining requests
4515 ///
4516 /// ```no_run
4517 /// # use agent_client_protocol_test::*;
4518 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
4519 /// # let connection = mock_connection();
4520 /// connection.on_receive_request(async |req: ValidateRequest, responder, cx| {
4521 /// // Send initial request
4522 /// cx.send_request(ValidateRequest { data: req.data.clone() })
4523 /// .on_receiving_ok_result(responder, async |validation, responder| {
4524 /// // Only runs if validation succeeded
4525 /// if validation.is_valid {
4526 /// // Respond to original request
4527 /// responder.respond(ValidateResponse { is_valid: true, error: None })
4528 /// } else {
4529 /// responder.respond_with_error(agent_client_protocol::util::internal_error("validation failed"))
4530 /// }
4531 /// })?;
4532 ///
4533 /// Ok(())
4534 /// }, agent_client_protocol::on_receive_request!())
4535 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
4536 /// # Ok(())
4537 /// # }
4538 /// ```
4539 ///
4540 /// # Ordering
4541 ///
4542 /// Like [`on_receiving_result`](Self::on_receiving_result), the callback blocks the
4543 /// dispatch loop until it completes. See the [`ordering`](crate::concepts::ordering) module
4544 /// for details.
4545 ///
4546 /// # When to Use
4547 ///
4548 /// Use this when:
4549 /// - You need to respond to a request based on another request's result
4550 /// - You want errors to automatically propagate to the request context
4551 /// - You only care about the success case
4552 ///
4553 /// For more control over error handling, use [`on_receiving_result`](Self::on_receiving_result).
4554 #[track_caller]
4555 pub fn on_receiving_ok_result<F>(
4556 self,
4557 responder: Responder<T>,
4558 task: impl FnOnce(T, Responder<T>) -> F + 'static + Send,
4559 ) -> Result<(), crate::Error>
4560 where
4561 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
4562 T: Send,
4563 {
4564 self.on_receiving_result(async move |result| match result {
4565 Ok(value) => task(value, responder).await,
4566 Err(err) => responder.respond_with_error(err),
4567 })
4568 }
4569
4570 /// Schedule an async task to run when the response is received.
4571 ///
4572 /// This is the recommended way to handle responses in handler callbacks, as it doesn't
4573 /// block the event loop. The task will be spawned automatically when the response arrives.
4574 ///
4575 /// # Example: Handle response in callback
4576 ///
4577 /// ```no_run
4578 /// # use agent_client_protocol_test::*;
4579 /// # async fn example() -> Result<(), agent_client_protocol::Error> {
4580 /// # let connection = mock_connection();
4581 /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
4582 /// // Send a request and schedule a callback for the response
4583 /// cx.send_request(QueryRequest { id: 22 })
4584 /// .on_receiving_result({
4585 /// let connection = cx.clone();
4586 /// async move |result| {
4587 /// match result {
4588 /// Ok(response) => {
4589 /// println!("Got response: {:?}", response);
4590 /// // Can send more messages here
4591 /// connection.send_notification(QueryComplete {})?;
4592 /// Ok(())
4593 /// }
4594 /// Err(error) => {
4595 /// eprintln!("Request failed: {}", error);
4596 /// Err(error)
4597 /// }
4598 /// }
4599 /// }
4600 /// })?;
4601 ///
4602 /// // Handler continues immediately without waiting
4603 /// responder.respond(MyResponse { status: "processing".into() })
4604 /// }, agent_client_protocol::on_receive_request!())
4605 /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
4606 /// # Ok(())
4607 /// # }
4608 /// ```
4609 ///
4610 /// # Ordering
4611 ///
4612 /// The callback runs as a spawned task, but the dispatch loop waits for it to complete
4613 /// before processing the next message. This gives you ordering guarantees: no other
4614 /// messages will be processed while your callback runs.
4615 ///
4616 /// This differs from [`block_task`](Self::block_task), which signals completion immediately
4617 /// upon receiving the response (before your code processes it).
4618 ///
4619 /// See the [`ordering`](crate::concepts::ordering) module for details on ordering guarantees
4620 /// and how to avoid deadlocks.
4621 ///
4622 /// # Error Handling
4623 ///
4624 /// If the scheduled task returns `Err`, the entire server will shut down. Make sure to handle
4625 /// errors appropriately within your task.
4626 ///
4627 /// # When to Use
4628 ///
4629 /// Use this method when:
4630 /// - You're in a handler callback (not a spawned task)
4631 /// - You want ordering guarantees (no other messages processed during your callback)
4632 /// - You need to do async work before "releasing" control back to the dispatch loop
4633 ///
4634 /// For spawned tasks where you don't need ordering guarantees, consider [`block_task`](Self::block_task).
4635 #[track_caller]
4636 pub fn on_receiving_result<F>(
4637 self,
4638 task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
4639 ) -> Result<(), crate::Error>
4640 where
4641 F: Future<Output = Result<(), crate::Error>> + 'static + Send,
4642 T: Send,
4643 {
4644 self.consume_with(async move |response| {
4645 match response {
4646 // Run the user's callback on the peer's result.
4647 Ok(result) => task(result).await,
4648 // A response that was never delivered fails the consuming
4649 // task instead of invoking the callback.
4650 Err(err) => Err(err),
4651 }
4652 })
4653 }
4654}
4655
4656// ============================================================================
4657// IntoJrConnectionTransport Implementations
4658// ============================================================================
4659
4660/// A component that communicates over line streams.
4661///
4662/// `Lines` implements the [`ConnectTo`] trait for any pair of line-based streams
4663/// (a `Stream<Item = io::Result<String>>` for incoming and a `Sink<String>` for outgoing),
4664/// handling serialization of JSON-RPC messages to/from newline-delimited JSON.
4665///
4666/// This is a lower-level primitive than [`ByteStreams`] that enables interception and
4667/// transformation of individual lines before they are parsed or after they are serialized.
4668/// This is particularly useful for debugging, logging, or implementing custom line-based
4669/// protocols.
4670///
4671/// # Use Cases
4672///
4673/// - **Line-by-line logging**: Intercept and log each line before parsing
4674/// - **Custom protocols**: Transform lines before/after JSON-RPC processing
4675/// - **Debugging**: Inspect raw message strings
4676/// - **Line filtering**: Skip or modify specific messages
4677///
4678/// Most users should use [`ByteStreams`] instead, which provides a simpler interface
4679/// for byte-based I/O.
4680///
4681/// [`ConnectTo`]: crate::ConnectTo
4682#[derive(Debug)]
4683pub struct Lines<OutgoingSink, IncomingStream> {
4684 /// Outgoing line sink (where we write serialized JSON-RPC messages)
4685 pub outgoing: OutgoingSink,
4686 /// Incoming line stream (where we read and parse JSON-RPC messages)
4687 pub incoming: IncomingStream,
4688}
4689
4690impl<OutgoingSink, IncomingStream> Lines<OutgoingSink, IncomingStream>
4691where
4692 OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
4693 IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
4694{
4695 /// Create a new line stream transport.
4696 pub fn new(outgoing: OutgoingSink, incoming: IncomingStream) -> Self {
4697 Self { outgoing, incoming }
4698 }
4699}
4700
4701impl<OutgoingSink, IncomingStream, R: Role> ConnectTo<R> for Lines<OutgoingSink, IncomingStream>
4702where
4703 OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
4704 IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
4705{
4706 async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
4707 let Self { outgoing, incoming } = self;
4708 let (channel, transport_channel) = Channel::duplex();
4709 let shutdown_tx = channel.tx.clone();
4710 let Channel { rx, tx } = transport_channel;
4711
4712 // Once the client completes successfully, its incoming channel is
4713 // gone. Keep consuming successful messages from the physical read
4714 // half without forwarding them so a full-duplex peer cannot block our
4715 // outgoing sink while it is being drained. Transport errors must still
4716 // fail the connection.
4717 let discard_incoming = Arc::new(AtomicBool::new(false));
4718 let incoming = incoming.filter_map({
4719 let discard_incoming = discard_incoming.clone();
4720 move |item| {
4721 let discard_incoming = discard_incoming.load(Ordering::Acquire);
4722 future::ready((!discard_incoming || item.is_err()).then_some(item))
4723 }
4724 });
4725
4726 let outgoing = transport_actor::transport_outgoing_lines_actor(rx, outgoing)
4727 .boxed()
4728 .shared();
4729 let serve_self = Box::pin({
4730 let outgoing = outgoing.clone();
4731 async move {
4732 futures::try_join!(
4733 outgoing,
4734 transport_actor::transport_incoming_lines_actor(incoming, tx),
4735 )?;
4736 Ok(())
4737 }
4738 });
4739
4740 match futures::future::select(Box::pin(client.connect_to(channel)), serve_self).await {
4741 Either::Left((result, serve_self)) => {
4742 result?;
4743 shutdown_tx.close_channel();
4744 discard_incoming.store(true, Ordering::Release);
4745
4746 // Drive the read half while waiting for the write half, but do
4747 // not require the peer's independent incoming stream to reach
4748 // EOF. If incoming processing finishes successfully first,
4749 // the shared outgoing future still owns and drains the sink.
4750 // A successful `serve_self` result includes its shared
4751 // outgoing clone, while any error must remain authoritative
4752 // instead of being hidden behind the other handle. Poll it
4753 // first so a ready read error wins over clean outgoing
4754 // completion.
4755 match future::select(serve_self, outgoing).await {
4756 Either::Left((result, _)) | Either::Right((result, _)) => result,
4757 }
4758 }
4759 Either::Right((result, _)) => result,
4760 }
4761 }
4762
4763 fn into_channel_and_future(self) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>) {
4764 let Self { outgoing, incoming } = self;
4765
4766 // Create a channel pair for the client to use
4767 let (channel_for_caller, channel_for_lines) = Channel::duplex();
4768
4769 // Create the server future that runs the line stream actors
4770 let server_future = Box::pin(async move {
4771 let Channel { rx, tx } = channel_for_lines;
4772
4773 // Run both actors concurrently
4774 let outgoing_future = transport_actor::transport_outgoing_lines_actor(rx, outgoing);
4775 let incoming_future = transport_actor::transport_incoming_lines_actor(incoming, tx);
4776
4777 // Wait for both to complete
4778 futures::try_join!(outgoing_future, incoming_future)?;
4779
4780 Ok(())
4781 });
4782
4783 (channel_for_caller, server_future)
4784 }
4785}
4786
4787/// A component that communicates over byte streams (stdin/stdout, sockets, pipes, etc.).
4788///
4789/// `ByteStreams` implements the [`ConnectTo`] trait for any pair of `AsyncRead` and `AsyncWrite`
4790/// streams, handling serialization of JSON-RPC messages to/from newline-delimited JSON.
4791/// This is the standard way to communicate with external processes or network connections.
4792///
4793/// # Use Cases
4794///
4795/// - **Stdio communication**: Connect to agents or proxies via stdin/stdout
4796/// - **Network sockets**: TCP, Unix domain sockets, or other stream-based protocols
4797/// - **Named pipes**: Cross-process communication on the same machine
4798/// - **File I/O**: Reading from and writing to file descriptors
4799///
4800/// # Example
4801///
4802/// Connecting to an agent via stdio:
4803///
4804/// ```no_run
4805/// use agent_client_protocol::UntypedRole;
4806/// # use agent_client_protocol::{ByteStreams};
4807/// use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
4808///
4809/// # async fn example() -> Result<(), agent_client_protocol::Error> {
4810/// let component = ByteStreams::new(
4811/// tokio::io::stdout().compat_write(),
4812/// tokio::io::stdin().compat(),
4813/// );
4814///
4815/// // Use as a component in a connection
4816/// agent_client_protocol::UntypedRole.builder()
4817/// .name("my-client")
4818/// .connect_to(component)
4819/// .await?;
4820/// # Ok(())
4821/// # }
4822/// ```
4823///
4824/// [`ConnectTo`]: crate::ConnectTo
4825#[derive(Debug)]
4826pub struct ByteStreams<OB, IB> {
4827 /// Outgoing byte stream (where we write serialized messages)
4828 pub outgoing: OB,
4829 /// Incoming byte stream (where we read and parse messages)
4830 pub incoming: IB,
4831}
4832
4833impl<OB, IB> ByteStreams<OB, IB>
4834where
4835 OB: AsyncWrite + Send + 'static,
4836 IB: AsyncRead + Send + 'static,
4837{
4838 /// Create a new byte stream transport.
4839 pub fn new(outgoing: OB, incoming: IB) -> Self {
4840 Self { outgoing, incoming }
4841 }
4842
4843 fn into_lines(
4844 self,
4845 ) -> Lines<
4846 impl futures::Sink<String, Error = std::io::Error> + Send + 'static,
4847 impl futures::Stream<Item = std::io::Result<String>> + Send + 'static,
4848 > {
4849 use futures::AsyncBufReadExt;
4850 use futures::io::BufReader;
4851 let Self { outgoing, incoming } = self;
4852
4853 let incoming_lines = Box::pin(BufReader::new(incoming).lines());
4854 let outgoing_lines =
4855 futures::sink::unfold(Box::pin(outgoing), async move |mut writer, line: String| {
4856 write_line(&mut writer, line).await?;
4857 Ok::<_, std::io::Error>(writer)
4858 });
4859
4860 Lines::new(outgoing_lines, incoming_lines)
4861 }
4862}
4863
4864pub(crate) async fn write_line<W>(writer: &mut W, line: String) -> std::io::Result<()>
4865where
4866 W: AsyncWrite + Unpin + ?Sized,
4867{
4868 use futures::AsyncWriteExt as _;
4869
4870 let mut bytes = line.into_bytes();
4871 bytes.push(b'\n');
4872 writer.write_all(&bytes).await?;
4873 writer.flush().await
4874}
4875
4876impl<OB, IB, R: Role> ConnectTo<R> for ByteStreams<OB, IB>
4877where
4878 OB: AsyncWrite + Send + 'static,
4879 IB: AsyncRead + Send + 'static,
4880{
4881 async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
4882 ConnectTo::<R>::connect_to(self.into_lines(), client).await
4883 }
4884
4885 fn into_channel_and_future(self) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>) {
4886 ConnectTo::<R>::into_channel_and_future(self.into_lines())
4887 }
4888}
4889
4890/// A channel endpoint representing one side of a bidirectional message channel.
4891///
4892/// `Channel` represents a single endpoint's view of a bidirectional communication channel.
4893/// Each endpoint has:
4894/// - `rx`: A receiver for incoming messages (or errors) from the counterpart
4895/// - `tx`: A sender for outgoing messages (or errors) to the counterpart
4896///
4897/// # Example
4898///
4899/// ```no_run
4900/// # use agent_client_protocol::UntypedRole;
4901/// # use agent_client_protocol::{Channel, Builder};
4902/// # async fn example() -> Result<(), agent_client_protocol::Error> {
4903/// // Create a pair of connected channels
4904/// let (channel_a, channel_b) = Channel::duplex();
4905///
4906/// // Each channel can be used by a different component
4907/// UntypedRole.builder()
4908/// .name("connection-a")
4909/// .connect_to(channel_a)
4910/// .await?;
4911/// # Ok(())
4912/// # }
4913/// ```
4914#[derive(Debug)]
4915pub struct Channel {
4916 /// Receives messages (or errors) from the counterpart.
4917 pub rx: mpsc::UnboundedReceiver<Result<RawJsonRpcMessage, crate::Error>>,
4918 /// Sends messages (or errors) to the counterpart.
4919 pub tx: mpsc::UnboundedSender<Result<RawJsonRpcMessage, crate::Error>>,
4920}
4921
4922impl Channel {
4923 /// Create a pair of connected channel endpoints.
4924 ///
4925 /// Returns two `Channel` instances that are connected to each other:
4926 /// - Messages sent via `channel_a.tx` are received on `channel_b.rx`
4927 /// - Messages sent via `channel_b.tx` are received on `channel_a.rx`
4928 ///
4929 /// # Returns
4930 ///
4931 /// A tuple `(channel_a, channel_b)` of connected channel endpoints.
4932 #[must_use]
4933 pub fn duplex() -> (Self, Self) {
4934 // Create channels: A sends Result<Message> which B receives as Message
4935 let (a_tx, b_rx) = mpsc::unbounded();
4936 let (b_tx, a_rx) = mpsc::unbounded();
4937
4938 let channel_a = Self { rx: a_rx, tx: a_tx };
4939 let channel_b = Self { rx: b_rx, tx: b_tx };
4940
4941 (channel_a, channel_b)
4942 }
4943
4944 /// Copy messages from `rx` to `tx`.
4945 ///
4946 /// # Returns
4947 ///
4948 /// A `Result` indicating success or failure.
4949 pub async fn copy(mut self) -> Result<(), crate::Error> {
4950 while let Some(msg) = self.rx.next().await {
4951 self.tx
4952 .unbounded_send(msg)
4953 .map_err(crate::util::internal_error)?;
4954 }
4955 Ok(())
4956 }
4957}
4958
4959impl<R: Role> ConnectTo<R> for Channel {
4960 async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
4961 let (client_channel, client_serve) = client.into_channel_and_future();
4962
4963 match futures::try_join!(
4964 Channel {
4965 rx: client_channel.rx,
4966 tx: self.tx
4967 }
4968 .copy(),
4969 Channel {
4970 rx: self.rx,
4971 tx: client_channel.tx
4972 }
4973 .copy(),
4974 client_serve
4975 ) {
4976 Ok(((), (), ())) => Ok(()),
4977 Err(err) => Err(err),
4978 }
4979 }
4980
4981 fn into_channel_and_future(self) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>) {
4982 (self, Box::pin(future::ready(Ok(()))))
4983 }
4984}
4985
4986#[cfg(test)]
4987mod tests {
4988 use super::*;
4989
4990 #[tokio::test]
4991 async fn write_line_flushes_buffered_writers() {
4992 let mut writer =
4993 futures::io::BufWriter::with_capacity(4096, futures::io::Cursor::new(Vec::new()));
4994
4995 write_line(&mut writer, "message".into()).await.unwrap();
4996
4997 assert_eq!(writer.into_inner().into_inner(), b"message\n");
4998 }
4999
5000 #[test]
5001 fn peel_successor_envelopes_returns_plain_messages_unchanged() {
5002 let params = serde_json::json!({ "key": "value" });
5003 let (method, peeled) = peel_successor_envelopes("session/update", ¶ms);
5004 assert_eq!(method, "session/update");
5005 assert_eq!(peeled, ¶ms);
5006 }
5007
5008 #[test]
5009 fn peel_successor_envelopes_unwraps_nested_envelopes() {
5010 let params = serde_json::json!({
5011 "method": "_proxy/successor",
5012 "params": {
5013 "method": "$/cancel_request",
5014 "params": { "requestId": "req-1" }
5015 }
5016 });
5017 let (method, peeled) = peel_successor_envelopes("_proxy/successor", ¶ms);
5018 assert_eq!(method, "$/cancel_request");
5019 assert_eq!(peeled, &serde_json::json!({ "requestId": "req-1" }));
5020 }
5021
5022 #[test]
5023 fn peel_successor_envelopes_leaves_malformed_envelopes_intact() {
5024 // No string `method` field: the envelope cannot be peeled, so the
5025 // message is returned as-is for the handler chain to deal with.
5026 let params = serde_json::json!({ "unexpected": true });
5027 let (method, peeled) = peel_successor_envelopes("_proxy/successor", ¶ms);
5028 assert_eq!(method, "_proxy/successor");
5029 assert_eq!(peeled, ¶ms);
5030 }
5031
5032 mod cancel_request {
5033 use super::super::*;
5034
5035 fn notification(method: &str, params: serde_json::Value) -> UntypedMessage {
5036 UntypedMessage::new(method, params).expect("well-formed JSON")
5037 }
5038
5039 #[test]
5040 fn cancellation_request_id_is_extracted_from_wrapped_notifications() {
5041 let message = notification(
5042 "_proxy/successor",
5043 serde_json::json!({
5044 "method": "$/cancel_request",
5045 "params": { "requestId": "req-1" }
5046 }),
5047 );
5048 let request_id = cancellation_request_id_from_message(&message)
5049 .expect("wrapped cancel should parse");
5050 assert_eq!(request_id, Some(RequestId::Str("req-1".into())));
5051 }
5052
5053 #[test]
5054 fn malformed_successor_envelope_is_not_treated_as_cancellation() {
5055 // The envelope cannot be peeled; the message must flow on to the
5056 // handler chain instead of erroring the dispatch.
5057 let message = notification("_proxy/successor", serde_json::json!({ "bogus": true }));
5058 let request_id = cancellation_request_id_from_message(&message)
5059 .expect("malformed envelope should be left to the handler chain");
5060 assert_eq!(request_id, None);
5061 }
5062
5063 #[test]
5064 fn cancel_request_notifications_are_detected_even_when_wrapped() {
5065 let plain = notification("$/cancel_request", serde_json::json!({ "requestId": 1 }));
5066 assert!(is_cancel_request_notification(&plain));
5067
5068 let wrapped = notification(
5069 "_proxy/successor",
5070 serde_json::json!({
5071 "method": "$/cancel_request",
5072 "params": { "requestId": 1 }
5073 }),
5074 );
5075 assert!(is_cancel_request_notification(&wrapped));
5076
5077 let other_wrapped = notification(
5078 "_proxy/successor",
5079 serde_json::json!({
5080 "method": "session/update",
5081 "params": {}
5082 }),
5083 );
5084 assert!(!is_cancel_request_notification(&other_wrapped));
5085
5086 let malformed_envelope =
5087 notification("_proxy/successor", serde_json::json!({ "bogus": true }));
5088 assert!(!is_cancel_request_notification(&malformed_envelope));
5089 }
5090
5091 #[test]
5092 fn malformed_cancel_request_params_error() {
5093 let message = notification(
5094 "$/cancel_request",
5095 serde_json::json!({ "requestId": { "not": "an id" } }),
5096 );
5097 cancellation_request_id_from_message(&message)
5098 .expect_err("malformed cancel params should error");
5099 }
5100
5101 #[test]
5102 fn registry_marks_and_removes_requests() {
5103 let registry = RequestCancellationRegistry::new();
5104 let id = RequestId::Str("req-1".into());
5105
5106 let responder_cancellation = registry.register(&id);
5107 let marker = responder_cancellation.cancellation();
5108 assert!(!marker.is_cancelled());
5109
5110 assert!(registry.cancel(&id));
5111 assert!(marker.is_cancelled());
5112 assert!(responder_cancellation.cancellation().is_cancelled());
5113
5114 drop(responder_cancellation);
5115 assert!(!registry.cancel(&id), "slot should be removed on drop");
5116 }
5117
5118 #[test]
5119 fn reused_request_id_does_not_cross_wire_cancellation_state() {
5120 let registry = RequestCancellationRegistry::new();
5121 let id = RequestId::Str("dup".into());
5122
5123 // A protocol-violating peer reuses an in-flight request ID.
5124 let first = registry.register(&id);
5125 let first_marker = first.cancellation();
5126 let second = registry.register(&id);
5127 let second_marker = second.cancellation();
5128
5129 // A cancellation targets whichever request currently owns the ID.
5130 assert!(registry.cancel(&id));
5131 assert!(second_marker.is_cancelled());
5132 assert!(
5133 !first_marker.is_cancelled(),
5134 "the stale request must not observe the newer request's cancellation"
5135 );
5136
5137 // The stale responder must hand out detached markers, not the
5138 // newer request's marker.
5139 assert!(!first.cancellation().is_cancelled());
5140
5141 // Dropping the stale responder must not remove the newer
5142 // request's slot.
5143 drop(first);
5144 assert!(registry.cancel(&id), "newer slot should still be present");
5145
5146 drop(second);
5147 assert!(!registry.cancel(&id), "slot should be removed on drop");
5148 }
5149 }
5150}