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