Skip to main content

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(&notif)?;
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(&notification) {
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", &params);
5004        assert_eq!(method, "session/update");
5005        assert_eq!(peeled, &params);
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", &params);
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", &params);
5028        assert_eq!(method, "_proxy/successor");
5029        assert_eq!(peeled, &params);
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}