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, 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::marker::PhantomData;
15use std::panic::Location;
16use std::pin::pin;
17use std::sync::{
18    Arc, Mutex, Weak,
19    atomic::{AtomicBool, AtomicU8, Ordering},
20};
21use uuid::Uuid;
22
23use futures::FutureExt;
24use futures::channel::{mpsc, oneshot};
25use futures::future::{self, BoxFuture, Either};
26use futures::{AsyncRead, AsyncWrite, StreamExt};
27
28pub(crate) mod close;
29mod dynamic_handler;
30pub(crate) mod handlers;
31mod incoming_actor;
32mod outgoing_actor;
33#[cfg(test)]
34mod prepared_request_tests;
35mod protocol_compat;
36mod raw_error;
37pub(crate) mod run;
38mod task_actor;
39mod termination;
40mod transport_actor;
41
42use crate::jsonrpc::close::{ChainedClose, CloseCallback};
43pub use crate::jsonrpc::close::{HandleConnectionClose, NullClose};
44use crate::jsonrpc::dynamic_handler::DynamicHandlerMessage;
45pub use crate::jsonrpc::handlers::NullHandler;
46use crate::jsonrpc::handlers::{ChainedHandler, NamedHandler};
47use crate::jsonrpc::handlers::{MessageHandler, NotificationHandler, RequestHandler};
48use crate::jsonrpc::outgoing_actor::{OutgoingMessageTx, send_raw_message};
49#[cfg(feature = "unstable_protocol_v2")]
50pub(crate) use crate::jsonrpc::protocol_compat::normalize_v1_initialize_request;
51#[cfg(feature = "unstable_protocol_v2")]
52pub use crate::jsonrpc::protocol_compat::{
53    InitializationVersionMismatch, InitializeOutcome, SelectedInitializeResponse,
54};
55use crate::jsonrpc::protocol_compat::{ProtocolCompat, ProtocolMode};
56pub use crate::jsonrpc::raw_error::{RawJsonRpcError, RawJsonRpcResponse};
57use crate::jsonrpc::run::SpawnedRun;
58use crate::jsonrpc::run::{ChainRun, NullRun, RunWithConnectionTo};
59use crate::jsonrpc::task_actor::{Task, TaskTx};
60pub use crate::jsonrpc::termination::{
61    CONNECTION_TERMINATED_REASON, ConnectionTermination, is_connection_terminated,
62};
63use crate::jsonrpc::termination::{
64    TerminationSource, TerminationSources, connection_terminated_error,
65};
66#[cfg(feature = "unstable_mcp_over_acp")]
67use crate::mcp_server::McpServer;
68use crate::role::HasPeer;
69use crate::role::Role;
70use crate::{Agent, Client, ConnectTo, Proxy, RoleId};
71
72/// One valid JSON-RPC message carried inside a [`TransportFrame`].
73///
74/// This uses the JSON-RPC envelope types from `agent-client-protocol-schema`
75/// while keeping method params and response errors protocol-neutral at the
76/// transport boundary.
77#[derive(Debug, Clone)]
78pub enum RawJsonRpcMessage {
79    /// A JSON-RPC request with an id and expected response.
80    Request(RpcRequest<RawJsonRpcParams>),
81    /// A JSON-RPC notification without a response.
82    Notification(RpcNotification<RawJsonRpcParams>),
83    /// A JSON-RPC response to a prior request.
84    Response(RawJsonRpcResponse),
85}
86
87/// A JSON-RPC frame exchanged between protocol components and transports.
88///
89/// A frame preserves the boundary between a single JSON-RPC value and a batch.
90/// Malformed wire input is represented explicitly; transport failures are
91/// reported by the future that drives the transport rather than sent through a
92/// [`Channel`].
93#[derive(Clone, Debug)]
94pub enum TransportFrame {
95    /// One valid JSON-RPC message.
96    Single(RawJsonRpcMessage),
97    /// One malformed or invalid wire value retained for relays.
98    Malformed {
99        /// The original wire representation.
100        raw: String,
101        /// The JSON-RPC error associated with the malformed value.
102        error: crate::Error,
103    },
104    /// Entries retained from one non-empty JSON-RPC batch, kept in source order.
105    Batch(TransportBatch),
106}
107
108/// A structurally non-empty JSON-RPC batch retained across framed relays.
109#[derive(Clone, Debug)]
110pub struct TransportBatch {
111    first: TransportBatchEntry,
112    rest: Vec<TransportBatchEntry>,
113}
114
115/// One entry in a [`TransportBatch`].
116#[derive(Clone, Debug)]
117pub enum TransportBatchEntry {
118    /// A valid JSON-RPC message.
119    Message(RawJsonRpcMessage),
120    /// A malformed or invalid JSON-RPC value retained for relays.
121    Malformed {
122        /// The original JSON value.
123        raw: serde_json::Value,
124        /// The JSON-RPC error associated with the malformed value.
125        error: crate::Error,
126    },
127}
128
129pub(crate) fn is_response_only_shape(value: &serde_json::Value) -> bool {
130    value.as_object().is_some_and(|object| {
131        !object.contains_key("method")
132            && (object.contains_key("result") || object.contains_key("error"))
133    })
134}
135
136pub(crate) fn raw_is_response_only_shape(raw: &str) -> bool {
137    serde_json::from_str(raw).is_ok_and(|value| is_response_only_shape(&value))
138}
139
140impl TransportBatchEntry {
141    /// Create a valid batch entry.
142    #[must_use]
143    pub fn message(message: RawJsonRpcMessage) -> Self {
144        Self::Message(message)
145    }
146
147    /// Create a malformed batch entry.
148    #[must_use]
149    pub fn malformed(raw: serde_json::Value, error: crate::Error) -> Self {
150        Self::Malformed { raw, error }
151    }
152
153    #[cfg(test)]
154    fn as_result(&self) -> Result<&RawJsonRpcMessage, &crate::Error> {
155        match self {
156            Self::Message(message) => Ok(message),
157            Self::Malformed { error, .. } => Err(error),
158        }
159    }
160
161    fn message_ref(&self) -> Option<&RawJsonRpcMessage> {
162        match self {
163            Self::Message(message) => Some(message),
164            Self::Malformed { .. } => None,
165        }
166    }
167}
168
169impl Serialize for TransportBatchEntry {
170    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171    where
172        S: serde::Serializer,
173    {
174        match self {
175            Self::Message(message) => message.serialize(serializer),
176            Self::Malformed { raw, .. } => raw.serialize(serializer),
177        }
178    }
179}
180
181impl TransportBatch {
182    /// Create a non-empty batch from entries.
183    ///
184    /// Returns `None` when the iterator is empty.
185    pub fn from_entries(entries: impl IntoIterator<Item = TransportBatchEntry>) -> Option<Self> {
186        let mut entries = entries.into_iter();
187        Some(Self {
188            first: entries.next()?,
189            rest: entries.collect(),
190        })
191    }
192
193    /// Create a non-empty batch from valid messages.
194    ///
195    /// Returns `None` when the iterator is empty.
196    pub fn from_messages(messages: impl IntoIterator<Item = RawJsonRpcMessage>) -> Option<Self> {
197        Self::from_entries(messages.into_iter().map(TransportBatchEntry::message))
198    }
199
200    /// Iterate over entries in source order.
201    pub fn entries(&self) -> impl Iterator<Item = &TransportBatchEntry> {
202        std::iter::once(&self.first).chain(&self.rest)
203    }
204
205    /// Iterate mutably over entries in source order.
206    pub fn entries_mut(&mut self) -> impl Iterator<Item = &mut TransportBatchEntry> {
207        std::iter::once(&mut self.first).chain(&mut self.rest)
208    }
209
210    /// Consume this batch and iterate over its entries in source order.
211    pub fn into_entries(self) -> impl Iterator<Item = TransportBatchEntry> {
212        std::iter::once(self.first).chain(self.rest)
213    }
214
215    /// Return the number of entries in this non-empty batch.
216    #[must_use]
217    pub fn len(&self) -> usize {
218        1 + self.rest.len()
219    }
220
221    /// Return whether this batch is empty.
222    ///
223    /// A `TransportBatch` is structurally non-empty, so this always returns
224    /// `false`.
225    #[must_use]
226    pub const fn is_empty(&self) -> bool {
227        false
228    }
229
230    #[cfg(test)]
231    pub(crate) fn iter_results(
232        &self,
233    ) -> impl Iterator<Item = Result<&RawJsonRpcMessage, &crate::Error>> {
234        self.entries().map(TransportBatchEntry::as_result)
235    }
236
237    fn messages(&self) -> impl Iterator<Item = &RawJsonRpcMessage> {
238        self.entries().filter_map(TransportBatchEntry::message_ref)
239    }
240}
241
242impl Serialize for TransportBatch {
243    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
244    where
245        S: serde::Serializer,
246    {
247        let mut sequence = serializer.serialize_seq(Some(1 + self.rest.len()))?;
248        sequence.serialize_element(&self.first)?;
249        for entry in &self.rest {
250            sequence.serialize_element(entry)?;
251        }
252        sequence.end()
253    }
254}
255
256impl TransportFrame {
257    fn inspect_messages(
258        &self,
259        observer: &mut impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error>,
260    ) -> Result<(), crate::Error> {
261        match self {
262            Self::Single(message) => observer(message),
263            Self::Malformed { .. } => Ok(()),
264            Self::Batch(batch) => {
265                for message in batch.messages() {
266                    observer(message)?;
267                }
268                Ok(())
269            }
270        }
271    }
272}
273
274/// Raw JSON-RPC request or notification parameters.
275///
276/// JSON-RPC params, when present, must be either an array or an object.
277#[derive(Debug, Clone, PartialEq)]
278pub enum RawJsonRpcParams {
279    /// Positional JSON-RPC params.
280    Array(Vec<serde_json::Value>),
281    /// Named JSON-RPC params.
282    Object(serde_json::Map<String, serde_json::Value>),
283}
284
285impl RawJsonRpcParams {
286    /// Convert a JSON value into JSON-RPC params.
287    pub fn from_value(value: serde_json::Value) -> Result<Option<Self>, crate::Error> {
288        match value {
289            serde_json::Value::Null => Ok(None),
290            serde_json::Value::Array(array) => Ok(Some(Self::Array(array))),
291            serde_json::Value::Object(object) => Ok(Some(Self::Object(object))),
292            _ => {
293                Err(crate::Error::invalid_params()
294                    .data("JSON-RPC params must be an object or array"))
295            }
296        }
297    }
298
299    /// Convert params back into a JSON value.
300    #[must_use]
301    pub fn into_value(self) -> serde_json::Value {
302        match self {
303            Self::Array(array) => serde_json::Value::Array(array),
304            Self::Object(object) => serde_json::Value::Object(object),
305        }
306    }
307}
308
309impl Serialize for RawJsonRpcParams {
310    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
311    where
312        S: serde::Serializer,
313    {
314        match self {
315            Self::Array(array) => array.serialize(serializer),
316            Self::Object(object) => object.serialize(serializer),
317        }
318    }
319}
320
321impl<'de> Deserialize<'de> for RawJsonRpcParams {
322    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323    where
324        D: serde::Deserializer<'de>,
325    {
326        let value = serde_json::Value::deserialize(deserializer)?;
327        match value {
328            serde_json::Value::Array(array) => Ok(Self::Array(array)),
329            serde_json::Value::Object(object) => Ok(Self::Object(object)),
330            _ => Err(serde::de::Error::custom(
331                "JSON-RPC params must be an object or array",
332            )),
333        }
334    }
335}
336
337impl RawJsonRpcMessage {
338    /// Build a raw JSON-RPC request message.
339    pub fn request(
340        method: String,
341        params: serde_json::Value,
342        id: RequestId,
343    ) -> Result<Self, crate::Error> {
344        Ok(Self::Request(RpcRequest {
345            id,
346            method: Arc::from(method),
347            params: RawJsonRpcParams::from_value(params)?,
348        }))
349    }
350
351    /// Build a raw JSON-RPC notification message.
352    pub fn notification(method: String, params: serde_json::Value) -> Result<Self, crate::Error> {
353        Ok(Self::Notification(RpcNotification {
354            method: Arc::from(method),
355            params: RawJsonRpcParams::from_value(params)?,
356        }))
357    }
358
359    /// Build a JSON-RPC response from an ACP result.
360    ///
361    /// For other protocols, construct [`RawJsonRpcResponse`] directly so error
362    /// codes and fields are not interpreted as ACP.
363    #[must_use]
364    pub fn response(id: RequestId, response: Result<serde_json::Value, crate::Error>) -> Self {
365        Self::Response(RawJsonRpcResponse::new(
366            id,
367            response.map_err(|error| Box::new(error.into())),
368        ))
369    }
370
371    /// The response id, if this is a response.
372    #[must_use]
373    pub fn response_id(&self) -> Option<&RequestId> {
374        match self {
375            Self::Response(
376                RawJsonRpcResponse::Result { id, .. } | RawJsonRpcResponse::Error { id, .. },
377            ) => Some(id),
378            Self::Request(_) | Self::Notification(_) => None,
379        }
380    }
381}
382
383impl Serialize for RawJsonRpcMessage {
384    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
385    where
386        S: serde::Serializer,
387    {
388        match self {
389            Self::Request(request) => {
390                VersionedJsonRpcMessage::wrap(request.clone()).serialize(serializer)
391            }
392            Self::Notification(notification) => {
393                VersionedJsonRpcMessage::wrap(notification.clone()).serialize(serializer)
394            }
395            Self::Response(response) => {
396                VersionedJsonRpcMessage::wrap(response.clone()).serialize(serializer)
397            }
398        }
399    }
400}
401
402impl<'de> Deserialize<'de> for RawJsonRpcMessage {
403    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
404    where
405        D: serde::Deserializer<'de>,
406    {
407        let value = serde_json::Value::deserialize(deserializer)?;
408        let Some(object) = value.as_object() else {
409            return Err(serde::de::Error::custom("invalid JSON-RPC message"));
410        };
411
412        let has_method = object.contains_key("method");
413        let has_id = object.contains_key("id");
414        let has_result = object.contains_key("result");
415        let has_error = object.contains_key("error");
416
417        if has_method && !has_result && !has_error {
418            if has_id {
419                let request = serde_json::from_value::<
420                    VersionedJsonRpcMessage<RpcRequest<RawJsonRpcParams>>,
421                >(value)
422                .map_err(serde::de::Error::custom)?
423                .into_inner();
424                Ok(Self::Request(request))
425            } else {
426                let notification = serde_json::from_value::<
427                    VersionedJsonRpcMessage<RpcNotification<RawJsonRpcParams>>,
428                >(value)
429                .map_err(serde::de::Error::custom)?
430                .into_inner();
431                Ok(Self::Notification(notification))
432            }
433        } else if !has_method && has_id && has_result != has_error {
434            let response =
435                serde_json::from_value::<VersionedJsonRpcMessage<RawJsonRpcResponse>>(value)
436                    .map_err(serde::de::Error::custom)?
437                    .into_inner();
438            Ok(Self::Response(response))
439        } else {
440            Err(serde::de::Error::custom("invalid JSON-RPC message"))
441        }
442    }
443}
444
445fn params_from_transport(params: Option<RawJsonRpcParams>) -> serde_json::Value {
446    params.map_or(serde_json::Value::Null, RawJsonRpcParams::into_value)
447}
448
449/// Handlers process incoming JSON-RPC messages on a connection.
450///
451/// When messages arrive, they flow through a chain of handlers. Each handler can
452/// either **claim** the message (handle it) or **decline** it (pass to the next handler).
453///
454/// # Message Flow
455///
456/// Messages flow through three layers of handlers in order:
457///
458/// ```text
459/// ┌─────────────────────────────────────────────────────────────────┐
460/// │                     Incoming Message                            │
461/// └─────────────────────────────────────────────────────────────────┘
462///                              │
463///                              ▼
464/// ┌─────────────────────────────────────────────────────────────────┐
465/// │  1. User Handlers (registered via on_receive_request, etc.)     │
466/// │     - Tried in registration order                               │
467/// │     - First handler to return Handled::Yes claims the message   │
468/// └─────────────────────────────────────────────────────────────────┘
469///                              │ Handled::No
470///                              ▼
471/// ┌─────────────────────────────────────────────────────────────────┐
472/// │  2. Dynamic Handlers (added at runtime)                         │
473/// │     - Used for session-specific message handling                │
474/// │     - Added via ConnectionTo::add_dynamic_handler             │
475/// └─────────────────────────────────────────────────────────────────┘
476///                              │ Handled::No
477///                              ▼
478/// ┌─────────────────────────────────────────────────────────────────┐
479/// │  3. Role Default Handler                                        │
480/// │     - Fallback based on the connection's Role                   │
481/// │     - Handles protocol-level messages (e.g., proxy forwarding)  │
482/// └─────────────────────────────────────────────────────────────────┘
483///                              │ Handled::No
484///                              ▼
485/// ┌─────────────────────────────────────────────────────────────────┐
486/// │  Unhandled: requests error, notifications ignored               │
487/// └─────────────────────────────────────────────────────────────────┘
488/// ```
489///
490/// # The `Handled` Return Value
491///
492/// Each handler returns [`Handled`] to indicate whether it processed the message:
493///
494/// - **`Handled::Yes`** - Message was handled. No further handlers are invoked.
495/// - **`Handled::No { message, retry }`** - Message was not handled. The message
496///   (possibly modified) is passed to the next handler in the chain.
497///
498/// For convenience, handlers can return `()` which is equivalent to `Handled::Yes`.
499///
500/// # The Retry Mechanism
501///
502/// The `retry` flag in `Handled::No` controls what happens when no handler claims a message:
503///
504/// - **`retry: false`** (default) - Send a "method not found" error
505///   response immediately for requests, or ignore notifications.
506/// - **`retry: true`** - Queue the message and retry it when new dynamic handlers are added.
507///
508/// This mechanism exists because of a timing issue with sessions: when a `session/new`
509/// response is being processed, the dynamic handler for that session hasn't been registered
510/// yet, but `session/update` notifications for that session may already be arriving.
511/// By setting `retry: true`, these early notifications are queued until the session's
512/// dynamic handler is added.
513///
514/// # Handler Registration
515///
516/// Most users register handlers using the builder methods on [`Builder`]:
517///
518/// ```
519/// # use agent_client_protocol::{Agent, Client, ConnectTo};
520/// # use agent_client_protocol::schema::v1::{AgentCapabilities, InitializeRequest, InitializeResponse};
521/// # use agent_client_protocol_test::StatusUpdate;
522/// # async fn example(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
523/// Agent.builder()
524///     .on_receive_request(async |req: InitializeRequest, responder, cx| {
525///         responder.respond(
526///             InitializeResponse::new(req.protocol_version)
527///                 .agent_capabilities(AgentCapabilities::new()),
528///         )
529///     }, agent_client_protocol::on_receive_request!())
530///     .on_receive_notification(async |notif: StatusUpdate, cx| {
531///         // Process notification
532///         Ok(())
533///     }, agent_client_protocol::on_receive_notification!())
534///     .connect_to(transport)
535///     .await?;
536/// # Ok(())
537/// # }
538/// ```
539///
540/// The type parameter on the closure determines which messages are dispatched to it.
541/// Messages that don't match the type are automatically passed to the next handler.
542///
543/// # Implementing Custom Handlers
544///
545/// For advanced use cases, you can implement [`HandleDispatchFrom`] directly:
546///
547/// ```no_run
548/// use agent_client_protocol::{
549///     Client, ConnectionTo, Dispatch, Error, HandleDispatchFrom, Handled,
550/// };
551///
552/// struct MyHandler;
553///
554/// impl HandleDispatchFrom<Client> for MyHandler {
555///     async fn handle_dispatch_from(
556///         &mut self,
557///         message: Dispatch,
558///         _connection: ConnectionTo<Client>,
559///     ) -> Result<Handled<Dispatch>, Error> {
560///         if message.method() == "my/custom/method" {
561///             // Handle it
562///             Ok(Handled::Yes)
563///         } else {
564///             // Pass to next handler
565///             Ok(Handled::No { message, retry: false })
566///         }
567///     }
568///
569///     fn describe_chain(&self) -> impl std::fmt::Debug {
570///         "MyHandler"
571///     }
572/// }
573/// ```
574///
575/// # Important: Handlers Must Not Block
576///
577/// The connection processes messages on a single async task. While a handler is running,
578/// no other messages can be processed. For expensive operations, use [`ConnectionTo::spawn`]
579/// to run work concurrently:
580///
581/// ```
582/// # use agent_client_protocol::{Client, Agent, ConnectTo};
583/// # use agent_client_protocol_test::{expensive_operation, ProcessComplete};
584/// # async fn example(transport: impl ConnectTo<Client>) -> Result<(), agent_client_protocol::Error> {
585/// # Client.builder().connect_with(transport, async |cx| {
586/// cx.spawn({
587///     let connection = cx.clone();
588///     async move {
589///         let result = expensive_operation("data").await?;
590///         connection.send_notification(ProcessComplete { result })?;
591///         Ok(())
592///     }
593/// })?;
594/// # Ok(())
595/// # }).await?;
596/// # Ok(())
597/// # }
598/// ```
599#[allow(async_fn_in_trait)]
600/// A handler for incoming JSON-RPC messages.
601///
602/// This trait is implemented by types that can process incoming messages on a connection.
603/// Handlers are registered with a [`Builder`] and are called in order until
604/// one claims the message.
605///
606/// The type parameter is the counterpart role that messages arrive from and
607/// that the supplied [`ConnectionTo`] addresses. An agent handler therefore
608/// implements `HandleDispatchFrom<Client>`, while a client handler implements
609/// `HandleDispatchFrom<Agent>`.
610pub trait HandleDispatchFrom<Counterpart: Role>: Send {
611    /// Attempt to claim an incoming dispatch (request, notification, or response).
612    ///
613    /// # Important: do not block
614    ///
615    /// The server will not process new messages until this handler returns.
616    /// You should avoid blocking in this callback unless you wish to block the server (e.g., for rate limiting).
617    /// The recommended approach to manage expensive operations is to the [`ConnectionTo::spawn`] method available on the message context.
618    ///
619    /// # Parameters
620    ///
621    /// * `message` - The incoming message to handle.
622    /// * `connection` - The connection, used to send messages and access connection state.
623    ///
624    /// # Returns
625    ///
626    /// * `Ok(Handled::Yes)` if the message was claimed. It will not be propagated further.
627    /// * `Ok(Handled::No(message))` if not; the (possibly changed) message will be passed to the remaining handlers.
628    /// * `Err` if processing fails. Requests receive an Error Response, response
629    ///   errors are routed to the local request awaiter, and notification errors
630    ///   are logged without a wire reply.
631    fn handle_dispatch_from(
632        &mut self,
633        message: Dispatch,
634        connection: ConnectionTo<Counterpart>,
635    ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send;
636
637    /// Returns a debug description of the registered handlers for diagnostics.
638    fn describe_chain(&self) -> impl std::fmt::Debug;
639}
640
641impl<Counterpart: Role, H> HandleDispatchFrom<Counterpart> for &mut H
642where
643    H: HandleDispatchFrom<Counterpart>,
644{
645    fn handle_dispatch_from(
646        &mut self,
647        message: Dispatch,
648        cx: ConnectionTo<Counterpart>,
649    ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
650        H::handle_dispatch_from(self, message, cx)
651    }
652
653    fn describe_chain(&self) -> impl std::fmt::Debug {
654        H::describe_chain(self)
655    }
656}
657
658/// Selects the connection context exposed by a [`Builder`]'s callbacks.
659///
660/// This trait is an implementation detail of the typed builder aliases. It is
661/// public so the callback connection type remains expressible in public API
662/// signatures.
663#[doc(hidden)]
664#[allow(private_bounds)]
665pub trait ConnectionContext: connection_context::Sealed + Send + Sync + 'static {
666    /// The connection type exposed to callbacks for `Counterpart`.
667    type Connection<Counterpart: Role>: Clone + Send + Sync + 'static;
668}
669
670mod connection_context {
671    use super::{ConnectionContext, ConnectionTo, Role};
672
673    pub trait Sealed {
674        fn from_raw<Counterpart: Role>(
675            connection: ConnectionTo<Counterpart>,
676        ) -> <Self as ConnectionContext>::Connection<Counterpart>
677        where
678            Self: ConnectionContext;
679    }
680
681    pub(crate) fn from_raw<Context: ConnectionContext, Counterpart: Role>(
682        connection: ConnectionTo<Counterpart>,
683    ) -> Context::Connection<Counterpart> {
684        <Context as Sealed>::from_raw(connection)
685    }
686}
687
688/// The default callback context used by stable and low-level builders.
689#[doc(hidden)]
690#[derive(Copy, Clone, Debug, Default)]
691pub struct RawConnectionContext;
692
693impl connection_context::Sealed for RawConnectionContext {
694    fn from_raw<Counterpart: Role>(
695        connection: ConnectionTo<Counterpart>,
696    ) -> <Self as ConnectionContext>::Connection<Counterpart> {
697        connection
698    }
699}
700
701impl ConnectionContext for RawConnectionContext {
702    type Connection<Counterpart: Role> = ConnectionTo<Counterpart>;
703}
704
705/// The callback context used by ACP protocol v2 builders.
706#[cfg(feature = "unstable_protocol_v2")]
707#[doc(hidden)]
708#[derive(Copy, Clone, Debug, Default)]
709pub struct V2ConnectionContext;
710
711#[cfg(feature = "unstable_protocol_v2")]
712impl connection_context::Sealed for V2ConnectionContext {
713    fn from_raw<Counterpart: Role>(
714        connection: ConnectionTo<Counterpart>,
715    ) -> <Self as ConnectionContext>::Connection<Counterpart> {
716        V2ConnectionTo { inner: connection }
717    }
718}
719
720#[cfg(feature = "unstable_protocol_v2")]
721impl ConnectionContext for V2ConnectionContext {
722    type Connection<Counterpart: Role> = V2ConnectionTo<Counterpart>;
723}
724
725/// A JSON-RPC connection builder whose callbacks receive [`V2ConnectionTo`].
726#[cfg(feature = "unstable_protocol_v2")]
727pub type V2Builder<Host, Handler = NullHandler, Runner = NullRun, Close = NullClose> =
728    Builder<Host, Handler, Runner, Close, V2ConnectionContext>;
729
730/// A JSON-RPC connection that can act as either a server, client, or both.
731///
732/// [`Builder`] provides a builder-style API for creating JSON-RPC servers and clients.
733/// You start by calling `Role.builder()` (e.g., `Client.builder()`), then add message
734/// handlers, and finally drive the connection with either [`connect_to`](Builder::connect_to)
735/// or [`connect_with`](Builder::connect_with), providing a component implementation
736/// (e.g., [`ByteStreams`] for byte streams).
737///
738/// # JSON-RPC Primer
739///
740/// JSON-RPC 2.0 has two fundamental message types:
741///
742/// * **Requests** - Messages that expect a response. They have an `id` field that gets
743///   echoed back in the response so the sender can correlate them.
744/// * **Notifications** - Fire-and-forget messages with no `id` field. The sender doesn't
745///   expect or receive a response.
746///
747/// # Type-Driven Message Dispatch
748///
749/// The handler registration methods use Rust's type system to determine which messages
750/// to handle. The type parameter you provide controls what gets dispatched to your handler:
751///
752/// ## Single Message Types
753///
754/// The simplest case - handle one specific message type:
755///
756/// ```no_run
757/// # use agent_client_protocol_test::*;
758/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, SessionNotification};
759/// # async fn example() -> Result<(), agent_client_protocol::Error> {
760/// # let connection = mock_connection();
761/// connection
762///     .on_receive_request(async |req: InitializeRequest, responder, cx| {
763///         // Handle only InitializeRequest messages
764///         responder.respond(InitializeResponse::make())
765///     }, agent_client_protocol::on_receive_request!())
766///     .on_receive_notification(async |notif: SessionNotification, cx| {
767///         // Handle only SessionUpdate notifications
768///         Ok(())
769///     }, agent_client_protocol::on_receive_notification!())
770/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
771/// # Ok(())
772/// # }
773/// ```
774///
775/// ## Enum Message Types
776///
777/// You can also handle multiple related messages with a single handler by defining an enum
778/// that implements the appropriate trait ([`JsonRpcRequest`] or [`JsonRpcNotification`]):
779///
780/// ```no_run
781/// # use agent_client_protocol_test::*;
782/// # use agent_client_protocol::{JsonRpcRequest, JsonRpcMessage, UntypedMessage};
783/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse};
784/// # async fn example() -> Result<(), agent_client_protocol::Error> {
785/// # let connection = mock_connection();
786/// // Define an enum for multiple request types
787/// #[derive(Debug, Clone)]
788/// enum MyRequests {
789///     Initialize(InitializeRequest),
790///     Prompt(PromptRequest),
791/// }
792///
793/// // Implement JsonRpcRequest for your enum
794/// # impl JsonRpcMessage for MyRequests {
795/// #     fn matches_method(_method: &str) -> bool { false }
796/// #     fn method(&self) -> &str { "myRequests" }
797/// #     fn to_untyped_message(&self) -> Result<UntypedMessage, agent_client_protocol::Error> { todo!() }
798/// #     fn parse_message(_method: &str, _params: &impl serde::Serialize) -> Result<Self, agent_client_protocol::Error> { Err(agent_client_protocol::Error::method_not_found()) }
799/// # }
800/// impl JsonRpcRequest for MyRequests { type Response = serde_json::Value; }
801///
802/// // Handle all variants in one place
803/// connection.on_receive_request(async |req: MyRequests, responder, cx| {
804///     match req {
805///         MyRequests::Initialize(init) => { responder.respond(serde_json::json!({})) }
806///         MyRequests::Prompt(prompt) => { responder.respond(serde_json::json!({})) }
807///     }
808/// }, agent_client_protocol::on_receive_request!())
809/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
810/// # Ok(())
811/// # }
812/// ```
813///
814/// ## Mixed Message Types
815///
816/// To handle requests, notifications, and responses in one callback, use
817/// [`on_receive_dispatch`](Self::on_receive_dispatch):
818///
819/// ```no_run
820/// # use agent_client_protocol_test::*;
821/// # use agent_client_protocol::Dispatch;
822/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, SessionNotification};
823/// # async fn example() -> Result<(), agent_client_protocol::Error> {
824/// # let connection = mock_connection();
825/// // on_receive_dispatch receives requests, notifications, and responses
826/// connection.on_receive_dispatch(async |msg: Dispatch<InitializeRequest, SessionNotification>, _cx| {
827///     match msg {
828///         Dispatch::Request(req, responder) => {
829///             responder.respond(InitializeResponse::make())
830///         }
831///         Dispatch::Notification(notif) => {
832///             Ok(())
833///         }
834///         Dispatch::Response(result, router) => {
835///             // Forward response to its destination
836///             router.route_with_result(result)
837///         }
838///     }
839/// }, agent_client_protocol::on_receive_dispatch!())
840/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
841/// # Ok(())
842/// # }
843/// ```
844///
845/// # Handler Registration
846///
847/// Register handlers using these methods (listed from most common to most flexible):
848///
849/// * [`on_receive_request`](Self::on_receive_request) - Handle JSON-RPC requests (messages expecting responses)
850/// * [`on_receive_notification`](Self::on_receive_notification) - Handle JSON-RPC notifications (fire-and-forget)
851/// * [`on_receive_dispatch`](Self::on_receive_dispatch) - Handle requests, notifications, and responses in one callback
852/// * [`with_handler`](Self::with_handler) - Low-level primitive for maximum flexibility
853///
854/// ## Handler Ordering
855///
856/// Handlers are tried in the order you register them. The first handler that claims a message
857/// (by matching its type) will process it. Subsequent handlers won't see that message:
858///
859/// ```no_run
860/// # use agent_client_protocol_test::*;
861/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse};
862/// # async fn example() -> Result<(), agent_client_protocol::Error> {
863/// # let connection = mock_connection();
864/// connection
865///     .on_receive_request(async |req: InitializeRequest, responder, cx| {
866///         // This runs first for InitializeRequest
867///         responder.respond(InitializeResponse::make())
868///     }, agent_client_protocol::on_receive_request!())
869///     .on_receive_request(async |req: PromptRequest, responder, cx| {
870///         // This runs first for PromptRequest
871///         responder.respond(PromptResponse::make())
872///     }, agent_client_protocol::on_receive_request!())
873///     // Unknown requests receive Method not found automatically; unhandled
874///     // notifications are ignored.
875/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
876/// # Ok(())
877/// # }
878/// ```
879///
880/// # Event Loop and Concurrency
881///
882/// Understanding the event loop is critical for writing correct handlers.
883///
884/// ## The Event Loop
885///
886/// [`Builder`] runs all handler callbacks on a single async task - the event loop.
887/// While a handler is running, **the server cannot receive new messages**. This means
888/// any blocking or expensive work in your handlers will stall the entire connection.
889///
890/// To avoid blocking the event loop, use [`ConnectionTo::spawn`] to offload serious
891/// work to concurrent tasks:
892///
893/// ```no_run
894/// # use agent_client_protocol_test::*;
895/// # async fn example() -> Result<(), agent_client_protocol::Error> {
896/// # let connection = mock_connection();
897/// connection.on_receive_request(async |req: AnalyzeRequest, responder, cx| {
898///     // Clone cx for the spawned task
899///     cx.spawn({
900///         let connection = cx.clone();
901///         async move {
902///             let result = expensive_analysis(&req.data).await?;
903///             connection.send_notification(AnalysisComplete { result })?;
904///             Ok(())
905///         }
906///     })?;
907///
908///     // Respond immediately without blocking
909///     responder.respond(AnalysisStarted { job_id: 42 })
910/// }, agent_client_protocol::on_receive_request!())
911/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
912/// # Ok(())
913/// # }
914/// ```
915///
916/// Note that the entire connection runs within one async task, so parallelism must be
917/// managed explicitly using [`spawn`](ConnectionTo::spawn).
918///
919/// ## The Connection Context
920///
921/// Handler callbacks receive a context object (`cx`) for interacting with the connection:
922///
923/// * **For request handlers** - [`Responder<R>`] provides [`respond`](Responder::respond)
924///   to send the response, plus methods to send other messages
925/// * **For notification handlers** - [`ConnectionTo`] provides methods to send messages
926///   and spawn tasks
927///
928/// Both context types support:
929/// * [`send_request`](ConnectionTo::send_request) - Send requests to the other side
930/// * [`send_notification`](ConnectionTo::send_notification) - Send notifications
931/// * [`spawn`](ConnectionTo::spawn) - Run tasks concurrently without blocking the event loop
932///
933/// The [`SentRequest`] returned by `send_request` provides methods like
934/// [`on_receiving_result`](SentRequest::on_receiving_result) that help you
935/// avoid accidentally blocking the event loop while waiting for responses.
936///
937/// # Driving the Connection
938///
939/// After adding handlers, you must drive the connection using one of two modes:
940///
941/// ## Server Mode: `connect_to()`
942///
943/// Use [`connect_to`](Self::connect_to) when you only need to respond to incoming messages:
944///
945/// ```no_run
946/// # use agent_client_protocol_test::*;
947/// # async fn example() -> Result<(), agent_client_protocol::Error> {
948/// # let connection = mock_connection();
949/// connection
950///     .on_receive_request(async |req: MyRequest, responder, cx| {
951///         responder.respond(MyResponse { status: "ok".into() })
952///     }, agent_client_protocol::on_receive_request!())
953///     .connect_to(MockTransport)  // Runs until connection closes or error occurs
954///     .await?;
955/// # Ok(())
956/// # }
957/// ```
958///
959/// The connection will process incoming messages and invoke your handlers until the
960/// connection is closed or an error occurs.
961///
962/// ## Client Mode: `connect_with()`
963///
964/// Use [`connect_with`](Self::connect_with) when you need to both handle incoming messages
965/// AND send your own requests/notifications:
966///
967/// ```no_run
968/// # use agent_client_protocol_test::*;
969/// # use agent_client_protocol::schema::v1::InitializeRequest;
970/// # async fn example() -> Result<(), agent_client_protocol::Error> {
971/// # let connection = mock_connection();
972/// connection
973///     .on_receive_request(async |req: MyRequest, responder, cx| {
974///         responder.respond(MyResponse { status: "ok".into() })
975///     }, agent_client_protocol::on_receive_request!())
976///     .connect_with(MockTransport, async |cx| {
977///         // You can send requests to the other side
978///         let response = cx.send_request(InitializeRequest::make())
979///             .block_task()
980///             .await?;
981///
982///         // And send notifications
983///         cx.send_notification(StatusUpdate { message: "ready".into() })?;
984///
985///         Ok(())
986///     })
987///     .await?;
988/// # Ok(())
989/// # }
990/// ```
991///
992/// The connection will serve incoming messages in the background while your client closure
993/// runs. When the closure returns, the connection shuts down.
994///
995/// # Example: Complete Agent
996///
997/// ```no_run
998/// # use agent_client_protocol::UntypedRole;
999/// # use agent_client_protocol::{Builder};
1000/// # use agent_client_protocol::schema::v1::{InitializeRequest, InitializeResponse, PromptRequest, PromptResponse, SessionNotification};
1001/// # async fn example(transport: impl agent_client_protocol::ConnectTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
1002///
1003/// UntypedRole.builder()
1004///     .name("my-agent")  // Optional: for debugging logs
1005///     .on_receive_request(async |init: InitializeRequest, responder, cx| {
1006///         let response: InitializeResponse = todo!();
1007///         responder.respond(response)
1008///     }, agent_client_protocol::on_receive_request!())
1009///     .on_receive_request(async |prompt: PromptRequest, responder, cx| {
1010///         // You can send notifications while processing a request
1011///         let notif: SessionNotification = todo!();
1012///         cx.send_notification(notif)?;
1013///
1014///         // Then respond to the request
1015///         let response: PromptResponse = todo!();
1016///         responder.respond(response)
1017///     }, agent_client_protocol::on_receive_request!())
1018///     .connect_to(transport)
1019///     .await?;
1020/// # Ok(())
1021/// # }
1022/// ```
1023#[must_use]
1024#[derive(Debug)]
1025pub struct Builder<
1026    Host: Role,
1027    Handler = NullHandler,
1028    Runner = NullRun,
1029    Close = NullClose,
1030    Context = RawConnectionContext,
1031> where
1032    Handler: HandleDispatchFrom<Host::Counterpart>,
1033    Runner: RunWithConnectionTo<Host::Counterpart>,
1034    Close: HandleConnectionClose<Host::Counterpart>,
1035    Context: ConnectionContext,
1036{
1037    /// My role.
1038    host: Host,
1039
1040    /// Name of the connection, used in tracing logs.
1041    name: Option<String>,
1042
1043    /// Handler for incoming messages.
1044    handler: Handler,
1045
1046    /// Runner for background connection tasks.
1047    runner: Runner,
1048
1049    /// Protocol version mode for the public API and wire compatibility layer.
1050    protocol_mode: ProtocolMode,
1051
1052    /// Handler run when the incoming transport reaches clean EOF.
1053    on_close: Close,
1054
1055    /// Selects the connection type exposed to user callbacks.
1056    context: PhantomData<fn() -> Context>,
1057}
1058
1059fn default_protocol_mode<Host: Role>() -> ProtocolMode {
1060    let role = TypeId::of::<Host>();
1061
1062    if role == TypeId::of::<Agent>() {
1063        ProtocolMode::v1_agent()
1064    } else if role == TypeId::of::<Client>() {
1065        ProtocolMode::v1_client()
1066    } else if role == TypeId::of::<Proxy>() {
1067        ProtocolMode::v1_proxy()
1068    } else {
1069        ProtocolMode::disabled()
1070    }
1071}
1072
1073impl<Host: Role> Builder<Host, NullHandler, NullRun, NullClose> {
1074    /// Create a new connection builder for the given role.
1075    /// This type follows a builder pattern; use other methods to configure and then invoke
1076    /// [`Self::connect_to`] (to use as a server) or [`Self::connect_with`] to use as a client.
1077    pub fn new(role: Host) -> Self {
1078        Self {
1079            host: role,
1080            name: None,
1081            handler: NullHandler,
1082            runner: NullRun,
1083            protocol_mode: default_protocol_mode::<Host>(),
1084            on_close: NullClose,
1085            context: PhantomData,
1086        }
1087    }
1088}
1089
1090impl<Host: Role, Handler> Builder<Host, Handler, NullRun, NullClose>
1091where
1092    Handler: HandleDispatchFrom<Host::Counterpart>,
1093{
1094    /// Create a new connection builder with the given handler.
1095    pub fn new_with(role: Host, handler: Handler) -> Self {
1096        Self {
1097            host: role,
1098            name: None,
1099            handler,
1100            runner: NullRun,
1101            protocol_mode: default_protocol_mode::<Host>(),
1102            on_close: NullClose,
1103            context: PhantomData,
1104        }
1105    }
1106}
1107
1108#[cfg(feature = "unstable_protocol_v2")]
1109impl<
1110    Host: Role,
1111    Handler: HandleDispatchFrom<Host::Counterpart>,
1112    Runner: RunWithConnectionTo<Host::Counterpart>,
1113    Close: HandleConnectionClose<Host::Counterpart>,
1114> Builder<Host, Handler, Runner, Close>
1115{
1116    pub(crate) fn v2_agent(self) -> V2Builder<Host, Handler, Runner, Close> {
1117        Builder {
1118            host: self.host,
1119            name: self.name,
1120            handler: self.handler,
1121            runner: self.runner,
1122            protocol_mode: ProtocolMode::v2_agent(),
1123            on_close: self.on_close,
1124            context: PhantomData,
1125        }
1126    }
1127
1128    pub(crate) fn v2_client(self) -> V2Builder<Host, Handler, Runner, Close> {
1129        Builder {
1130            host: self.host,
1131            name: self.name,
1132            handler: self.handler,
1133            runner: self.runner,
1134            protocol_mode: ProtocolMode::v2_client(),
1135            on_close: self.on_close,
1136            context: PhantomData,
1137        }
1138    }
1139
1140    pub(crate) fn v2_proxy(self) -> V2Builder<Host, Handler, Runner, Close> {
1141        Builder {
1142            host: self.host,
1143            name: self.name,
1144            handler: self.handler,
1145            runner: self.runner,
1146            protocol_mode: ProtocolMode::v2_proxy(),
1147            on_close: self.on_close,
1148            context: PhantomData,
1149        }
1150    }
1151
1152    /// Disable all automatic ACP protocol-version tracking and validation.
1153    ///
1154    /// This is a low-level escape hatch for protocol-routing infrastructure
1155    /// that inspects and validates raw initialize requests and responses
1156    /// itself before selecting a version-specific implementation. It also
1157    /// disables the version checks applied to messages after initialization.
1158    ///
1159    /// This method is deliberately available only on builders whose callbacks
1160    /// receive raw [`ConnectionTo`] values. Applications should normally use
1161    /// [`Client::builder`](crate::Client::builder),
1162    /// [`Agent::builder`](crate::Agent::builder),
1163    /// [`Proxy::builder`](crate::Proxy::builder), [`Client::v2`](crate::Client::v2),
1164    /// [`Agent::v2`](crate::Agent::v2), or [`Proxy::v2`](crate::Proxy::v2)
1165    /// instead.
1166    ///
1167    /// ```compile_fail
1168    /// # use agent_client_protocol::Client;
1169    /// let _ = Client.v2().without_acp_version_guard();
1170    /// ```
1171    pub fn without_acp_version_guard(mut self) -> Self {
1172        self.protocol_mode = ProtocolMode::disabled();
1173        self
1174    }
1175}
1176
1177#[cfg(feature = "unstable_protocol_v2")]
1178impl<
1179    Handler: HandleDispatchFrom<Agent>,
1180    Runner: RunWithConnectionTo<Agent>,
1181    Close: HandleConnectionClose<Agent>,
1182> Builder<Client, Handler, Runner, Close>
1183{
1184    /// Apply protocol-v2 wire validation while retaining raw callback contexts.
1185    ///
1186    /// This is intended for protocol-routing infrastructure that has already
1187    /// selected v2 but still needs protocol-neutral [`ConnectionTo`] values in
1188    /// its callbacks. The guarded child must still send and receive the
1189    /// `initialize` round trip; a router that consumes initialization itself
1190    /// must use [`Builder::without_acp_version_guard`] for the selected child.
1191    /// Most clients should use [`Client::v2`](crate::Client::v2), which also
1192    /// exposes the version-typed [`V2ConnectionTo`] API.
1193    pub fn with_v2_protocol_guard(mut self) -> Self {
1194        self.protocol_mode = ProtocolMode::v2_client();
1195        self
1196    }
1197}
1198
1199#[cfg(feature = "unstable_protocol_v2")]
1200impl<
1201    Handler: HandleDispatchFrom<Client>,
1202    Runner: RunWithConnectionTo<Client>,
1203    Close: HandleConnectionClose<Client>,
1204> Builder<Agent, Handler, Runner, Close>
1205{
1206    /// Apply protocol-v2 wire validation while retaining raw callback contexts.
1207    ///
1208    /// This is intended for protocol-routing infrastructure that has already
1209    /// selected v2 but still needs protocol-neutral [`ConnectionTo`] values in
1210    /// its callbacks. The guarded child must still receive and answer the
1211    /// `initialize` request; a router that consumes initialization itself must
1212    /// use [`Builder::without_acp_version_guard`] for the selected child. Most
1213    /// agents should use [`Agent::v2`](crate::Agent::v2), which also exposes the
1214    /// version-typed [`V2ConnectionTo`] API.
1215    pub fn with_v2_protocol_guard(mut self) -> Self {
1216        self.protocol_mode = ProtocolMode::v2_agent();
1217        self
1218    }
1219}
1220
1221impl<
1222    Host: Role,
1223    Handler: HandleDispatchFrom<Host::Counterpart>,
1224    Runner: RunWithConnectionTo<Host::Counterpart>,
1225    Close: HandleConnectionClose<Host::Counterpart>,
1226    Context: ConnectionContext,
1227> Builder<Host, Handler, Runner, Close, Context>
1228{
1229    /// Set the "name" of this connection -- used only for debugging logs.
1230    pub fn name(mut self, name: impl ToString) -> Self {
1231        self.name = Some(name.to_string());
1232        self
1233    }
1234
1235    pub(crate) fn v1_agent(mut self) -> Self {
1236        self.protocol_mode = ProtocolMode::v1_agent();
1237        self
1238    }
1239
1240    pub(crate) fn v1_client(mut self) -> Self {
1241        self.protocol_mode = ProtocolMode::v1_client();
1242        self
1243    }
1244
1245    /// Merge another [`Builder`] into this one.
1246    ///
1247    /// Prefer [`Self::on_receive_request`] or [`Self::on_receive_notification`].
1248    /// This is a low-level method that is not intended for general use.
1249    pub fn with_connection_builder(
1250        self,
1251        other: Builder<
1252            Host,
1253            impl HandleDispatchFrom<Host::Counterpart>,
1254            impl RunWithConnectionTo<Host::Counterpart>,
1255            impl HandleConnectionClose<Host::Counterpart>,
1256            Context,
1257        >,
1258    ) -> Builder<
1259        Host,
1260        impl HandleDispatchFrom<Host::Counterpart>,
1261        impl RunWithConnectionTo<Host::Counterpart>,
1262        impl HandleConnectionClose<Host::Counterpart>,
1263        Context,
1264    > {
1265        let Builder {
1266            name: other_name,
1267            handler: other_handler,
1268            runner: other_runner,
1269            protocol_mode: other_protocol_mode,
1270            on_close: other_on_close,
1271            context: _,
1272            host: _,
1273        } = other;
1274        Builder {
1275            host: self.host,
1276            name: self.name,
1277            handler: ChainedHandler::new(
1278                self.handler,
1279                NamedHandler::new(other_name, other_handler),
1280            ),
1281            runner: ChainRun::new(self.runner, other_runner),
1282            protocol_mode: self.protocol_mode.merge(other_protocol_mode),
1283            on_close: ChainedClose::new(self.on_close, other_on_close),
1284            context: PhantomData,
1285        }
1286    }
1287
1288    /// Add a new [`HandleDispatchFrom`] to the chain.
1289    ///
1290    /// Prefer [`Self::on_receive_request`] or [`Self::on_receive_notification`].
1291    /// This is a low-level method that is not intended for general use.
1292    pub fn with_handler(
1293        self,
1294        handler: impl HandleDispatchFrom<Host::Counterpart>,
1295    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context> {
1296        Builder {
1297            host: self.host,
1298            name: self.name,
1299            handler: ChainedHandler::new(self.handler, handler),
1300            runner: self.runner,
1301            protocol_mode: self.protocol_mode,
1302            on_close: self.on_close,
1303            context: PhantomData,
1304        }
1305    }
1306
1307    /// Add a new [`RunWithConnectionTo`] to the chain.
1308    pub fn with_runner<Run1>(
1309        self,
1310        runner: Run1,
1311    ) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close, Context>
1312    where
1313        Run1: RunWithConnectionTo<Host::Counterpart>,
1314    {
1315        Builder {
1316            host: self.host,
1317            name: self.name,
1318            handler: self.handler,
1319            runner: ChainRun::new(self.runner, runner),
1320            protocol_mode: self.protocol_mode,
1321            on_close: self.on_close,
1322            context: PhantomData,
1323        }
1324    }
1325
1326    /// Enqueue a task to run once the connection is actively serving traffic.
1327    #[track_caller]
1328    pub fn with_spawned<F, Fut>(
1329        self,
1330        task: F,
1331    ) -> Builder<Host, Handler, impl RunWithConnectionTo<Host::Counterpart>, Close, Context>
1332    where
1333        F: FnOnce(Context::Connection<Host::Counterpart>) -> Fut + Send,
1334        Fut: Future<Output = Result<(), crate::Error>> + Send,
1335    {
1336        let location = Location::caller();
1337        self.with_runner(SpawnedRun::<_, Context>::new(location, task))
1338    }
1339
1340    /// Run a callback when the incoming transport reaches clean EOF.
1341    ///
1342    /// Each callback runs at most once and receives the connection context. A
1343    /// successful callback observes the close without otherwise changing the
1344    /// lifetime of [`connect_with`](Self::connect_with). Returning an error
1345    /// shuts down the connection and cancels a still-running `connect_with`
1346    /// future.
1347    ///
1348    /// Multiple callbacks run sequentially in registration order. All of them
1349    /// run even if an earlier callback fails, after which the first error is
1350    /// returned. Pending requests are failed before callbacks begin, while the
1351    /// selected connection context's `incoming_closed` future completes only
1352    /// after they finish. A callback must therefore not await that close
1353    /// future itself.
1354    ///
1355    /// This separation lets applications choose their cancellation policy. A
1356    /// callback can notify application-owned tasks and return `Ok(())` for
1357    /// graceful cleanup, or return an error to stop them immediately.
1358    ///
1359    /// Close callbacks do not run for transport or handler errors, foreground
1360    /// completion, or a dropped driver. Use [`ConnectionTo::terminated`] to
1361    /// observe every way the connection can end.
1362    ///
1363    /// ```
1364    /// # use agent_client_protocol::{Client, ConnectTo, Error};
1365    /// # async fn example(transport: impl ConnectTo<Client>) -> Result<(), Error> {
1366    /// Client.builder()
1367    ///     .on_close(async |_cx| {
1368    ///         Err(Error::internal_error().data("agent transport closed"))
1369    ///     })
1370    ///     .connect_with(transport, async |_cx| {
1371    ///         std::future::pending().await
1372    ///     })
1373    ///     .await?;
1374    /// # Ok(())
1375    /// # }
1376    /// ```
1377    pub fn on_close<F, Fut>(
1378        self,
1379        callback: F,
1380    ) -> Builder<Host, Handler, Runner, impl HandleConnectionClose<Host::Counterpart>, Context>
1381    where
1382        F: FnOnce(Context::Connection<Host::Counterpart>) -> Fut + Send,
1383        Fut: Future<Output = Result<(), crate::Error>> + Send,
1384    {
1385        Builder {
1386            host: self.host,
1387            name: self.name,
1388            handler: self.handler,
1389            runner: self.runner,
1390            protocol_mode: self.protocol_mode,
1391            on_close: ChainedClose::new(self.on_close, CloseCallback::<_, Context>::new(callback)),
1392            context: PhantomData,
1393        }
1394    }
1395
1396    /// Register a handler for requests, notifications, and responses.
1397    ///
1398    /// Use this when you want to handle all JSON-RPC message kinds in one callback.
1399    /// Your handler receives a [`Dispatch<Req, Notif>`] with three variants:
1400    ///
1401    /// - `Dispatch::Request(request, responder)` - A request with its response context
1402    /// - `Dispatch::Notification(notification)` - A notification
1403    /// - `Dispatch::Response(result, router)` - A response to a request we sent
1404    ///
1405    /// # Example
1406    ///
1407    /// ```no_run
1408    /// # use agent_client_protocol_test::*;
1409    /// # use agent_client_protocol::Dispatch;
1410    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1411    /// # let connection = mock_connection();
1412    /// connection.on_receive_dispatch(async |message: Dispatch<MyRequest, StatusUpdate>, _cx| {
1413    ///     match message {
1414    ///         Dispatch::Request(req, responder) => {
1415    ///             // Handle request and send response
1416    ///             responder.respond(MyResponse { status: "ok".into() })
1417    ///         }
1418    ///         Dispatch::Notification(notif) => {
1419    ///             // Handle notification (no response needed)
1420    ///             Ok(())
1421    ///         }
1422    ///         Dispatch::Response(result, router) => {
1423    ///             // Forward response to its destination
1424    ///             router.route_with_result(result)
1425    ///         }
1426    ///     }
1427    /// }, agent_client_protocol::on_receive_dispatch!())
1428    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
1429    /// # Ok(())
1430    /// # }
1431    /// ```
1432    ///
1433    /// For most use cases, prefer [`on_receive_request`](Self::on_receive_request) or
1434    /// [`on_receive_notification`](Self::on_receive_notification) which provide cleaner APIs
1435    /// for handling requests or notifications separately.
1436    ///
1437    /// # Ordering
1438    ///
1439    /// This callback runs inside the dispatch loop and blocks further message processing
1440    /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1441    /// ordering guarantees and how to avoid deadlocks.
1442    pub fn on_receive_dispatch<Req, Notif, F, T, ToFut>(
1443        self,
1444        op: F,
1445        to_future_hack: ToFut,
1446    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1447    where
1448        Host::Counterpart: HasPeer<Host::Counterpart>,
1449        Req: JsonRpcRequest,
1450        Notif: JsonRpcNotification,
1451        F: AsyncFnMut(
1452                Dispatch<Req, Notif>,
1453                Context::Connection<Host::Counterpart>,
1454            ) -> Result<T, crate::Error>
1455            + Send,
1456        T: IntoHandled<Dispatch<Req, Notif>>,
1457        ToFut: Fn(
1458                &mut F,
1459                Dispatch<Req, Notif>,
1460                Context::Connection<Host::Counterpart>,
1461            ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1462            + Send
1463            + Sync,
1464    {
1465        let handler = MessageHandler::<_, _, _, _, _, _, Context>::new(
1466            self.host.counterpart(),
1467            self.host.counterpart(),
1468            op,
1469            to_future_hack,
1470        );
1471        self.with_handler(handler)
1472    }
1473
1474    /// Register a handler for JSON-RPC requests of type `Req`.
1475    ///
1476    /// Your handler receives three arguments:
1477    /// 1. The request (type `Req`)
1478    /// 2. A [`Responder<Req::Response>`] for sending the response
1479    /// 3. The builder-selected connection context for the peer that sent the
1480    ///    request (`ConnectionTo` by default, or `V2ConnectionTo` for a
1481    ///    `V2Builder`)
1482    ///
1483    /// The request context allows you to:
1484    /// - Send the response with [`Responder::respond`]
1485    /// - Send notifications to the client with the context's
1486    ///   `send_notification` method
1487    /// - Send requests to the client with the context's `send_request` method
1488    ///
1489    /// # Example
1490    ///
1491    /// ```no_run
1492    /// # use agent_client_protocol::{Agent, ConnectTo};
1493    /// # use agent_client_protocol::schema::v1::{PromptRequest, PromptResponse, SessionNotification};
1494    /// # async fn example(transport: impl ConnectTo<Agent>) -> Result<(), agent_client_protocol::Error> {
1495    /// Agent.builder().on_receive_request(async |request: PromptRequest, responder, cx| {
1496    ///     // Send a notification while processing
1497    ///     let notif: SessionNotification = todo!();
1498    ///     cx.send_notification(notif)?;
1499    ///
1500    ///     // Send the response
1501    ///     let response: PromptResponse = todo!();
1502    ///     responder.respond(response)
1503    /// }, agent_client_protocol::on_receive_request!())
1504    /// .connect_to(transport)
1505    /// .await
1506    /// # }
1507    /// ```
1508    ///
1509    /// # Type Parameter
1510    ///
1511    /// `Req` can be either a single request type or an enum of multiple request types.
1512    /// See the [type-driven dispatch](Self#type-driven-message-dispatch) section for details.
1513    ///
1514    /// # Ordering
1515    ///
1516    /// This callback runs inside the dispatch loop and blocks further message processing
1517    /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1518    /// ordering guarantees and how to avoid deadlocks.
1519    pub fn on_receive_request<Req: JsonRpcRequest, F, T, ToFut>(
1520        self,
1521        op: F,
1522        to_future_hack: ToFut,
1523    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1524    where
1525        Host::Counterpart: HasPeer<Host::Counterpart>,
1526        F: AsyncFnMut(
1527                Req,
1528                Responder<Req::Response>,
1529                Context::Connection<Host::Counterpart>,
1530            ) -> Result<T, crate::Error>
1531            + Send,
1532        T: IntoHandled<(Req, Responder<Req::Response>)>,
1533        ToFut: Fn(
1534                &mut F,
1535                Req,
1536                Responder<Req::Response>,
1537                Context::Connection<Host::Counterpart>,
1538            ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1539            + Send
1540            + Sync,
1541    {
1542        let handler = RequestHandler::<_, _, _, _, _, Context>::new(
1543            self.host.counterpart(),
1544            self.host.counterpart(),
1545            op,
1546            to_future_hack,
1547        );
1548        self.with_handler(handler)
1549    }
1550
1551    /// Register a handler for JSON-RPC notifications of type `Notif`.
1552    ///
1553    /// Notifications are fire-and-forget messages that don't expect a response.
1554    /// Your handler receives:
1555    /// 1. The notification (type `Notif`)
1556    /// 2. The builder-selected connection context for sending messages to the
1557    ///    other side
1558    ///
1559    /// Unlike request handlers, you cannot send a response (notifications don't have IDs),
1560    /// but you can still send your own requests and notifications using the context.
1561    ///
1562    /// # Example
1563    ///
1564    /// ```no_run
1565    /// # use agent_client_protocol_test::*;
1566    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
1567    /// # let connection = mock_connection();
1568    /// connection.on_receive_notification(async |notif: SessionUpdate, cx| {
1569    ///     // Process the notification
1570    ///     update_session_state(&notif)?;
1571    ///
1572    ///     // Optionally send a notification back
1573    ///     cx.send_notification(StatusUpdate {
1574    ///         message: "Acknowledged".into(),
1575    ///     })?;
1576    ///
1577    ///     Ok(())
1578    /// }, agent_client_protocol::on_receive_notification!())
1579    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
1580    /// # Ok(())
1581    /// # }
1582    /// ```
1583    ///
1584    /// # Type Parameter
1585    ///
1586    /// `Notif` can be either a single notification type or an enum of multiple notification types.
1587    /// See the [type-driven dispatch](Self#type-driven-message-dispatch) section for details.
1588    ///
1589    /// # Ordering
1590    ///
1591    /// This callback runs inside the dispatch loop and blocks further message processing
1592    /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1593    /// ordering guarantees and how to avoid deadlocks.
1594    pub fn on_receive_notification<Notif, F, T, ToFut>(
1595        self,
1596        op: F,
1597        to_future_hack: ToFut,
1598    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1599    where
1600        Host::Counterpart: HasPeer<Host::Counterpart>,
1601        Notif: JsonRpcNotification,
1602        F: AsyncFnMut(Notif, Context::Connection<Host::Counterpart>) -> Result<T, crate::Error>
1603            + Send,
1604        T: IntoHandled<(Notif, Context::Connection<Host::Counterpart>)>,
1605        ToFut: Fn(
1606                &mut F,
1607                Notif,
1608                Context::Connection<Host::Counterpart>,
1609            ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1610            + Send
1611            + Sync,
1612    {
1613        let handler = NotificationHandler::<_, _, _, _, _, Context>::new(
1614            self.host.counterpart(),
1615            self.host.counterpart(),
1616            op,
1617            to_future_hack,
1618        );
1619        self.with_handler(handler)
1620    }
1621
1622    /// Register a handler for messages from a specific peer.
1623    ///
1624    /// This is similar to [`on_receive_dispatch`](Self::on_receive_dispatch), but allows
1625    /// specifying the source peer explicitly. This is useful when receiving messages
1626    /// from a peer that requires message transformation (e.g., unwrapping `SuccessorMessage`
1627    /// envelopes when receiving from an agent via a proxy).
1628    ///
1629    /// For the common case of receiving from the default counterpart, use
1630    /// [`on_receive_dispatch`](Self::on_receive_dispatch) instead.
1631    ///
1632    /// # Ordering
1633    ///
1634    /// This callback runs inside the dispatch loop and blocks further message processing
1635    /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1636    /// ordering guarantees and how to avoid deadlocks.
1637    pub fn on_receive_dispatch_from<
1638        Req: JsonRpcRequest,
1639        Notif: JsonRpcNotification,
1640        Peer: Role,
1641        F,
1642        T,
1643        ToFut,
1644    >(
1645        self,
1646        peer: Peer,
1647        op: F,
1648        to_future_hack: ToFut,
1649    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1650    where
1651        Host::Counterpart: HasPeer<Peer>,
1652        F: AsyncFnMut(
1653                Dispatch<Req, Notif>,
1654                Context::Connection<Host::Counterpart>,
1655            ) -> Result<T, crate::Error>
1656            + Send,
1657        T: IntoHandled<Dispatch<Req, Notif>>,
1658        ToFut: Fn(
1659                &mut F,
1660                Dispatch<Req, Notif>,
1661                Context::Connection<Host::Counterpart>,
1662            ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1663            + Send
1664            + Sync,
1665    {
1666        let handler = MessageHandler::<_, _, _, _, _, _, Context>::new(
1667            self.host.counterpart(),
1668            peer,
1669            op,
1670            to_future_hack,
1671        );
1672        self.with_handler(handler)
1673    }
1674
1675    /// Register a handler for JSON-RPC requests from a specific peer.
1676    ///
1677    /// This is similar to [`on_receive_request`](Self::on_receive_request), but allows
1678    /// specifying the source peer explicitly. This is useful when receiving messages
1679    /// from a peer that requires message transformation (e.g., unwrapping `SuccessorRequest`
1680    /// envelopes when receiving from an agent via a proxy).
1681    ///
1682    /// For the common case of receiving from the default counterpart, use
1683    /// [`on_receive_request`](Self::on_receive_request) instead.
1684    ///
1685    /// # Example
1686    ///
1687    /// ```ignore
1688    /// use agent_client_protocol::Agent;
1689    /// use agent_client_protocol::schema::v1::InitializeRequest;
1690    ///
1691    /// // Conductor receiving from agent direction - messages will be unwrapped from SuccessorMessage
1692    /// connection.on_receive_request_from(Agent, async |req: InitializeRequest, responder, cx| {
1693    ///     // Handle the request
1694    ///     responder.respond(InitializeResponse::make())
1695    /// })
1696    /// ```
1697    ///
1698    /// # Ordering
1699    ///
1700    /// This callback runs inside the dispatch loop and blocks further message processing
1701    /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1702    /// ordering guarantees and how to avoid deadlocks.
1703    pub fn on_receive_request_from<Req: JsonRpcRequest, Peer: Role, F, T, ToFut>(
1704        self,
1705        peer: Peer,
1706        op: F,
1707        to_future_hack: ToFut,
1708    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1709    where
1710        Host::Counterpart: HasPeer<Peer>,
1711        F: AsyncFnMut(
1712                Req,
1713                Responder<Req::Response>,
1714                Context::Connection<Host::Counterpart>,
1715            ) -> Result<T, crate::Error>
1716            + Send,
1717        T: IntoHandled<(Req, Responder<Req::Response>)>,
1718        ToFut: Fn(
1719                &mut F,
1720                Req,
1721                Responder<Req::Response>,
1722                Context::Connection<Host::Counterpart>,
1723            ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1724            + Send
1725            + Sync,
1726    {
1727        let handler = RequestHandler::<_, _, _, _, _, Context>::new(
1728            self.host.counterpart(),
1729            peer,
1730            op,
1731            to_future_hack,
1732        );
1733        self.with_handler(handler)
1734    }
1735
1736    /// Register a handler for JSON-RPC notifications from a specific peer.
1737    ///
1738    /// This is similar to [`on_receive_notification`](Self::on_receive_notification), but allows
1739    /// specifying the source peer explicitly. This is useful when receiving messages
1740    /// from a peer that requires message transformation (e.g., unwrapping `SuccessorNotification`
1741    /// envelopes when receiving from an agent via a proxy).
1742    ///
1743    /// For the common case of receiving from the default counterpart, use
1744    /// [`on_receive_notification`](Self::on_receive_notification) instead.
1745    ///
1746    /// # Ordering
1747    ///
1748    /// This callback runs inside the dispatch loop and blocks further message processing
1749    /// until it completes. See the [`ordering`](crate::concepts::ordering) module for details on
1750    /// ordering guarantees and how to avoid deadlocks.
1751    pub fn on_receive_notification_from<Notif: JsonRpcNotification, Peer: Role, F, T, ToFut>(
1752        self,
1753        peer: Peer,
1754        op: F,
1755        to_future_hack: ToFut,
1756    ) -> Builder<Host, impl HandleDispatchFrom<Host::Counterpart>, Runner, Close, Context>
1757    where
1758        Host::Counterpart: HasPeer<Peer>,
1759        F: AsyncFnMut(Notif, Context::Connection<Host::Counterpart>) -> Result<T, crate::Error>
1760            + Send,
1761        T: IntoHandled<(Notif, Context::Connection<Host::Counterpart>)>,
1762        ToFut: Fn(
1763                &mut F,
1764                Notif,
1765                Context::Connection<Host::Counterpart>,
1766            ) -> crate::BoxFuture<'_, Result<T, crate::Error>>
1767            + Send
1768            + Sync,
1769    {
1770        let handler = NotificationHandler::<_, _, _, _, _, Context>::new(
1771            self.host.counterpart(),
1772            peer,
1773            op,
1774            to_future_hack,
1775        );
1776        self.with_handler(handler)
1777    }
1778
1779    /// Run in server mode with the provided transport.
1780    ///
1781    /// This drives the connection by continuously processing messages from the transport
1782    /// and dispatching them to your registered handlers. The connection will run until:
1783    /// - The transport closes (e.g., EOF on byte streams)
1784    /// - An error occurs
1785    ///
1786    /// Handler errors are normally contained: requests receive an Error Response,
1787    /// response-handler errors are routed to the pending local request, and
1788    /// notification errors are logged without a wire reply.
1789    ///
1790    /// On clean EOF, messages already accepted by the outgoing queue—including
1791    /// handler responses and close-callback notifications—are drained through
1792    /// the transport sink before this returns `Ok(())`.
1793    ///
1794    /// The transport boundary carries [`TransportFrame`] values. Physical stream adapters
1795    /// serialize and deserialize frames, while channel-based components relay them directly.
1796    ///
1797    /// Use this mode when you only need to respond to incoming messages and don't need
1798    /// to initiate your own requests. If you need to send requests to the other side,
1799    /// use [`connect_with`](Self::connect_with) instead.
1800    ///
1801    /// # Example: Byte Stream Transport
1802    ///
1803    /// ```no_run
1804    /// # use agent_client_protocol::UntypedRole;
1805    /// # use agent_client_protocol::{Builder};
1806    /// # use agent_client_protocol_test::*;
1807    /// # async fn example(transport: impl agent_client_protocol::ConnectTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
1808    ///
1809    /// UntypedRole.builder()
1810    ///     .on_receive_request(async |req: MyRequest, responder, cx| {
1811    ///         responder.respond(MyResponse { status: "ok".into() })
1812    ///     }, agent_client_protocol::on_receive_request!())
1813    ///     .connect_to(transport)
1814    ///     .await?;
1815    /// # Ok(())
1816    /// # }
1817    /// ```
1818    pub async fn connect_to(
1819        self,
1820        transport: impl ConnectTo<Host> + 'static,
1821    ) -> Result<(), crate::Error> {
1822        let (_, future) = self.into_connection_and_future(transport, true, async move |cx| {
1823            cx.incoming_closed().await;
1824            Ok(())
1825        });
1826        future.await
1827    }
1828
1829    /// Run the connection until the provided closure completes.
1830    ///
1831    /// This drives the connection by:
1832    /// 1. Running your registered handlers in the background to process incoming messages
1833    /// 2. Executing your `main_fn` closure with the builder-selected connection
1834    ///    context for sending requests and notifications
1835    ///
1836    /// The connection stays active until your `main_fn` returns, then shuts down.
1837    /// Clean incoming EOF fails every pending request and makes future
1838    /// requests fail immediately. It does not cancel unrelated work in
1839    /// `main_fn`: that future may observe the context's `incoming_closed`
1840    /// future, or the builder can use [`on_close`](Self::on_close) to notify it
1841    /// or return an error and stop it.
1842    ///
1843    /// Use this mode when you need to initiate communication (send requests/notifications)
1844    /// in addition to responding to incoming messages. For server-only mode where you just
1845    /// respond to messages, use [`connect_to`](Self::connect_to) instead.
1846    ///
1847    /// # Example
1848    ///
1849    /// ```no_run
1850    /// # use agent_client_protocol::UntypedRole;
1851    /// # use agent_client_protocol::{Builder};
1852    /// # use agent_client_protocol::ByteStreams;
1853    /// # use agent_client_protocol::schema::v1::InitializeRequest;
1854    /// # use agent_client_protocol_test::*;
1855    /// # async fn example(transport: impl agent_client_protocol::ConnectTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
1856    ///
1857    /// UntypedRole.builder()
1858    ///     .on_receive_request(async |req: MyRequest, responder, cx| {
1859    ///         // Handle incoming requests in the background
1860    ///         responder.respond(MyResponse { status: "ok".into() })
1861    ///     }, agent_client_protocol::on_receive_request!())
1862    ///     .connect_with(transport, async |cx| {
1863    ///         // Initialize the protocol
1864    ///         let init_response = cx.send_request(InitializeRequest::make())
1865    ///             .block_task()
1866    ///             .await?;
1867    ///
1868    ///         // Send more requests...
1869    ///         let result = cx.send_request(MyRequest {})
1870    ///             .block_task()
1871    ///             .await?;
1872    ///
1873    ///         // When this closure returns, the connection shuts down
1874    ///         Ok(())
1875    ///     })
1876    ///     .await?;
1877    /// # Ok(())
1878    /// # }
1879    /// ```
1880    ///
1881    /// # Parameters
1882    ///
1883    /// - `main_fn`: Your client logic. Receives the builder-selected connection
1884    ///   context for sending messages.
1885    ///
1886    /// # Errors
1887    ///
1888    /// Returns an error if a handler, background task, transport, or close
1889    /// callback fails, or if `main_fn` returns an error. Clean incoming EOF is
1890    /// observable through the context's `incoming_closed` future and is not
1891    /// itself an error in this mode.
1892    pub async fn connect_with<R>(
1893        self,
1894        transport: impl ConnectTo<Host> + 'static,
1895        main_fn: impl AsyncFnOnce(Context::Connection<Host::Counterpart>) -> Result<R, crate::Error>,
1896    ) -> Result<R, crate::Error> {
1897        let (_, future) =
1898            self.into_connection_and_future(transport, false, async move |connection| {
1899                main_fn(connection_context::from_raw::<Context, _>(connection)).await
1900            });
1901        future.await
1902    }
1903
1904    /// Helper that returns a [`ConnectionTo<R>`] and a future that runs this connection until `main_fn` returns.
1905    fn into_connection_and_future<R>(
1906        self,
1907        transport: impl ConnectTo<Host> + 'static,
1908        wait_owned_transport: bool,
1909        main_fn: impl AsyncFnOnce(ConnectionTo<Host::Counterpart>) -> Result<R, crate::Error>,
1910    ) -> (
1911        ConnectionTo<Host::Counterpart>,
1912        impl Future<Output = Result<R, crate::Error>>,
1913    ) {
1914        let Self {
1915            name,
1916            handler,
1917            runner,
1918            host: me,
1919            protocol_mode,
1920            on_close,
1921            context: _,
1922        } = self;
1923
1924        let (outgoing_tx, outgoing_rx) = mpsc::unbounded();
1925        let (new_task_tx, new_task_rx) = mpsc::unbounded();
1926        let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
1927        let (foreground_succeeded_tx, foreground_succeeded) = completion_signal();
1928        let (foreground_done_tx, foreground_done) = completion_signal();
1929        let pending_replies = PendingReplies::default();
1930        let termination_sources = TerminationSources::default();
1931
1932        // Normalize the transport without losing ownership or finish metadata.
1933        let transport_component = crate::DynConnectTo::new(transport);
1934        let (transport_channel, mut transport_future) =
1935            transport_component.into_channel_and_future();
1936        let owned_transport = transport_future.is_some();
1937        let transport_finish = transport_future
1938            .as_mut()
1939            .and_then(crate::ConnectionDriver::take_finish);
1940        let (transport_completion_tx, transport_completion_rx) = oneshot::channel();
1941        let transport_completion = transport_completion_rx
1942            .map(|result| {
1943                result.unwrap_or_else(|error| {
1944                    Err(crate::util::internal_error(format!(
1945                        "transport task dropped before reporting completion: {error}"
1946                    )))
1947                })
1948            })
1949            .boxed()
1950            .shared();
1951
1952        let connection = ConnectionTo::new(
1953            me.counterpart(),
1954            outgoing_tx,
1955            new_task_tx,
1956            dynamic_handler_tx,
1957            transport_completion,
1958            pending_replies.registrar(),
1959            protocol_mode,
1960        );
1961        // Transport progress must outlive successful foreground completion.
1962        // Application tasks remain cancellable. The inherited close-callback
1963        // phase still polls them for cleanup, but physical drain alone does not.
1964        let transport_driver = if let Some(driver) = transport_future {
1965            let termination_sources = termination_sources.clone();
1966            async move {
1967                let result = driver.await;
1968                termination_sources.record(TerminationSource::Transport, &result);
1969                drop(transport_completion_tx.send(result.clone()));
1970                result
1971            }
1972            .boxed()
1973        } else {
1974            // Channel-only endpoints have no physical sink work to await.
1975            // Their protocol drain marker still orders accepted output.
1976            drop(transport_completion_tx.send(Ok(())));
1977            future::ready(Ok(())).boxed()
1978        };
1979
1980        // Destructure the channel endpoints
1981        let Channel {
1982            rx: mut transport_incoming_rx,
1983            tx: transport_outgoing_tx,
1984        } = transport_channel;
1985
1986        let transport_incoming = futures::stream::poll_fn({
1987            let mut completion = connection.transport_completion.clone();
1988            let mut completed = false;
1989            move |cx| {
1990                if owned_transport
1991                    && !completed
1992                    && let std::task::Poll::Ready(Ok(())) =
1993                        std::pin::Pin::new(&mut completion).poll(cx)
1994                {
1995                    // Owned completion closes the producer boundary, not the
1996                    // accepted buffer. The incoming actor still dispatches every
1997                    // accepted frame and completes its close callbacks in order.
1998                    transport_incoming_rx.close();
1999                    completed = true;
2000                }
2001                transport_incoming_rx.poll_next_unpin(cx)
2002            }
2003        });
2004
2005        let protocol_compat = ProtocolCompat::new(protocol_mode);
2006
2007        // Constructed outside the driver so dropping it unpolled still
2008        // publishes a termination.
2009        let termination_guard = TerminationGuard {
2010            incoming_closed: connection.incoming_closed.clone(),
2011            pending_replies: pending_replies.clone(),
2012        };
2013
2014        let future = crate::util::instrument_with_connection_name(name, {
2015            let connection = connection.clone();
2016            async move {
2017                let sources = &termination_sources;
2018                let driver = async {
2019                    let background = async {
2020                        let incoming = {
2021                            let pending_replies = pending_replies.clone();
2022                            let protocol_compat = protocol_compat.clone();
2023                            async {
2024                                let mut transport_incoming = std::pin::pin!(transport_incoming);
2025                                let incoming = incoming_actor::incoming_protocol_actor(
2026                                    me.counterpart(),
2027                                    &connection,
2028                                    transport_incoming.as_mut(),
2029                                    dynamic_handler_rx,
2030                                    pending_replies,
2031                                    incoming_actor::IncomingHandlers::new(
2032                                        handler,
2033                                        on_close,
2034                                        foreground_succeeded.clone(),
2035                                    ),
2036                                    protocol_compat,
2037                                );
2038                                // Success stops delivery, not physical I/O. An
2039                                // underway close callback finishes before sealing.
2040                                let result = run_incoming_until_foreground_succeeds(
2041                                    incoming,
2042                                    foreground_succeeded,
2043                                    connection.incoming_closed.clone(),
2044                                )
2045                                .await;
2046                                sources.record(TerminationSource::Handler, &result);
2047                                if result.is_err() {
2048                                    connection.request_shutdown();
2049                                }
2050                                result?;
2051                                // Keep the raw producer boundary alive while the
2052                                // physical driver drains. Discard without application
2053                                // delivery, rather than fail a late read-side send.
2054                                while transport_incoming.next().await.is_some() {}
2055                                Ok(())
2056                            }
2057                        };
2058                        let other_actors = async {
2059                            let result = futures::try_join!(
2060                                // A ready driver error is authoritative even if its
2061                                // closed channel would also make output forwarding fail.
2062                                transport_driver,
2063                                // Protocol layer: OutgoingMessage -> RawJsonRpcMessage
2064                                outgoing_actor::outgoing_protocol_actor(
2065                                    outgoing_rx,
2066                                    pending_replies,
2067                                    transport_outgoing_tx,
2068                                    protocol_compat,
2069                                    foreground_done,
2070                                )
2071                                .inspect(
2072                                    |result| sources.record(TerminationSource::Transport, result)
2073                                ),
2074                            );
2075                            // Signal before awaiting an underway close callback.
2076                            if result.is_err() {
2077                                connection.request_shutdown();
2078                            }
2079                            result?;
2080                            Ok(())
2081                        };
2082
2083                        // Keep close callbacks alive when another core actor fails.
2084                        // The outer coordination provides the same protection when
2085                        // EOF wakes an application task or the foreground.
2086                        run_until_connection_close(
2087                            incoming,
2088                            other_actors,
2089                            connection.incoming_closed.clone(),
2090                        )
2091                        .await
2092                    };
2093
2094                    run_until_connection_close(
2095                        finish_actor_error(background, &connection),
2096                        async {
2097                            let application = async {
2098                                futures::try_join!(
2099                                    finish_actor_error(
2100                                        task_actor::task_actor(new_task_rx, &connection)
2101                                            .inspect(|result| sources
2102                                                .record(TerminationSource::Handler, result),),
2103                                        &connection,
2104                                    ),
2105                                    finish_actor_error(
2106                                        runner
2107                                            .run_with_connection_to(connection.clone())
2108                                            .inspect(|result| sources
2109                                                .record(TerminationSource::Handler, result),),
2110                                        &connection,
2111                                    ),
2112                                )?;
2113                                Ok(())
2114                            };
2115                            let result = run_until_connection_close(
2116                                application,
2117                                async {
2118                                    let result = main_fn(connection.clone()).await;
2119                                    sources.record(TerminationSource::Foreground, &result);
2120                                    connection.request_shutdown();
2121                                    if result.is_ok() {
2122                                        // Stop new incoming delivery immediately,
2123                                        // including during the callback cleanup phase.
2124                                        let _ = foreground_succeeded_tx.send(());
2125                                    }
2126                                    // Shutdown cancels local consumers, not remote
2127                                    // requests. Do not add cancellation traffic while
2128                                    // dropping those consumers before the drain.
2129                                    connection.pending_replies.disarm_cancellations();
2130                                    // The application actor (including actual scoped
2131                                    // runners) remains polled while supervisors finish.
2132                                    // Only then may ordinary application tasks drop.
2133                                    connection.wait_protected_operations().await;
2134                                    result
2135                                },
2136                                connection.incoming_closed.clone(),
2137                            )
2138                            .await?;
2139                            let _ = foreground_done_tx.send(());
2140                            let drained = connection
2141                                .drain_outgoing(transport_finish, wait_owned_transport)
2142                                .await;
2143                            sources.record(TerminationSource::Transport, &drained);
2144                            drained?;
2145                            Ok(result)
2146                        },
2147                        connection.incoming_closed.clone(),
2148                    )
2149                    .await
2150                };
2151                // Every actor, handler, and task has completed or been
2152                // dropped by the time `driver` resolves.
2153                let result = driver.await;
2154                termination_guard.finish(match &result {
2155                    // A foreground that finishes after observing EOF still
2156                    // ended at a clean EOF boundary.
2157                    Ok(_) if connection.incoming_closed.is_closing() => {
2158                        ConnectionTermination::IncomingClosed
2159                    }
2160                    Ok(_) => ConnectionTermination::LocalShutdown,
2161                    Err(error) => sources.classify(error.clone()),
2162                });
2163                result
2164            }
2165        });
2166
2167        (connection, future)
2168    }
2169}
2170
2171/// Defer an actor error without dropping the other actors that drive owned
2172/// cleanup or an underway close callback. Physical output drain stays separate.
2173async fn finish_actor_error<R: Role>(
2174    actor: impl Future<Output = Result<(), crate::Error>>,
2175    connection: &ConnectionTo<R>,
2176) -> Result<(), crate::Error> {
2177    let result = actor.await;
2178    if result.is_err() {
2179        connection.request_shutdown();
2180        if connection.incoming_closed.is_closing() {
2181            connection.incoming_closed.closed().await;
2182        }
2183        connection.wait_protected_operations().await;
2184    }
2185    result
2186}
2187
2188#[cfg(feature = "unstable_mcp_over_acp")]
2189impl<
2190    Host: Role,
2191    Handler: HandleDispatchFrom<Host::Counterpart>,
2192    Runner: RunWithConnectionTo<Host::Counterpart>,
2193    Close: HandleConnectionClose<Host::Counterpart>,
2194> Builder<Host, Handler, Runner, Close, RawConnectionContext>
2195{
2196    /// Add an MCP server to protocol v1 session setup requests proxied through
2197    /// this connection.
2198    ///
2199    /// The same native MCP server declaration is added to new, load, and resume
2200    /// requests, plus fork requests when `unstable_session_fork` is enabled.
2201    ///
2202    /// Only applicable to proxies. Use the same method on `V2Builder` to
2203    /// attach the server to protocol v2 setup requests.
2204    pub fn with_mcp_server(
2205        self,
2206        mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
2207    ) -> Builder<
2208        Host,
2209        impl HandleDispatchFrom<Host::Counterpart>,
2210        impl RunWithConnectionTo<Host::Counterpart>,
2211        Close,
2212        RawConnectionContext,
2213    >
2214    where
2215        Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
2216    {
2217        let (handler, runner) = mcp_server.into_handler_and_runner();
2218        self.with_handler(handler).with_runner(runner)
2219    }
2220}
2221
2222#[cfg(all(feature = "unstable_mcp_over_acp", feature = "unstable_protocol_v2"))]
2223impl<
2224    Host: Role,
2225    Handler: HandleDispatchFrom<Host::Counterpart>,
2226    Runner: RunWithConnectionTo<Host::Counterpart>,
2227    Close: HandleConnectionClose<Host::Counterpart>,
2228> Builder<Host, Handler, Runner, Close, V2ConnectionContext>
2229{
2230    /// Add an MCP server to protocol v2 session setup requests proxied through
2231    /// this connection.
2232    ///
2233    /// The same native MCP server declaration is added to new and resume
2234    /// requests, plus fork requests when `unstable_session_fork` is enabled.
2235    /// Unrelated request fields are preserved exactly.
2236    ///
2237    /// Only applicable to proxies.
2238    pub fn with_mcp_server(
2239        self,
2240        mcp_server: McpServer<Host::Counterpart, impl RunWithConnectionTo<Host::Counterpart>>,
2241    ) -> Builder<
2242        Host,
2243        impl HandleDispatchFrom<Host::Counterpart>,
2244        impl RunWithConnectionTo<Host::Counterpart>,
2245        Close,
2246        V2ConnectionContext,
2247    >
2248    where
2249        Host::Counterpart: HasPeer<Agent> + HasPeer<Client>,
2250    {
2251        let (handler, runner) = mcp_server.into_v2_handler_and_runner();
2252        self.with_handler(handler).with_runner(runner)
2253    }
2254}
2255
2256impl<R, H, Run, Close, Context> ConnectTo<R::Counterpart> for Builder<R, H, Run, Close, Context>
2257where
2258    R: Role,
2259    H: HandleDispatchFrom<R::Counterpart> + 'static,
2260    Run: RunWithConnectionTo<R::Counterpart> + 'static,
2261    Close: HandleConnectionClose<R::Counterpart> + 'static,
2262    Context: ConnectionContext,
2263{
2264    async fn connect_to(self, client: impl ConnectTo<R>) -> Result<(), crate::Error> {
2265        Builder::connect_to(self, client).await
2266    }
2267}
2268
2269/// The payload sent through the response oneshot channel.
2270///
2271/// Includes the response value and an optional ack channel for dispatch loop
2272/// synchronization.
2273pub(crate) struct ResponsePayload {
2274    /// The response result - either the JSON value or an error.
2275    pub(crate) result: Result<serde_json::Value, crate::Error>,
2276
2277    /// Optional acknowledgment channel for dispatch loop synchronization.
2278    ///
2279    /// When present, the receiver must send on this channel to signal that
2280    /// response processing is complete, allowing the dispatch loop to continue
2281    /// to the next message.
2282    ///
2283    /// This is present when ordered response consumption was selected before
2284    /// the response was routed during its original dispatch. Public callback
2285    /// consumption and framework-owned ordered blocking transforms can hold
2286    /// the dispatch loop; ordinary blocking consumers, local error paths, and
2287    /// responses routed later do not.
2288    pub(crate) ack_tx: Option<oneshot::Sender<()>>,
2289}
2290
2291type ResponseRouteHook =
2292    Box<dyn FnOnce(&str, &serde_json::Value) -> Result<(), crate::Error> + Send>;
2293
2294/// A prerequisite that must complete before an outgoing request is published
2295/// to the transport.
2296struct RequestReadiness {
2297    future: BoxFuture<'static, Result<(), crate::Error>>,
2298}
2299
2300impl RequestReadiness {
2301    fn new(future: impl Future<Output = Result<(), crate::Error>> + Send + 'static) -> Self {
2302        Self {
2303            future: future.boxed(),
2304        }
2305    }
2306}
2307
2308impl Future for RequestReadiness {
2309    type Output = Result<(), crate::Error>;
2310
2311    fn poll(
2312        mut self: std::pin::Pin<&mut Self>,
2313        cx: &mut std::task::Context<'_>,
2314    ) -> std::task::Poll<Self::Output> {
2315        self.future.as_mut().poll(cx)
2316    }
2317}
2318
2319impl Debug for RequestReadiness {
2320    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2321        formatter
2322            .debug_struct("RequestReadiness")
2323            .finish_non_exhaustive()
2324    }
2325}
2326
2327impl std::fmt::Debug for ResponsePayload {
2328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2329        f.debug_struct("ResponsePayload")
2330            .field("result", &self.result)
2331            .field("ack_tx", &self.ack_tx.as_ref().map(|_| "..."))
2332            .finish()
2333    }
2334}
2335
2336#[derive(Clone, Debug, Default)]
2337struct ResponseOrdering {
2338    ordered: Arc<AtomicBool>,
2339}
2340
2341impl ResponseOrdering {
2342    fn mark_ordered(&self) {
2343        self.ordered.store(true, Ordering::Release);
2344    }
2345
2346    fn is_ordered(&self) -> bool {
2347        self.ordered.load(Ordering::Acquire)
2348    }
2349}
2350
2351struct PendingReply {
2352    method: String,
2353    role_id: RoleId,
2354    sender: oneshot::Sender<ResponsePayload>,
2355    cancellation_disarm: SentRequestCancellationDisarm,
2356    ordering: ResponseOrdering,
2357    response_route_hook: Option<ResponseRouteHook>,
2358    /// Set only by `V2ConnectionTo::send_initialize`, whose typed outcome
2359    /// reports a lower-version initialize response as a success.
2360    accepts_lower_initialize_version: bool,
2361}
2362
2363impl PendingReply {
2364    fn fail(self, error: crate::Error) {
2365        self.cancellation_disarm.disarm();
2366        if self
2367            .sender
2368            .send(ResponsePayload {
2369                result: Err(error),
2370                ack_tx: None,
2371            })
2372            .is_err()
2373        {
2374            tracing::trace!(method = %self.method, "Pending request was already dropped");
2375        }
2376    }
2377
2378    fn fail_incoming_closed(self) {
2379        let error = incoming_transport_closed_error(&self.method);
2380        self.fail(error);
2381    }
2382}
2383
2384#[derive(Default)]
2385struct PendingRepliesInner {
2386    incoming_closed: bool,
2387    termination: Option<ConnectionTermination>,
2388    replies: HashMap<RequestId, PendingReply>,
2389}
2390
2391#[derive(Clone, Default)]
2392struct PendingReplies {
2393    inner: Arc<Mutex<PendingRepliesInner>>,
2394}
2395
2396impl PendingReplies {
2397    fn registrar(&self) -> PendingRepliesRegistrar {
2398        PendingRepliesRegistrar {
2399            inner: Arc::downgrade(&self.inner),
2400        }
2401    }
2402
2403    fn contains(&self, id: &RequestId) -> bool {
2404        self.inner
2405            .lock()
2406            .expect("pending replies mutex poisoned")
2407            .replies
2408            .contains_key(id)
2409    }
2410
2411    fn remove(&self, id: &RequestId) -> Option<PendingReply> {
2412        self.inner
2413            .lock()
2414            .expect("pending replies mutex poisoned")
2415            .replies
2416            .remove(id)
2417    }
2418
2419    /// Atomically reject new subscriptions and fail every existing one.
2420    fn close_incoming(&self) -> usize {
2421        let replies = {
2422            let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
2423            inner.incoming_closed = true;
2424            std::mem::take(&mut inner.replies)
2425        };
2426        let count = replies.len();
2427        for (_, reply) in replies {
2428            reply.fail_incoming_closed();
2429        }
2430        count
2431    }
2432
2433    /// Atomically reject new subscriptions and fail every remaining one
2434    /// because the driver has terminated.
2435    fn terminate(&self, termination: &ConnectionTermination) -> usize {
2436        let replies = {
2437            let mut inner = self.inner.lock().expect("pending replies mutex poisoned");
2438            inner.termination = Some(termination.clone());
2439            std::mem::take(&mut inner.replies)
2440        };
2441        let count = replies.len();
2442        for (_, reply) in replies {
2443            let error = connection_terminated_error(&reply.method, termination);
2444            reply.fail(error);
2445        }
2446        count
2447    }
2448}
2449
2450/// A non-owning handle used to register a request before it enters the
2451/// outgoing queue. Keeping this weak prevents escaped [`ConnectionTo`] clones
2452/// from extending the lifetime of response senders after the driver stops.
2453#[derive(Clone)]
2454struct PendingRepliesRegistrar {
2455    inner: Weak<Mutex<PendingRepliesInner>>,
2456}
2457
2458impl PendingRepliesRegistrar {
2459    fn disarm_cancellations(&self) {
2460        if let Some(inner) = self.inner.upgrade() {
2461            let inner = inner.lock().expect("pending replies mutex poisoned");
2462            for reply in inner.replies.values() {
2463                reply.cancellation_disarm.disarm();
2464            }
2465        }
2466    }
2467
2468    /// Register a response destination before the request becomes observable.
2469    ///
2470    /// Returns an error after failing `reply` when EOF has already made a
2471    /// response impossible or the connection driver is no longer running.
2472    fn subscribe(
2473        &self,
2474        id: RequestId,
2475        reply: PendingReply,
2476        incoming_closed: &IncomingClosed,
2477    ) -> Result<(), crate::Error> {
2478        let Some(inner) = self.inner.upgrade() else {
2479            let error = if incoming_closed.is_closing() {
2480                incoming_transport_closed_error(&reply.method)
2481            } else if let Some(termination) = incoming_closed.termination() {
2482                connection_terminated_error(&reply.method, &termination)
2483            } else {
2484                crate::util::internal_error(format!(
2485                    "failed to send outgoing request `{}`: connection is no longer running",
2486                    reply.method
2487                ))
2488            };
2489            reply.fail(error.clone());
2490            return Err(error);
2491        };
2492
2493        let result = {
2494            let mut inner = inner.lock().expect("pending replies mutex poisoned");
2495            if inner.incoming_closed {
2496                Err((reply, None))
2497            } else if let Some(termination) = &inner.termination {
2498                Err((reply, Some(termination.clone())))
2499            } else {
2500                Ok(inner.replies.insert(id, reply))
2501            }
2502        };
2503
2504        match result {
2505            Err((rejected, termination)) => {
2506                let error = match termination {
2507                    Some(termination) => {
2508                        connection_terminated_error(&rejected.method, &termination)
2509                    }
2510                    None => incoming_transport_closed_error(&rejected.method),
2511                };
2512                rejected.fail(error.clone());
2513                Err(error)
2514            }
2515            Ok(replaced) => {
2516                if let Some(replaced) = replaced {
2517                    replaced.fail(
2518                        crate::Error::internal_error()
2519                            .data("outgoing request ID was reused before its response arrived"),
2520                    );
2521                }
2522                Ok(())
2523            }
2524        }
2525    }
2526
2527    fn remove(&self, id: &RequestId) -> Option<PendingReply> {
2528        self.inner
2529            .upgrade()?
2530            .lock()
2531            .expect("pending replies mutex poisoned")
2532            .replies
2533            .remove(id)
2534    }
2535}
2536
2537impl Debug for PendingRepliesRegistrar {
2538    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2539        formatter
2540            .debug_struct("PendingRepliesRegistrar")
2541            .field("is_connected", &(self.inner.strong_count() > 0))
2542            .finish()
2543    }
2544}
2545
2546/// A request-local marker that is set when the peer asks to cancel the request.
2547///
2548/// Request handlers can get this handle from [`Responder::cancellation`] and
2549/// use it from spawned work to stop long-running request processing
2550/// cooperatively.
2551#[derive(Clone)]
2552pub struct RequestCancellation {
2553    state: Arc<RequestCancellationState>,
2554}
2555
2556struct RequestCancellationState {
2557    cancelled: AtomicBool,
2558    signal_tx: Mutex<Option<oneshot::Sender<()>>>,
2559    signal_rx: future::Shared<BoxFuture<'static, ()>>,
2560}
2561
2562impl RequestCancellation {
2563    fn new() -> Self {
2564        let (signal_tx, signal_rx) = oneshot::channel();
2565        let signal_rx = signal_rx.map(|_| ()).boxed().shared();
2566        Self {
2567            state: Arc::new(RequestCancellationState {
2568                cancelled: AtomicBool::new(false),
2569                signal_tx: Mutex::new(Some(signal_tx)),
2570                signal_rx,
2571            }),
2572        }
2573    }
2574
2575    /// Wait until the peer sends `$/cancel_request` for this request.
2576    ///
2577    /// If cancellation was already requested, this returns immediately.
2578    pub async fn cancelled(&self) {
2579        self.state.signal_rx.clone().await;
2580    }
2581
2582    /// Run request work until it completes or the peer asks to cancel it.
2583    ///
2584    /// If cancellation is requested first, this returns
2585    /// [`Error::request_cancelled`]. This is a convenience for request handlers
2586    /// that want to respond with the normal result or the standard
2587    /// cancellation error.
2588    ///
2589    /// When cancellation wins, `future` is dropped: work stops at its next
2590    /// await point, partial results are lost, and any cleanup must happen in
2591    /// `Drop` implementations. Handlers that need to flush partial results or
2592    /// run async cleanup should instead watch [`cancelled`](Self::cancelled)
2593    /// or poll [`is_cancelled`](Self::is_cancelled) from inside the work.
2594    ///
2595    /// [`Error::request_cancelled`]: crate::Error::request_cancelled
2596    pub async fn run_until_cancelled<T>(
2597        &self,
2598        future: impl std::future::Future<Output = Result<T, crate::Error>>,
2599    ) -> Result<T, crate::Error> {
2600        if self.is_cancelled() {
2601            return Err(crate::Error::request_cancelled());
2602        }
2603
2604        match future::select(pin!(future), pin!(self.cancelled())).await {
2605            Either::Left((result, _)) => result,
2606            Either::Right(((), _)) => Err(crate::Error::request_cancelled()),
2607        }
2608    }
2609
2610    /// Returns whether the peer has already requested cancellation.
2611    #[must_use]
2612    pub fn is_cancelled(&self) -> bool {
2613        self.state.cancelled.load(Ordering::Acquire)
2614    }
2615
2616    fn cancel(&self) {
2617        if self.state.cancelled.swap(true, Ordering::AcqRel) {
2618            return;
2619        }
2620
2621        let signal_tx = self
2622            .state
2623            .signal_tx
2624            .lock()
2625            .expect("request cancellation signal mutex poisoned")
2626            .take();
2627
2628        // Complete the oneshot outside the lock: it wakes waiters, and
2629        // arbitrary waker code must not observe the lock held.
2630        if let Some(signal_tx) = signal_tx {
2631            let _ = signal_tx.send(());
2632        }
2633    }
2634}
2635
2636impl Debug for RequestCancellation {
2637    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2638        formatter
2639            .debug_struct("RequestCancellation")
2640            .field("is_cancelled", &self.is_cancelled())
2641            .finish_non_exhaustive()
2642    }
2643}
2644
2645/// Per-request cancellation state tracked by [`RequestCancellationRegistry`].
2646///
2647/// The full [`RequestCancellation`] marker (with its wakeup machinery) is only
2648/// allocated once a handler asks for it via [`Responder::cancellation`]; until
2649/// then an incoming `$/cancel_request` just flips the entry to `Cancelled`.
2650/// This keeps the per-request cost of the registry to a single map entry.
2651#[derive(Debug)]
2652enum RequestCancellationEntry {
2653    /// The request is in flight; no marker handed out, no cancellation yet.
2654    Armed,
2655    /// `$/cancel_request` arrived before a marker was handed out.
2656    Cancelled,
2657    /// A marker was handed out via [`Responder::cancellation`].
2658    Marker(RequestCancellation),
2659}
2660
2661/// A registered request's cancellation state, tagged with the generation of
2662/// its registration.
2663///
2664/// The generation distinguishes a registration from earlier ones that used
2665/// the same request ID, so that when a (protocol-violating) peer reuses the
2666/// ID of a request that is still in flight, the stale request's responder can
2667/// neither remove nor observe the cancellation state of the newer request.
2668#[derive(Debug)]
2669struct RequestCancellationSlot {
2670    generation: u64,
2671    entry: RequestCancellationEntry,
2672}
2673
2674#[derive(Debug, Default)]
2675struct RequestCancellationRegistryInner {
2676    slots: HashMap<RequestId, RequestCancellationSlot>,
2677    next_generation: u64,
2678}
2679
2680#[derive(Clone, Debug, Default)]
2681struct RequestCancellationRegistry {
2682    inner: Arc<Mutex<RequestCancellationRegistryInner>>,
2683}
2684
2685#[derive(Debug)]
2686struct ResponderCancellation {
2687    id: RequestId,
2688    generation: u64,
2689    registry: RequestCancellationRegistry,
2690}
2691
2692impl RequestCancellationRegistry {
2693    fn new() -> Self {
2694        Self::default()
2695    }
2696
2697    fn register(&self, id: &RequestId) -> ResponderCancellation {
2698        let generation = {
2699            let mut inner = self
2700                .inner
2701                .lock()
2702                .expect("request cancellation registry mutex poisoned");
2703            let generation = inner.next_generation;
2704            inner.next_generation += 1;
2705            if inner
2706                .slots
2707                .insert(
2708                    id.clone(),
2709                    RequestCancellationSlot {
2710                        generation,
2711                        entry: RequestCancellationEntry::Armed,
2712                    },
2713                )
2714                .is_some()
2715            {
2716                tracing::debug!(
2717                    ?id,
2718                    "peer reused the ID of a request that is still in flight"
2719                );
2720            }
2721            generation
2722        };
2723        ResponderCancellation {
2724            id: id.clone(),
2725            generation,
2726            registry: self.clone(),
2727        }
2728    }
2729
2730    /// Get the cancellation marker for a registered request, creating it on
2731    /// first use. Repeated calls return markers that share the same state.
2732    ///
2733    /// Exception: when the registration is stale (a protocol-violating peer
2734    /// reused this request ID and the slot now belongs to a newer request, or
2735    /// was already removed by it), every call returns a fresh *detached*
2736    /// marker. Detached markers can never fire, and detached markers from
2737    /// repeated calls do not share state with each other.
2738    fn marker(&self, id: &RequestId, generation: u64) -> RequestCancellation {
2739        let mut inner = self
2740            .inner
2741            .lock()
2742            .expect("request cancellation registry mutex poisoned");
2743        let Some(slot) = inner.slots.get_mut(id) else {
2744            // The slot lives as long as the responder that owns it, so this
2745            // is only reachable if the peer reused this request ID and the
2746            // newer request's responder already removed the replacement slot.
2747            // Hand out a detached marker rather than panicking.
2748            return RequestCancellation::new();
2749        };
2750        if slot.generation != generation {
2751            // The peer reused this request ID while the request was still in
2752            // flight, and the slot now belongs to the newer request. Hand the
2753            // stale responder a detached marker instead of cross-wiring the
2754            // two requests' cancellation states.
2755            return RequestCancellation::new();
2756        }
2757        let entry = &mut slot.entry;
2758        match entry {
2759            RequestCancellationEntry::Marker(marker) => marker.clone(),
2760            RequestCancellationEntry::Armed => {
2761                let marker = RequestCancellation::new();
2762                *entry = RequestCancellationEntry::Marker(marker.clone());
2763                marker
2764            }
2765            RequestCancellationEntry::Cancelled => {
2766                // No one can be waiting on a marker that did not exist yet,
2767                // so firing it while holding the registry lock is fine.
2768                let marker = RequestCancellation::new();
2769                marker.cancel();
2770                *entry = RequestCancellationEntry::Marker(marker.clone());
2771                marker
2772            }
2773        }
2774    }
2775
2776    fn cancel_if_requested(&self, dispatch: &Dispatch) -> Result<bool, crate::Error> {
2777        let Some(request_id) = cancellation_request_id(dispatch)? else {
2778            return Ok(false);
2779        };
2780        Ok(self.cancel(&request_id))
2781    }
2782
2783    /// Mark whichever request currently owns `request_id` as cancelled.
2784    fn cancel(&self, request_id: &RequestId) -> bool {
2785        let marker = {
2786            let mut inner = self
2787                .inner
2788                .lock()
2789                .expect("request cancellation registry mutex poisoned");
2790            let Some(slot) = inner.slots.get_mut(request_id) else {
2791                return false;
2792            };
2793            let entry = &mut slot.entry;
2794            match entry {
2795                RequestCancellationEntry::Marker(marker) => marker.clone(),
2796                RequestCancellationEntry::Cancelled => return true,
2797                RequestCancellationEntry::Armed => {
2798                    *entry = RequestCancellationEntry::Cancelled;
2799                    return true;
2800                }
2801            }
2802        };
2803
2804        // Fire the marker outside the registry lock: waking waiters runs
2805        // arbitrary waker code that must not observe the lock held.
2806        marker.cancel();
2807        true
2808    }
2809
2810    /// Remove the slot for `request_id`, but only if it still belongs to the
2811    /// registration identified by `generation`.
2812    fn remove(&self, request_id: &RequestId, generation: u64) {
2813        let mut inner = self
2814            .inner
2815            .lock()
2816            .expect("request cancellation registry mutex poisoned");
2817        if inner
2818            .slots
2819            .get(request_id)
2820            .is_some_and(|slot| slot.generation == generation)
2821        {
2822            inner.slots.remove(request_id);
2823        }
2824    }
2825}
2826
2827impl ResponderCancellation {
2828    fn cancellation(&self) -> RequestCancellation {
2829        self.registry.marker(&self.id, self.generation)
2830    }
2831}
2832
2833impl Drop for ResponderCancellation {
2834    fn drop(&mut self) {
2835        self.registry.remove(&self.id, self.generation);
2836    }
2837}
2838
2839fn cancellation_request_id(dispatch: &Dispatch) -> Result<Option<RequestId>, crate::Error> {
2840    let Dispatch::Notification(message) = dispatch else {
2841        return Ok(None);
2842    };
2843    cancellation_request_id_from_message(message)
2844}
2845
2846fn cancellation_request_id_from_message(
2847    message: &UntypedMessage,
2848) -> Result<Option<RequestId>, crate::Error> {
2849    let (method, params) = peel_successor_envelopes(&message.method, &message.params);
2850    if !crate::schema::v1::CancelRequestNotification::matches_method(method) {
2851        return Ok(None);
2852    }
2853
2854    let notification = crate::schema::v1::CancelRequestNotification::parse_message(method, params)?;
2855    Ok(Some(notification.request_id))
2856}
2857
2858/// Peel any [`SuccessorMessage`] envelopes off a notification by reference,
2859/// returning the innermost method and params.
2860///
2861/// This only peeks at the envelope's `method`/`params` fields instead of
2862/// deserializing the envelope, for two reasons:
2863///
2864/// - It avoids deep-cloning the params of every wrapped notification on the
2865///   hot dispatch path just to inspect the inner method name.
2866/// - It is deliberately lenient: a malformed envelope is left as-is here and
2867///   flows on to the handler chain, which is responsible for reporting it.
2868///
2869/// [`SuccessorMessage`]: crate::schema::SuccessorMessage
2870fn peel_successor_envelopes<'message>(
2871    mut method: &'message str,
2872    mut params: &'message serde_json::Value,
2873) -> (&'message str, &'message serde_json::Value) {
2874    while crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
2875        let Some(inner_method) = params.get("method").and_then(serde_json::Value::as_str) else {
2876            break;
2877        };
2878        method = inner_method;
2879        params = params.get("params").unwrap_or(&serde_json::Value::Null);
2880    }
2881    (method, params)
2882}
2883
2884/// Whether a notification is a `$/cancel_request`, even when it is still
2885/// wrapped in `_proxy/successor` envelopes.
2886///
2887/// `$/cancel_request` is connection-scoped: its `requestId` was allocated on
2888/// the connection the notification arrived over and means nothing on any
2889/// other connection. Generic forwarding code (such as
2890/// [`ConnectionTo::send_proxied_message_to`]) uses this check to drop the raw
2891/// notification instead of tunneling it across a hop; the cancellation still
2892/// propagates because [`forward_response_to`](SentRequest::forward_response_to)
2893/// re-issues it with the forwarded request's own ID.
2894///
2895/// Checking a notification whose method is not the successor envelope is a
2896/// plain method-name comparison. Only successor-wrapped notifications pay for
2897/// a serialization to peel the envelope.
2898#[must_use]
2899pub fn is_cancel_request_notification<N: JsonRpcNotification>(notification: &N) -> bool {
2900    let method = notification.method();
2901    if crate::schema::v1::CancelRequestNotification::matches_method(method) {
2902        return true;
2903    }
2904    if !crate::schema::SuccessorMessage::<UntypedMessage>::matches_method(method) {
2905        return false;
2906    }
2907
2908    match notification.to_untyped_message() {
2909        Ok(untyped) => {
2910            let (method, _params) = peel_successor_envelopes(&untyped.method, &untyped.params);
2911            crate::schema::v1::CancelRequestNotification::matches_method(method)
2912        }
2913        Err(error) => {
2914            tracing::debug!(
2915                ?error,
2916                "failed to inspect successor-wrapped notification for cancellation"
2917            );
2918            false
2919        }
2920    }
2921}
2922
2923/// Messages send to be serialized over the transport.
2924#[derive(Clone)]
2925enum ResponseDestination {
2926    Individual(IndividualResponseSlot),
2927    Batch(BatchResponseSlot),
2928}
2929
2930impl std::fmt::Debug for ResponseDestination {
2931    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2932        match self {
2933            Self::Individual(slot) => formatter.debug_tuple("Individual").field(slot).finish(),
2934            Self::Batch(slot) => formatter.debug_tuple("Batch").field(slot).finish(),
2935        }
2936    }
2937}
2938
2939impl ResponseDestination {
2940    fn individual() -> Self {
2941        Self::Individual(IndividualResponseSlot::default())
2942    }
2943
2944    fn batch(slot_count: usize) -> (impl Iterator<Item = Self>, BatchDispatchCompletion) {
2945        let state = Arc::new(Mutex::new(BatchResponseState {
2946            remaining: slot_count,
2947            responses: (0..slot_count).map(|_| None).collect(),
2948            abandoned: (0..slot_count).map(|_| None).collect(),
2949            active_handler_attempts: (0..slot_count).map(|_| 0).collect(),
2950            dispatch_complete: false,
2951            emitted: false,
2952        }));
2953
2954        (
2955            (0..slot_count).map({
2956                let state = state.clone();
2957                move |index| {
2958                    Self::Batch(BatchResponseSlot {
2959                        state: state.clone(),
2960                        index,
2961                    })
2962                }
2963            }),
2964            BatchDispatchCompletion { state },
2965        )
2966    }
2967
2968    fn complete(self, response: RawJsonRpcMessage) -> Option<TransportFrame> {
2969        match self {
2970            Self::Individual(slot) => slot.complete(response),
2971            Self::Batch(slot) => slot.complete(response).map(batch_response_frame),
2972        }
2973    }
2974
2975    fn abandon(self, fallback: RawJsonRpcMessage) -> Option<TransportFrame> {
2976        match self {
2977            Self::Individual(_) => None,
2978            Self::Batch(slot) => slot.abandon(fallback).map(batch_response_frame),
2979        }
2980    }
2981
2982    fn is_batch(&self) -> bool {
2983        matches!(self, Self::Batch(_))
2984    }
2985
2986    fn begin_handler_attempt(
2987        &self,
2988        message_tx: OutgoingMessageTx,
2989    ) -> Option<ResponderHandlerAttempt> {
2990        let Self::Batch(slot) = self else {
2991            return None;
2992        };
2993        slot.begin_handler_attempt();
2994        Some(ResponderHandlerAttempt {
2995            message_tx,
2996            destination: self.clone(),
2997        })
2998    }
2999
3000    fn finish_handler_attempt(self) -> Option<TransportFrame> {
3001        match self {
3002            Self::Individual(_) => None,
3003            Self::Batch(slot) => slot.finish_handler_attempt().map(batch_response_frame),
3004        }
3005    }
3006}
3007
3008#[derive(Clone, Debug, Default)]
3009struct IndividualResponseSlot {
3010    completed: Arc<AtomicBool>,
3011}
3012
3013impl IndividualResponseSlot {
3014    fn complete(self, response: RawJsonRpcMessage) -> Option<TransportFrame> {
3015        if self.completed.swap(true, Ordering::AcqRel) {
3016            tracing::warn!("Ignoring duplicate completion of JSON-RPC request");
3017            return None;
3018        }
3019
3020        Some(TransportFrame::Single(response))
3021    }
3022}
3023
3024fn batch_response_frame(responses: Vec<RawJsonRpcMessage>) -> TransportFrame {
3025    TransportFrame::Batch(
3026        TransportBatch::from_messages(responses)
3027            .expect("a completed JSON-RPC response batch is non-empty"),
3028    )
3029}
3030
3031#[derive(Clone)]
3032struct BatchDispatchCompletion {
3033    state: Arc<Mutex<BatchResponseState>>,
3034}
3035
3036impl std::fmt::Debug for BatchDispatchCompletion {
3037    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3038        formatter
3039            .debug_struct("BatchDispatchCompletion")
3040            .finish_non_exhaustive()
3041    }
3042}
3043
3044impl BatchDispatchCompletion {
3045    fn complete(self) -> Option<TransportFrame> {
3046        let mut state = self
3047            .state
3048            .lock()
3049            .expect("batch response accumulator mutex poisoned");
3050        if state.dispatch_complete {
3051            tracing::warn!("Ignoring duplicate JSON-RPC batch dispatch completion");
3052            return None;
3053        }
3054        state.dispatch_complete = true;
3055        for index in 0..state.responses.len() {
3056            promote_abandoned_response(&mut state, index);
3057        }
3058        take_completed_batch(&mut state).map(batch_response_frame)
3059    }
3060}
3061
3062fn promote_abandoned_response(state: &mut BatchResponseState, index: usize) {
3063    if state.active_handler_attempts[index] == 0
3064        && state.responses[index].is_none()
3065        && let Some(fallback) = state.abandoned[index].take()
3066    {
3067        state.responses[index] = Some(fallback);
3068        state.remaining -= 1;
3069    }
3070}
3071
3072fn take_completed_batch(state: &mut BatchResponseState) -> Option<Vec<RawJsonRpcMessage>> {
3073    if !state.dispatch_complete || state.remaining != 0 || state.emitted {
3074        return None;
3075    }
3076
3077    state.emitted = true;
3078    Some(
3079        state
3080            .responses
3081            .iter_mut()
3082            .map(|response| {
3083                response
3084                    .take()
3085                    .expect("completed JSON-RPC batch has every response slot")
3086            })
3087            .collect(),
3088    )
3089}
3090
3091#[derive(Clone)]
3092struct BatchResponseSlot {
3093    state: Arc<Mutex<BatchResponseState>>,
3094    index: usize,
3095}
3096
3097impl std::fmt::Debug for BatchResponseSlot {
3098    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3099        formatter
3100            .debug_struct("BatchResponseSlot")
3101            .field("index", &self.index)
3102            .finish_non_exhaustive()
3103    }
3104}
3105
3106impl BatchResponseSlot {
3107    fn begin_handler_attempt(&self) {
3108        let mut state = self
3109            .state
3110            .lock()
3111            .expect("batch response accumulator mutex poisoned");
3112        state.active_handler_attempts[self.index] += 1;
3113    }
3114
3115    fn finish_handler_attempt(self) -> Option<Vec<RawJsonRpcMessage>> {
3116        let mut state = self
3117            .state
3118            .lock()
3119            .expect("batch response accumulator mutex poisoned");
3120        state.active_handler_attempts[self.index] = state.active_handler_attempts[self.index]
3121            .checked_sub(1)
3122            .expect("handler attempt completion without a matching start");
3123        if state.dispatch_complete {
3124            promote_abandoned_response(&mut state, self.index);
3125        }
3126        take_completed_batch(&mut state)
3127    }
3128
3129    fn complete(self, response: RawJsonRpcMessage) -> Option<Vec<RawJsonRpcMessage>> {
3130        let mut state = self
3131            .state
3132            .lock()
3133            .expect("batch response accumulator mutex poisoned");
3134        if state.emitted {
3135            tracing::warn!(
3136                index = self.index,
3137                "Ignoring response after JSON-RPC batch was already completed"
3138            );
3139            return None;
3140        }
3141        if self.index >= state.responses.len() {
3142            tracing::error!(index = self.index, "Invalid JSON-RPC batch response slot");
3143            return None;
3144        }
3145        if state.responses[self.index].is_some() {
3146            tracing::warn!(
3147                index = self.index,
3148                "Ignoring duplicate completion of JSON-RPC batch response slot"
3149            );
3150            return None;
3151        }
3152
3153        state.abandoned[self.index] = None;
3154        state.responses[self.index] = Some(response);
3155        state.remaining -= 1;
3156        take_completed_batch(&mut state)
3157    }
3158
3159    fn abandon(self, fallback: RawJsonRpcMessage) -> Option<Vec<RawJsonRpcMessage>> {
3160        let mut state = self
3161            .state
3162            .lock()
3163            .expect("batch response accumulator mutex poisoned");
3164        if state.emitted || state.responses[self.index].is_some() {
3165            return None;
3166        }
3167        if state.abandoned[self.index].is_some() {
3168            tracing::warn!(
3169                index = self.index,
3170                "Ignoring duplicate abandonment of JSON-RPC batch response slot"
3171            );
3172            return None;
3173        }
3174
3175        if state.dispatch_complete && state.active_handler_attempts[self.index] == 0 {
3176            state.responses[self.index] = Some(fallback);
3177            state.remaining -= 1;
3178        } else {
3179            state.abandoned[self.index] = Some(fallback);
3180        }
3181        take_completed_batch(&mut state)
3182    }
3183}
3184
3185struct BatchResponseState {
3186    remaining: usize,
3187    responses: Vec<Option<RawJsonRpcMessage>>,
3188    abandoned: Vec<Option<RawJsonRpcMessage>>,
3189    active_handler_attempts: Vec<usize>,
3190    dispatch_complete: bool,
3191    emitted: bool,
3192}
3193
3194#[derive(Clone, Debug)]
3195struct RequestReplyTarget {
3196    id: RequestId,
3197    method: String,
3198    destination: ResponseDestination,
3199}
3200
3201struct ResponderHandlerAttempt {
3202    message_tx: OutgoingMessageTx,
3203    destination: ResponseDestination,
3204}
3205
3206impl Drop for ResponderHandlerAttempt {
3207    fn drop(&mut self) {
3208        if let Err(error) = send_raw_message(
3209            &self.message_tx,
3210            OutgoingMessage::BatchHandlerAttemptComplete {
3211                destination: self.destination.clone(),
3212            },
3213        ) {
3214            tracing::debug!(?error, "could not complete JSON-RPC batch handler attempt");
3215        }
3216    }
3217}
3218
3219#[derive(Clone)]
3220struct ResponseReplyTarget {
3221    id: RequestId,
3222    method: String,
3223    sender: Arc<Mutex<Option<oneshot::Sender<ResponsePayload>>>>,
3224    ordering: ResponseOrdering,
3225    dispatch: ResponseDispatch,
3226}
3227
3228impl ResponseReplyTarget {
3229    fn route(self, result: Result<serde_json::Value, crate::Error>) {
3230        let sender = self
3231            .sender
3232            .lock()
3233            .expect("response reply mutex poisoned")
3234            .take();
3235        let Some(sender) = sender else {
3236            tracing::debug!(
3237                method = %self.method,
3238                id = ?self.id,
3239                "response was already routed to its local awaiter"
3240            );
3241            return;
3242        };
3243
3244        let ack_tx = self.dispatch.acknowledgment(&self.ordering);
3245        if sender.send(ResponsePayload { result, ack_tx }).is_err() {
3246            tracing::debug!(
3247                method = %self.method,
3248                id = ?self.id,
3249                "dropped response because local receiver was gone"
3250            );
3251        }
3252    }
3253}
3254
3255#[derive(Clone, Default)]
3256struct ResponseDispatch {
3257    state: Arc<Mutex<ResponseDispatchState>>,
3258}
3259
3260#[derive(Default)]
3261struct ResponseDispatchState {
3262    complete: bool,
3263    ack_rx: Option<oneshot::Receiver<()>>,
3264}
3265
3266impl ResponseDispatch {
3267    fn acknowledgment(&self, ordering: &ResponseOrdering) -> Option<oneshot::Sender<()>> {
3268        if !ordering.is_ordered() {
3269            return None;
3270        }
3271
3272        let mut state = self.state.lock().expect("response dispatch mutex poisoned");
3273        if state.complete {
3274            return None;
3275        }
3276
3277        let (ack_tx, ack_rx) = oneshot::channel();
3278        let previous_ack = state.ack_rx.replace(ack_rx);
3279        debug_assert!(
3280            previous_ack.is_none(),
3281            "a response dispatch can only be routed once"
3282        );
3283        Some(ack_tx)
3284    }
3285
3286    fn complete(&self) -> Option<oneshot::Receiver<()>> {
3287        let mut state = self.state.lock().expect("response dispatch mutex poisoned");
3288        state.complete = true;
3289        state.ack_rx.take()
3290    }
3291}
3292
3293enum HandlerErrorTarget {
3294    Request(RequestReplyTarget),
3295    Response(ResponseReplyTarget),
3296}
3297
3298impl HandlerErrorTarget {
3299    fn begin_handler_attempt(
3300        &self,
3301        message_tx: &OutgoingMessageTx,
3302    ) -> Option<ResponderHandlerAttempt> {
3303        match self {
3304            Self::Request(target) => target.destination.begin_handler_attempt(message_tx.clone()),
3305            Self::Response(_) => None,
3306        }
3307    }
3308}
3309
3310#[derive(Debug)]
3311enum OutgoingMessage {
3312    /// Close the outgoing application queue and acknowledge after every
3313    /// already-accepted message has entered the raw transport queue.
3314    CloseAfterDraining { done: oneshot::Sender<()> },
3315
3316    /// Mark every entry in an incoming batch as dispatched. A completed
3317    /// response array may only be emitted after this barrier.
3318    BatchDispatchComplete { completion: BatchDispatchCompletion },
3319
3320    /// Finish arbitration for a handler attempt that may have dropped a batch
3321    /// responder immediately before returning an error.
3322    BatchHandlerAttemptComplete { destination: ResponseDestination },
3323
3324    /// Record that a claimed batch request dropped its responder without
3325    /// replying. The fallback remains provisional while its handler attempt is
3326    /// active so a handler error can supply the authoritative response.
3327    AbandonedBatchResponse {
3328        id: RequestId,
3329        method: String,
3330        destination: ResponseDestination,
3331    },
3332
3333    /// Send a request to the server.
3334    Request {
3335        /// id assigned to this request (generated by sender)
3336        id: RequestId,
3337
3338        /// the original method
3339        method: String,
3340
3341        /// The logical message before peer-direction wrapping.
3342        untyped: UntypedMessage,
3343
3344        /// How to transform the logical message for its target peer.
3345        remote_style: crate::role::RemoteStyle,
3346
3347        /// Optional prerequisite that must finish before the request becomes
3348        /// visible on the transport.
3349        readiness: Option<RequestReadiness>,
3350    },
3351
3352    /// Send a notification to the server.
3353    Notification {
3354        /// the message to send; this may have a distinct method
3355        /// depending on the peer
3356        untyped: UntypedMessage,
3357    },
3358
3359    /// Send a response to a message from the server
3360    Response {
3361        id: RequestId,
3362
3363        /// Method of the incoming request this response completes.
3364        method: String,
3365
3366        response: Result<serde_json::Value, crate::Error>,
3367
3368        destination: ResponseDestination,
3369    },
3370
3371    /// Send an Error Response that cannot be correlated to a request ID.
3372    UncorrelatedErrorResponse {
3373        error: crate::Error,
3374        destination: ResponseDestination,
3375    },
3376}
3377
3378/// Return type from JrHandler; indicates whether the request was handled or not.
3379#[must_use]
3380#[derive(Debug)]
3381pub enum Handled<T> {
3382    /// The message was handled
3383    Yes,
3384
3385    /// The message was not handled; returns the original value.
3386    ///
3387    /// If `retry` is true,
3388    No {
3389        /// The message to be passed to subsequent handlers
3390        /// (typically the original message, but it may have been
3391        /// mutated.)
3392        message: T,
3393
3394        /// If true, request the message to be queued and retried with
3395        /// dynamic handlers as they are added.
3396        ///
3397        /// This is used for managing session updates since the dynamic
3398        /// handler for a session cannot be added until the response to the
3399        /// new session request has been processed and there may be updates
3400        /// that get processed at the same time.
3401        retry: bool,
3402    },
3403}
3404
3405/// Trait for converting handler return values into [`Handled`].
3406///
3407/// This trait allows handlers to return either `()` (which becomes `Handled::Yes`)
3408/// or an explicit `Handled<T>` value for more control over handler propagation.
3409pub trait IntoHandled<T> {
3410    /// Convert this value into a `Handled<T>`.
3411    fn into_handled(self) -> Handled<T>;
3412}
3413
3414impl<T> IntoHandled<T> for () {
3415    fn into_handled(self) -> Handled<T> {
3416        Handled::Yes
3417    }
3418}
3419
3420impl<T> IntoHandled<T> for Handled<T> {
3421    fn into_handled(self) -> Handled<T> {
3422        self
3423    }
3424}
3425
3426/// A protocol-v2 connection context.
3427///
3428/// Values of this type are supplied to callbacks registered on a
3429/// [`V2Builder`]. It exposes the general connection operations that are valid
3430/// for protocol v2 while keeping version-specific high-level helpers for other
3431/// protocol versions out of the typed context. The generic JSON-RPC send
3432/// methods remain intentionally schema-agnostic.
3433///
3434/// This is a thin, cheaply cloneable handle to the underlying JSON-RPC
3435/// connection. It intentionally does not implement [`Deref`](std::ops::Deref)
3436/// to [`ConnectionTo`].
3437#[cfg(feature = "unstable_protocol_v2")]
3438#[derive(Clone, Debug)]
3439pub struct V2ConnectionTo<Counterpart: Role> {
3440    inner: ConnectionTo<Counterpart>,
3441}
3442
3443#[cfg(feature = "unstable_protocol_v2")]
3444impl V2ConnectionTo<Agent> {
3445    /// Send the client's `initialize` request and report how the agent
3446    /// negotiated.
3447    ///
3448    /// Unlike [`send_request`](Self::send_request), a v1-only agent's valid v1
3449    /// response resolves successfully as [`InitializeOutcome::LowerVersion`]
3450    /// with the agent's typed response, so the caller can decide whether to
3451    /// fall back to a v1 client implementation. The connection stays
3452    /// uninitialized in that case; the SDK never converts it to v1.
3453    ///
3454    /// The request still resolves with an error for errors returned by the
3455    /// agent (including the `-32022` version mismatch), malformed responses,
3456    /// and transport failures.
3457    ///
3458    /// ```no_run
3459    /// # use agent_client_protocol::schema::{ProtocolVersion, v2};
3460    /// # use agent_client_protocol::{
3461    /// #     Client, ConnectTo, InitializeOutcome, SelectedInitializeResponse,
3462    /// # };
3463    /// # async fn example(agent: impl ConnectTo<Client>) -> agent_client_protocol::Result<()> {
3464    /// let outcome = Client
3465    ///     .v2()
3466    ///     .connect_with(agent, async |cx| {
3467    ///         let request = v2::InitializeRequest::new(
3468    ///             ProtocolVersion::V2,
3469    ///             v2::Implementation::new("example", "0.1.0"),
3470    ///         );
3471    ///         match cx.send_initialize(request).block_task().await? {
3472    ///             InitializeOutcome::Initialized(_response) => {
3473    ///                 // Continue with v2 traffic.
3474    ///                 Ok(None)
3475    ///             }
3476    ///             InitializeOutcome::LowerVersion(mismatch) => Ok(Some(mismatch)),
3477    ///             _ => Err(agent_client_protocol::util::internal_error("unexpected outcome")),
3478    ///         }
3479    ///     })
3480    ///     .await?;
3481    /// if let Some(mismatch) = outcome
3482    ///     && let SelectedInitializeResponse::V1(_v1_response) = mismatch.response
3483    /// {
3484    ///     // Reconnect with a v1 client implementation.
3485    /// }
3486    /// # Ok(())
3487    /// # }
3488    /// ```
3489    pub fn send_initialize(
3490        &self,
3491        request: crate::schema::v2::InitializeRequest,
3492    ) -> SentRequest<InitializeOutcome> {
3493        let mut prepared = self.inner.prepare_request_to_with_options(
3494            self.inner.counterpart(),
3495            protocol_compat::InitializeNegotiation(request),
3496            None,
3497            None,
3498        );
3499        prepared
3500            .publication
3501            .pending_reply
3502            .accepts_lower_initialize_version = true;
3503        prepared.into_sent_request(false)
3504    }
3505}
3506
3507#[cfg(feature = "unstable_protocol_v2")]
3508impl<Counterpart: Role> V2ConnectionTo<Counterpart> {
3509    /// Access the underlying version-neutral connection inside the SDK.
3510    pub(crate) fn raw_connection(&self) -> &ConnectionTo<Counterpart> {
3511        &self.inner
3512    }
3513
3514    /// Return the counterpart role this connection is talking to.
3515    pub fn counterpart(&self) -> Counterpart {
3516        self.inner.counterpart()
3517    }
3518
3519    /// Wait until the incoming transport reaches clean EOF.
3520    pub async fn incoming_closed(&self) {
3521        self.inner.incoming_closed().await;
3522    }
3523
3524    /// Return whether clean incoming-EOF processing has completed.
3525    #[must_use]
3526    pub fn is_incoming_closed(&self) -> bool {
3527        self.inner.is_incoming_closed()
3528    }
3529
3530    /// Wait until the connection driver has terminated, for any reason.
3531    ///
3532    /// See [`ConnectionTo::terminated`].
3533    pub async fn terminated(&self) -> ConnectionTermination {
3534        self.inner.terminated().await
3535    }
3536
3537    /// Return the termination reason if the connection driver has terminated.
3538    #[must_use]
3539    pub fn termination(&self) -> Option<ConnectionTermination> {
3540        self.inner.termination()
3541    }
3542
3543    /// Spawn a task that runs for as long as the JSON-RPC connection is served.
3544    #[track_caller]
3545    pub fn spawn(
3546        &self,
3547        task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
3548    ) -> Result<(), crate::Error> {
3549        self.inner.spawn(task)
3550    }
3551
3552    /// Spawn a JSON-RPC connection in the background.
3553    ///
3554    /// The returned connection context is selected by `builder`; spawning a
3555    /// [`V2Builder`] therefore returns another [`V2ConnectionTo`].
3556    ///
3557    /// ```no_run
3558    /// # use agent_client_protocol::{
3559    /// #     Agent, Client, ConnectTo, Error, V2ConnectionTo,
3560    /// # };
3561    /// # fn example(
3562    /// #     connection: V2ConnectionTo<Agent>,
3563    /// #     transport: impl ConnectTo<Client> + 'static,
3564    /// # ) -> Result<(), Error> {
3565    /// let child: V2ConnectionTo<Agent> =
3566    ///     connection.spawn_connection(Client.v2(), transport)?;
3567    /// # drop(child);
3568    /// # Ok(())
3569    /// # }
3570    /// ```
3571    #[track_caller]
3572    pub fn spawn_connection<R: Role, Context: ConnectionContext>(
3573        &self,
3574        builder: Builder<
3575            R,
3576            impl HandleDispatchFrom<R::Counterpart> + 'static,
3577            impl RunWithConnectionTo<R::Counterpart> + 'static,
3578            impl HandleConnectionClose<R::Counterpart> + 'static,
3579            Context,
3580        >,
3581        transport: impl ConnectTo<R> + 'static,
3582    ) -> Result<Context::Connection<R::Counterpart>, crate::Error> {
3583        self.inner.spawn_connection_with_context(builder, transport)
3584    }
3585
3586    /// Send a request or notification and forward its response appropriately.
3587    pub fn send_proxied_message<Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification>(
3588        &self,
3589        message: Dispatch<Req, Notif>,
3590    ) -> Result<(), crate::Error>
3591    where
3592        Counterpart: HasPeer<Counterpart>,
3593    {
3594        self.inner.send_proxied_message(message)
3595    }
3596
3597    /// Send a request or notification to a specific peer and forward its
3598    /// response appropriately.
3599    pub fn send_proxied_message_to<
3600        Peer: Role,
3601        Req: JsonRpcRequest<Response: Send>,
3602        Notif: JsonRpcNotification,
3603    >(
3604        &self,
3605        peer: Peer,
3606        message: Dispatch<Req, Notif>,
3607    ) -> Result<(), crate::Error>
3608    where
3609        Counterpart: HasPeer<Peer>,
3610    {
3611        self.inner.send_proxied_message_to(peer, message)
3612    }
3613
3614    /// Send an outgoing request to the default counterpart peer.
3615    pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response>
3616    where
3617        Counterpart: HasPeer<Counterpart>,
3618    {
3619        self.inner.send_request(request)
3620    }
3621
3622    /// Send an outgoing request to a specific peer.
3623    pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
3624        &self,
3625        peer: Peer,
3626        request: Req,
3627    ) -> SentRequest<Req::Response>
3628    where
3629        Counterpart: HasPeer<Peer>,
3630    {
3631        self.inner.send_request_to(peer, request)
3632    }
3633
3634    /// Prepare a request without sending it until response handling is selected.
3635    ///
3636    /// See [`ConnectionTo::prepare_request`] for publication and ordering semantics.
3637    pub fn prepare_request<Req: JsonRpcRequest>(
3638        &self,
3639        request: Req,
3640    ) -> PreparedRequest<Req::Response>
3641    where
3642        Counterpart: HasPeer<Counterpart>,
3643    {
3644        self.inner.prepare_request(request)
3645    }
3646
3647    /// Prepare a request to a specific peer without sending it.
3648    ///
3649    /// See [`ConnectionTo::prepare_request_to`] for publication and ordering semantics.
3650    pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
3651        &self,
3652        peer: Peer,
3653        request: Req,
3654    ) -> PreparedRequest<Req::Response>
3655    where
3656        Counterpart: HasPeer<Peer>,
3657    {
3658        self.inner.prepare_request_to(peer, request)
3659    }
3660
3661    /// Send an outgoing notification to the default counterpart peer.
3662    pub fn send_notification<N: JsonRpcNotification>(
3663        &self,
3664        notification: N,
3665    ) -> Result<(), crate::Error>
3666    where
3667        Counterpart: HasPeer<Counterpart>,
3668    {
3669        self.inner.send_notification(notification)
3670    }
3671
3672    /// Send an outgoing notification to a specific peer.
3673    pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
3674        &self,
3675        peer: Peer,
3676        notification: N,
3677    ) -> Result<(), crate::Error>
3678    where
3679        Counterpart: HasPeer<Peer>,
3680    {
3681        self.inner.send_notification_to(peer, notification)
3682    }
3683
3684    /// Send a `$/cancel_request` notification to the default counterpart peer.
3685    pub fn send_cancel_request(
3686        &self,
3687        request_id: impl Into<crate::schema::v1::RequestId>,
3688    ) -> Result<(), crate::Error>
3689    where
3690        Counterpart: HasPeer<Counterpart>,
3691    {
3692        self.inner.send_cancel_request(request_id)
3693    }
3694
3695    /// Send a `$/cancel_request` notification to a specific peer.
3696    pub fn send_cancel_request_to<Peer: Role>(
3697        &self,
3698        peer: Peer,
3699        request_id: impl Into<crate::schema::v1::RequestId>,
3700    ) -> Result<(), crate::Error>
3701    where
3702        Counterpart: HasPeer<Peer>,
3703    {
3704        self.inner.send_cancel_request_to(peer, request_id)
3705    }
3706
3707    /// Register a low-level dynamic message handler.
3708    ///
3709    /// Dynamic handlers use the version-neutral [`HandleDispatchFrom`] trait,
3710    /// so their callback receives the underlying [`ConnectionTo`]. Prefer
3711    /// typed handlers on [`V2Builder`] when registration can happen before the
3712    /// connection starts.
3713    ///
3714    /// ```no_run
3715    /// # use agent_client_protocol::{
3716    /// #     Agent, Client, ConnectTo, DynamicHandlerGuard, Error, NullHandler,
3717    /// # };
3718    /// # async fn example(
3719    /// #     transport: impl ConnectTo<Client> + 'static,
3720    /// # ) -> Result<(), Error> {
3721    /// Client.v2().connect_with(transport, async |connection| {
3722    ///     let guard: DynamicHandlerGuard<Agent> =
3723    ///         connection.add_dynamic_handler(NullHandler)?;
3724    ///
3725    ///     // Keep `guard` alive for as long as the modal handler is needed.
3726    ///     drop(guard);
3727    ///     Ok(())
3728    /// }).await
3729    /// # }
3730    /// ```
3731    pub fn add_dynamic_handler(
3732        &self,
3733        handler: impl HandleDispatchFrom<Counterpart> + 'static,
3734    ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
3735        self.inner.add_dynamic_handler(handler)
3736    }
3737}
3738
3739/// Connection context for sending messages and spawning tasks.
3740///
3741/// This is the primary handle for interacting with the JSON-RPC connection from
3742/// within handler callbacks. You can use it to:
3743///
3744/// * Send requests and notifications to the other side
3745/// * Spawn concurrent tasks that run alongside the connection
3746/// * Respond to requests (via [`Responder`] which wraps this)
3747///
3748/// # Cloning
3749///
3750/// `ConnectionTo` is cheaply cloneable - all clones refer to the same underlying connection.
3751/// This makes it easy to share across async tasks.
3752///
3753/// # Event Loop and Concurrency
3754///
3755/// Handler callbacks run on the event loop, which means the connection cannot process new
3756/// messages while your handler is running. Use [`spawn`](Self::spawn) to offload any
3757/// expensive or blocking work to concurrent tasks.
3758///
3759/// See the [Event Loop and Concurrency](Builder#event-loop-and-concurrency) section
3760/// for more details.
3761#[derive(Clone, Debug)]
3762pub struct ConnectionTo<Counterpart: Role> {
3763    counterpart: Counterpart,
3764    message_tx: OutgoingMessageTx,
3765    task_tx: TaskTx,
3766    dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
3767    transport_completion: SharedTransportCompletion,
3768    pending_replies: PendingRepliesRegistrar,
3769    #[cfg_attr(
3770        not(feature = "unstable_protocol_v2"),
3771        allow(
3772            dead_code,
3773            reason = "retained so ConnectionTo has one constructor shape"
3774        )
3775    )]
3776    protocol_mode: ProtocolMode,
3777    incoming_closed: IncomingClosed,
3778    protected_operations: Arc<Mutex<ProtectedOperations>>,
3779    runner_error_scope: Option<run::RunnerErrorScope>,
3780}
3781
3782type SharedTransportCompletion = future::Shared<BoxFuture<'static, Result<(), crate::Error>>>;
3783
3784type SharedCompletionSignal = future::Shared<BoxFuture<'static, ()>>;
3785
3786#[derive(Default)]
3787struct ProtectedOperations {
3788    pending: Vec<oneshot::Receiver<()>>,
3789    joining: Option<SharedCompletionSignal>,
3790}
3791
3792impl Debug for ProtectedOperations {
3793    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3794        formatter
3795            .debug_struct("ProtectedOperations")
3796            .field("pending", &self.pending.len())
3797            .field("joining", &self.joining.is_some())
3798            .finish_non_exhaustive()
3799    }
3800}
3801
3802fn completion_signal() -> (oneshot::Sender<()>, SharedCompletionSignal) {
3803    let (tx, rx) = oneshot::channel();
3804    let signal = async move {
3805        // Dropping a sender (e.g. foreground failure) is not success.
3806        if rx.await.is_err() {
3807            future::pending::<()>().await;
3808        }
3809    }
3810    .boxed()
3811    .shared();
3812    (tx, signal)
3813}
3814
3815#[derive(Clone)]
3816struct IncomingClosed {
3817    state: Arc<IncomingClosedState>,
3818}
3819
3820struct IncomingClosedState {
3821    closing: AtomicBool,
3822    closed: AtomicBool,
3823    signal_tx: Mutex<Option<oneshot::Sender<()>>>,
3824    signal_rx: future::Shared<BoxFuture<'static, ()>>,
3825    shutdown_tx: Mutex<Option<oneshot::Sender<()>>>,
3826    #[cfg(any(feature = "unstable_mcp_over_acp", test))]
3827    shutdown_rx: SharedCompletionSignal,
3828    termination: Mutex<Option<ConnectionTermination>>,
3829    terminated_tx: Mutex<Option<oneshot::Sender<()>>>,
3830    terminated_rx: SharedCompletionSignal,
3831}
3832
3833impl IncomingClosed {
3834    fn new() -> Self {
3835        let (signal_tx, signal_rx) = oneshot::channel();
3836        let (shutdown_tx, shutdown_rx) = oneshot::channel();
3837        #[cfg(not(any(feature = "unstable_mcp_over_acp", test)))]
3838        drop(shutdown_rx);
3839        let (terminated_tx, terminated_rx) = oneshot::channel();
3840        Self {
3841            state: Arc::new(IncomingClosedState {
3842                closing: AtomicBool::new(false),
3843                closed: AtomicBool::new(false),
3844                signal_tx: Mutex::new(Some(signal_tx)),
3845                signal_rx: signal_rx.map(|_| ()).boxed().shared(),
3846                shutdown_tx: Mutex::new(Some(shutdown_tx)),
3847                #[cfg(any(feature = "unstable_mcp_over_acp", test))]
3848                shutdown_rx: shutdown_rx.map(|_| ()).boxed().shared(),
3849                termination: Mutex::new(None),
3850                terminated_tx: Mutex::new(Some(terminated_tx)),
3851                terminated_rx: terminated_rx.map(|_| ()).boxed().shared(),
3852            }),
3853        }
3854    }
3855
3856    fn begin_close(&self) {
3857        self.state.closing.store(true, Ordering::Release);
3858        self.request_shutdown();
3859    }
3860
3861    fn request_shutdown(&self) {
3862        if let Some(tx) = self
3863            .state
3864            .shutdown_tx
3865            .lock()
3866            .expect("shutdown signal mutex poisoned")
3867            .take()
3868        {
3869            let _ = tx.send(());
3870        }
3871    }
3872
3873    fn finish_close(&self) {
3874        self.state.closed.store(true, Ordering::Release);
3875        let signal_tx = self
3876            .state
3877            .signal_tx
3878            .lock()
3879            .expect("incoming-close signal mutex poisoned")
3880            .take();
3881
3882        if let Some(signal_tx) = signal_tx {
3883            let _ = signal_tx.send(());
3884        }
3885    }
3886
3887    async fn closed(&self) {
3888        self.state.signal_rx.clone().await;
3889    }
3890
3891    fn is_closed(&self) -> bool {
3892        self.state.closed.load(Ordering::Acquire)
3893    }
3894
3895    fn is_closing(&self) -> bool {
3896        self.state.closing.load(Ordering::Acquire)
3897    }
3898
3899    /// Record the termination reason. Returns `false` if one was already set.
3900    fn set_termination(&self, termination: ConnectionTermination) -> bool {
3901        let mut slot = self
3902            .state
3903            .termination
3904            .lock()
3905            .expect("termination mutex poisoned");
3906        if slot.is_some() {
3907            return false;
3908        }
3909        *slot = Some(termination);
3910        true
3911    }
3912
3913    fn signal_terminated(&self) {
3914        if let Some(tx) = self
3915            .state
3916            .terminated_tx
3917            .lock()
3918            .expect("terminated signal mutex poisoned")
3919            .take()
3920        {
3921            let _ = tx.send(());
3922        }
3923    }
3924
3925    fn termination(&self) -> Option<ConnectionTermination> {
3926        self.state
3927            .termination
3928            .lock()
3929            .expect("termination mutex poisoned")
3930            .clone()
3931    }
3932
3933    async fn terminated(&self) -> ConnectionTermination {
3934        self.state.terminated_rx.clone().await;
3935        self.termination()
3936            .expect("termination is recorded before it is signalled")
3937    }
3938}
3939
3940/// Publishes the connection's termination exactly once: when the driver
3941/// completes via [`Self::finish`], or synchronously when the driver future is
3942/// dropped first.
3943struct TerminationGuard {
3944    incoming_closed: IncomingClosed,
3945    pending_replies: PendingReplies,
3946}
3947
3948impl TerminationGuard {
3949    fn finish(self, termination: ConnectionTermination) {
3950        self.publish(termination);
3951    }
3952
3953    fn publish(&self, termination: ConnectionTermination) {
3954        if !self.incoming_closed.set_termination(termination.clone()) {
3955            return;
3956        }
3957        // Record first, so a requester woken by its failure observes the
3958        // termination; then fail requests before waking `terminated()`.
3959        let pending_reply_count = self.pending_replies.terminate(&termination);
3960        tracing::debug!(
3961            termination = termination.kind(),
3962            pending_reply_count,
3963            "Connection terminated"
3964        );
3965        self.incoming_closed.signal_terminated();
3966    }
3967}
3968
3969impl Drop for TerminationGuard {
3970    fn drop(&mut self) {
3971        // No-op after `finish`.
3972        self.publish(ConnectionTermination::Dropped);
3973    }
3974}
3975
3976impl Debug for IncomingClosed {
3977    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3978        formatter
3979            .debug_struct("IncomingClosed")
3980            .field("is_closing", &self.is_closing())
3981            .field("is_closed", &self.is_closed())
3982            .finish_non_exhaustive()
3983    }
3984}
3985
3986/// Stable discriminator stored in the `data.reason` field of errors produced
3987/// when the incoming transport reaches clean EOF before a request receives its
3988/// response.
3989pub const INCOMING_TRANSPORT_CLOSED_REASON: &str = "incoming_transport_closed";
3990
3991/// Return whether `error` reports that the incoming transport reached clean
3992/// EOF before a request received its response.
3993#[must_use]
3994pub fn is_incoming_transport_closed(error: &crate::Error) -> bool {
3995    error
3996        .data
3997        .as_ref()
3998        .and_then(|data| data.get("reason"))
3999        .and_then(serde_json::Value::as_str)
4000        == Some(INCOMING_TRANSPORT_CLOSED_REASON)
4001}
4002
4003fn incoming_transport_closed_error(method: &str) -> crate::Error {
4004    let mut error = crate::Error::internal_error();
4005    error.message = "Incoming transport closed".to_string();
4006    error.data(serde_json::json!({
4007        "reason": INCOMING_TRANSPORT_CLOSED_REASON,
4008        "method": method,
4009    }))
4010}
4011
4012/// Unlike the cleanup coordinator below, check success before polling delivery:
4013/// an already-ready success must not resume a message handler into another
4014/// dispatch (including another entry of the same batch).
4015fn run_incoming_until_foreground_succeeds(
4016    incoming: impl Future<Output = Result<(), crate::Error>>,
4017    foreground_succeeded: SharedCompletionSignal,
4018    incoming_closed: IncomingClosed,
4019) -> impl Future<Output = Result<(), crate::Error>> {
4020    let mut incoming = Box::pin(incoming);
4021    future::poll_fn(move |cx| {
4022        if foreground_succeeded.clone().poll_unpin(cx).is_ready() && !incoming_closed.is_closing() {
4023            return std::task::Poll::Ready(Ok(()));
4024        }
4025        // A close callback already underway is protected. Its result is polled
4026        // before stopping, so callback errors retain their existing precedence.
4027        incoming.as_mut().poll(cx)
4028    })
4029}
4030
4031/// Run the connection background alongside its foreground while ensuring that
4032/// a foreground woken by incoming EOF cannot cancel close callbacks midway.
4033fn run_until_connection_close<R>(
4034    background: impl Future<Output = Result<(), crate::Error>>,
4035    foreground: impl Future<Output = Result<R, crate::Error>>,
4036    incoming_closed: IncomingClosed,
4037) -> impl Future<Output = Result<R, crate::Error>> {
4038    // Box these before constructing the returned future. Keeping the generic
4039    // connection actors directly in this async state would substantially grow
4040    // every `connect_*` future.
4041    let background = Box::pin(background);
4042    let foreground = Box::pin(foreground);
4043
4044    async move {
4045        match future::select(background, foreground).await {
4046            Either::Left((background_result, foreground)) => {
4047                background_result?;
4048                foreground.await
4049            }
4050            Either::Right((foreground_result, background)) => {
4051                if !incoming_closed.is_closing() {
4052                    return foreground_result;
4053                }
4054
4055                match future::select(background, Box::pin(incoming_closed.closed())).await {
4056                    Either::Left((background_result, _)) => {
4057                        background_result?;
4058                        foreground_result
4059                    }
4060                    Either::Right(((), background)) => {
4061                        // Poll the background first once more so an error returned
4062                        // by the just-finished close callback wins over the ready
4063                        // foreground result.
4064                        crate::util::run_until(background, future::ready(foreground_result)).await
4065                    }
4066                }
4067            }
4068        }
4069    }
4070}
4071
4072impl<Counterpart: Role> ConnectionTo<Counterpart> {
4073    fn new(
4074        counterpart: Counterpart,
4075        message_tx: mpsc::UnboundedSender<OutgoingMessage>,
4076        task_tx: mpsc::UnboundedSender<Task>,
4077        dynamic_handler_tx: mpsc::UnboundedSender<DynamicHandlerMessage<Counterpart>>,
4078        transport_completion: SharedTransportCompletion,
4079        pending_replies: PendingRepliesRegistrar,
4080        protocol_mode: ProtocolMode,
4081    ) -> Self {
4082        Self {
4083            counterpart,
4084            message_tx,
4085            task_tx,
4086            dynamic_handler_tx,
4087            transport_completion,
4088            pending_replies,
4089            protocol_mode,
4090            incoming_closed: IncomingClosed::new(),
4091            protected_operations: Arc::default(),
4092            runner_error_scope: None,
4093        }
4094    }
4095
4096    pub(crate) fn with_runner_error_scope(mut self, scope: run::RunnerErrorScope) -> Self {
4097        self.runner_error_scope = Some(scope);
4098        self
4099    }
4100
4101    pub(crate) fn finish_runner_error(
4102        &self,
4103        error: crate::Error,
4104    ) -> impl Future<Output = ()> + Send + '_ {
4105        if let Some(scope) = &self.runner_error_scope {
4106            Either::Left(scope.finish(error))
4107        } else {
4108            // Unscoped runners belong to the connection itself.
4109            self.request_shutdown();
4110            Either::Right(self.wait_protected_operations())
4111        }
4112    }
4113
4114    /// Spawn only a connection-owned supervisor whose async cleanup must finish
4115    /// before the driver returns. Ordinary application work must use `spawn`.
4116    #[cfg(any(feature = "unstable_mcp_over_acp", test))]
4117    #[track_caller]
4118    pub(crate) fn spawn_protected(
4119        &self,
4120        task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
4121    ) -> Result<(), crate::Error> {
4122        let mut state = self
4123            .protected_operations
4124            .lock()
4125            .expect("protected operations mutex poisoned");
4126        if state.joining.is_some() {
4127            return Err(crate::Error::request_cancelled());
4128        }
4129        // Reap completed acknowledgments at admission, rather than retaining
4130        // every operation for the entire lifetime of the connection.
4131        state
4132            .pending
4133            .retain_mut(|done| matches!(done.try_recv(), Ok(None)));
4134        let (done_tx, done_rx) = oneshot::channel();
4135        let task = task.into_future();
4136        self.spawn(async move {
4137            let result = task.await;
4138            let _ = done_tx.send(());
4139            result
4140        })?;
4141        state.pending.push(done_rx);
4142        Ok(())
4143    }
4144
4145    pub(crate) async fn wait_protected_operations(&self) {
4146        let joining = {
4147            let mut state = self
4148                .protected_operations
4149                .lock()
4150                .expect("protected operations mutex poisoned");
4151            if state.joining.is_none() {
4152                let operations = std::mem::take(&mut state.pending);
4153                state.joining = Some(
4154                    async move {
4155                        for operation in operations {
4156                            let _ = operation.await;
4157                        }
4158                    }
4159                    .boxed()
4160                    .shared(),
4161                );
4162            }
4163            state.joining.as_ref().expect("join initialized").clone()
4164        };
4165        joining.await;
4166    }
4167
4168    pub(crate) fn request_shutdown(&self) {
4169        self.incoming_closed.request_shutdown();
4170    }
4171
4172    /// Early cancellation for owned native work, before close callbacks or drain.
4173    #[cfg(any(feature = "unstable_mcp_over_acp", test))]
4174    pub(crate) async fn shutdown_requested(&self) {
4175        self.incoming_closed.state.shutdown_rx.clone().await;
4176    }
4177
4178    #[cfg(feature = "unstable_protocol_v2")]
4179    pub(crate) fn acp_protocol_version(&self) -> Option<crate::schema::ProtocolVersion> {
4180        self.protocol_mode.api_protocol_version()
4181    }
4182
4183    /// Return the counterpart role this connection is talking to.
4184    pub fn counterpart(&self) -> Counterpart {
4185        self.counterpart.clone()
4186    }
4187
4188    /// Wait until the incoming transport reaches clean EOF.
4189    ///
4190    /// Transport closure means that no more messages or responses can arrive.
4191    /// Pending requests are failed first; this completes after registered
4192    /// [`Builder::on_close`] callbacks finish.
4193    /// It does not automatically cancel the future passed to
4194    /// [`Builder::connect_with`]; use [`Builder::on_close`] when the connection
4195    /// should run application-specific cleanup or terminate that future.
4196    ///
4197    /// This never completes if the connection ends some other way; use
4198    /// [`terminated`](Self::terminated) to observe every termination.
4199    pub async fn incoming_closed(&self) {
4200        self.incoming_closed.closed().await;
4201    }
4202
4203    /// Return whether clean incoming-EOF processing has completed.
4204    ///
4205    /// This remains `false` while [`Builder::on_close`] callbacks are running.
4206    #[must_use]
4207    pub fn is_incoming_closed(&self) -> bool {
4208        self.incoming_closed.is_closed()
4209    }
4210
4211    /// Wait until the connection driver has terminated, for any reason.
4212    ///
4213    /// This is the universal terminal event for a connection driven by
4214    /// [`Builder::connect_to`] or [`Builder::connect_with`]. Unlike
4215    /// [`incoming_closed`](Self::incoming_closed), it also completes after
4216    /// transport errors, handler or close-callback errors, foreground
4217    /// completion, and a dropped driver future.
4218    ///
4219    /// The termination is published once, synchronously, as the driver future
4220    /// returns (or is dropped). At that point:
4221    ///
4222    /// - No further handler, close callback, runner, or
4223    ///   [`spawn`](Self::spawn)ed task of this connection will run; each has
4224    ///   completed or been dropped.
4225    /// - Every request still awaiting a response has been failed. Requests
4226    ///   failed earlier by clean incoming EOF satisfy
4227    ///   [`is_incoming_transport_closed`](crate::is_incoming_transport_closed);
4228    ///   the rest satisfy [`is_connection_terminated`](crate::is_connection_terminated),
4229    ///   as do requests sent afterwards.
4230    ///
4231    /// Do not await this from inside the connection's own handlers, close
4232    /// callbacks, or tasks: termination happens only after they finish.
4233    ///
4234    /// Termination does not reap child processes owned by the transport, nor
4235    /// drain application-owned queues.
4236    pub async fn terminated(&self) -> ConnectionTermination {
4237        self.incoming_closed.terminated().await
4238    }
4239
4240    /// Return the termination reason if the connection driver has terminated.
4241    ///
4242    /// See [`terminated`](Self::terminated).
4243    #[must_use]
4244    pub fn termination(&self) -> Option<ConnectionTermination> {
4245        self.incoming_closed.termination()
4246    }
4247
4248    /// Stop accepting outgoing messages, drain routable output through the
4249    /// protocol actor, and finish cooperative physical sinks. Reactive serving also
4250    /// joins owned transport work after incoming EOF.
4251    async fn drain_outgoing(
4252        &self,
4253        finish: Option<crate::component::FinishControl>,
4254        wait_owned_transport: bool,
4255    ) -> Result<(), crate::Error> {
4256        let (done_tx, done_rx) = oneshot::channel();
4257        let marker_result = send_raw_message(
4258            &self.message_tx,
4259            OutgoingMessage::CloseAfterDraining { done: done_tx },
4260        );
4261        let marker_result = match marker_result {
4262            Ok(()) => done_rx.await.map_err(|error| {
4263                crate::util::internal_error(format!(
4264                    "outgoing drain marker was dropped before completion: {error}"
4265                ))
4266            }),
4267            Err(error) => Err(error),
4268        };
4269
4270        let physical_finish = finish.is_some();
4271        if let Some(mut finish) = finish {
4272            // Only finish the physical sink after the protocol actor has handed
4273            // off its accepted output. Closing it earlier races the drain.
4274            finish.request();
4275        }
4276        if physical_finish || wait_owned_transport {
4277            // Cooperative completion proves physical sink drain. Reactive serving
4278            // also waits for owned work after EOF (e.g. child exit status).
4279            self.transport_completion.clone().await?;
4280        }
4281        // Opaque application drivers have no physical finish contract. Keep
4282        // polling their errors in the background, but do not globally join work
4283        // which may intentionally run forever after the foreground returns.
4284        marker_result
4285    }
4286
4287    fn is_incoming_closing(&self) -> bool {
4288        self.incoming_closed.is_closing()
4289    }
4290
4291    pub(super) fn begin_incoming_close(&self) {
4292        self.incoming_closed.begin_close();
4293    }
4294
4295    pub(super) fn finish_incoming_close(&self) {
4296        self.incoming_closed.finish_close();
4297    }
4298
4299    /// Spawns a task that will run so long as the JSON-RPC connection is being served.
4300    ///
4301    /// This is the primary mechanism for offloading expensive work from handler callbacks
4302    /// to avoid blocking the event loop. Spawned tasks run concurrently with the connection,
4303    /// allowing the server to continue processing messages.
4304    ///
4305    /// # Event Loop
4306    ///
4307    /// Handler callbacks run on the event loop, which cannot process new messages while
4308    /// your handler is running. Use `spawn` for any expensive operations:
4309    ///
4310    /// ```no_run
4311    /// # use agent_client_protocol_test::*;
4312    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
4313    /// # let connection = mock_connection();
4314    /// connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
4315    ///     // Clone cx for the spawned task
4316    ///     cx.spawn({
4317    ///         let connection = cx.clone();
4318    ///         async move {
4319    ///             let result = expensive_operation(&req.data).await?;
4320    ///             connection.send_notification(ProcessComplete { result })?;
4321    ///             Ok(())
4322    ///         }
4323    ///     })?;
4324    ///
4325    ///     // Respond immediately
4326    ///     responder.respond(ProcessResponse { result: "started".into() })
4327    /// }, agent_client_protocol::on_receive_request!())
4328    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
4329    /// # Ok(())
4330    /// # }
4331    /// ```
4332    ///
4333    /// # Errors
4334    ///
4335    /// If the spawned task returns an error, the entire server will shut down.
4336    #[track_caller]
4337    pub fn spawn(
4338        &self,
4339        task: impl IntoFuture<Output = Result<(), crate::Error>, IntoFuture: Send + 'static>,
4340    ) -> Result<(), crate::Error> {
4341        let location = std::panic::Location::caller();
4342        let task = task.into_future();
4343        Task::new(location, task).spawn(&self.task_tx)
4344    }
4345
4346    /// Spawn a JSON-RPC connection in the background and return a raw
4347    /// [`ConnectionTo`] for it.
4348    ///
4349    /// This is useful for creating multiple connections that communicate with each other,
4350    /// such as implementing proxy patterns or connecting to multiple backend services.
4351    ///
4352    /// # Arguments
4353    ///
4354    /// - `builder`: The connection builder with handlers configured
4355    /// - `transport`: The transport component to connect to
4356    ///
4357    /// # Returns
4358    ///
4359    /// The child builder may select any callback context. For example, this
4360    /// method can spawn a `V2Builder`, whose callbacks receive
4361    /// `V2ConnectionTo`, while preserving this method's existing raw return
4362    /// type and single explicit role parameter.
4363    ///
4364    /// When a raw parent also needs the builder-selected child handle, use the
4365    /// protocol-v2 `spawn_connection_with_context` method.
4366    ///
4367    /// # Example: Proxying to a backend connection
4368    ///
4369    /// ```
4370    /// # use agent_client_protocol::UntypedRole;
4371    /// # use agent_client_protocol::{Builder, ConnectionTo};
4372    /// # use agent_client_protocol_test::*;
4373    /// # async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4374    /// // Set up a backend connection builder
4375    /// let backend = UntypedRole.builder()
4376    ///     .on_receive_request(async |req: MyRequest, responder, _cx| {
4377    ///         responder.respond(MyResponse { status: "ok".into() })
4378    ///     }, agent_client_protocol::on_receive_request!());
4379    ///
4380    /// // Spawn it and get a context to send requests to it
4381    /// let backend_connection = cx.spawn_connection::<UntypedRole>(backend, MockTransport)?;
4382    ///
4383    /// // Now you can forward requests to the backend
4384    /// let response = backend_connection.send_request(MyRequest {}).block_task().await?;
4385    /// # Ok(())
4386    /// # }
4387    /// ```
4388    #[track_caller]
4389    pub fn spawn_connection<R: Role>(
4390        &self,
4391        builder: Builder<
4392            R,
4393            impl HandleDispatchFrom<R::Counterpart> + 'static,
4394            impl RunWithConnectionTo<R::Counterpart> + 'static,
4395            impl HandleConnectionClose<R::Counterpart> + 'static,
4396            impl ConnectionContext,
4397        >,
4398        transport: impl ConnectTo<R> + 'static,
4399    ) -> Result<ConnectionTo<R::Counterpart>, crate::Error> {
4400        self.spawn_connection_raw(builder, transport)
4401    }
4402
4403    /// Spawn a JSON-RPC connection and return the connection context selected
4404    /// by its builder.
4405    ///
4406    /// This is the low-level counterpart to
4407    /// [`V2ConnectionTo::spawn_connection`] for code that intentionally works
4408    /// with a raw [`ConnectionTo`], such as custom [`HandleDispatchFrom`] or
4409    /// [`RunWithConnectionTo`] implementations. Prefer [`Self::spawn_connection`]
4410    /// when a raw child handle is sufficient.
4411    ///
4412    /// ```no_run
4413    /// # use agent_client_protocol::{
4414    /// #     Agent, Client, ConnectTo, ConnectionTo, Error, UntypedRole,
4415    /// #     V2ConnectionTo,
4416    /// # };
4417    /// # fn example(
4418    /// #     connection: ConnectionTo<UntypedRole>,
4419    /// #     transport: impl ConnectTo<Client> + 'static,
4420    /// # ) -> Result<(), Error> {
4421    /// let child: V2ConnectionTo<Agent> =
4422    ///     connection.spawn_connection_with_context(Client.v2(), transport)?;
4423    /// # drop(child);
4424    /// # Ok(())
4425    /// # }
4426    /// ```
4427    #[cfg(feature = "unstable_protocol_v2")]
4428    #[track_caller]
4429    pub fn spawn_connection_with_context<R: Role, Context: ConnectionContext>(
4430        &self,
4431        builder: Builder<
4432            R,
4433            impl HandleDispatchFrom<R::Counterpart> + 'static,
4434            impl RunWithConnectionTo<R::Counterpart> + 'static,
4435            impl HandleConnectionClose<R::Counterpart> + 'static,
4436            Context,
4437        >,
4438        transport: impl ConnectTo<R> + 'static,
4439    ) -> Result<Context::Connection<R::Counterpart>, crate::Error> {
4440        let connection = self.spawn_connection_raw(builder, transport)?;
4441        Ok(connection_context::from_raw::<Context, _>(connection))
4442    }
4443
4444    #[track_caller]
4445    fn spawn_connection_raw<R: Role, Context: ConnectionContext>(
4446        &self,
4447        builder: Builder<
4448            R,
4449            impl HandleDispatchFrom<R::Counterpart> + 'static,
4450            impl RunWithConnectionTo<R::Counterpart> + 'static,
4451            impl HandleConnectionClose<R::Counterpart> + 'static,
4452            Context,
4453        >,
4454        transport: impl ConnectTo<R> + 'static,
4455    ) -> Result<ConnectionTo<R::Counterpart>, crate::Error> {
4456        let (connection, future) =
4457            builder.into_connection_and_future(transport, false, |_| std::future::pending());
4458        Task::new(std::panic::Location::caller(), future).spawn(&self.task_tx)?;
4459        Ok(connection)
4460    }
4461
4462    /// Send a request/notification and forward the response appropriately.
4463    ///
4464    /// The request context's response type matches the request's response type,
4465    /// enabling type-safe message forwarding.
4466    pub fn send_proxied_message<Req: JsonRpcRequest<Response: Send>, Notif: JsonRpcNotification>(
4467        &self,
4468        message: Dispatch<Req, Notif>,
4469    ) -> Result<(), crate::Error>
4470    where
4471        Counterpart: HasPeer<Counterpart>,
4472    {
4473        self.send_proxied_message_to(self.counterpart(), message)
4474    }
4475
4476    /// Send a request/notification and forward the response appropriately.
4477    ///
4478    /// The request context's response type matches the request's response type,
4479    /// enabling type-safe message forwarding.
4480    ///
4481    /// `$/cancel_request` notifications are *not* forwarded: their `requestId`
4482    /// refers to a request on the connection they arrived over and would be
4483    /// meaningless to `peer`. Cancellation instead propagates hop by hop,
4484    /// because the responders passed to
4485    /// [`forward_response_to`](SentRequest::forward_response_to) observe it
4486    /// and re-issue the cancellation with the forwarded request's own ID.
4487    pub fn send_proxied_message_to<
4488        Peer: Role,
4489        Req: JsonRpcRequest<Response: Send>,
4490        Notif: JsonRpcNotification,
4491    >(
4492        &self,
4493        peer: Peer,
4494        message: Dispatch<Req, Notif>,
4495    ) -> Result<(), crate::Error>
4496    where
4497        Counterpart: HasPeer<Peer>,
4498    {
4499        match message {
4500            Dispatch::Request(request, responder) => self
4501                .send_ordered_request_to(peer, request)
4502                .forward_response_to(responder),
4503            Dispatch::Notification(notification) => {
4504                // `$/cancel_request` is connection-scoped: its `requestId` was
4505                // allocated on the connection the notification arrived over
4506                // and means nothing to `peer`. The cancellation has already
4507                // been recorded on this connection's responder markers, and
4508                // `forward_response_to` re-issues it for the forwarded request
4509                // with the correct per-hop ID, so drop the raw notification
4510                // instead of tunneling a meaningless ID across the hop.
4511                if is_cancel_request_notification(&notification) {
4512                    tracing::debug!(
4513                        "not forwarding hop-scoped `$/cancel_request` notification across proxy hop"
4514                    );
4515                    return Ok(());
4516                }
4517                self.send_notification_to(peer, notification)
4518            }
4519            Dispatch::Response(result, router) => {
4520                // Responses are forwarded directly to their destination
4521                router.route_with_result(result)
4522            }
4523        }
4524    }
4525
4526    /// Send an outgoing request and return a [`SentRequest`] for handling the reply.
4527    ///
4528    /// The returned [`SentRequest`] makes the response-consumption mode explicit:
4529    ///
4530    /// * [`on_receiving_result`](SentRequest::on_receiving_result) - Register a callback and
4531    ///   return immediately. If registered before the response is routed during its original
4532    ///   dispatch, the loop waits for the callback to complete.
4533    /// * [`block_task`](SentRequest::block_task) - Wait on the current task until the response
4534    ///   arrives. This is only safe when that task already runs outside the dispatch loop.
4535    ///
4536    /// For callback ordering selected before publication, use
4537    /// [`prepare_request`](Self::prepare_request) instead. Even an immediately
4538    /// chained callback can race with a fast response on a concurrent connection.
4539    ///
4540    /// # Anti-Footgun Design
4541    ///
4542    /// The API intentionally makes it difficult to block on the result directly to prevent
4543    /// the common mistake of blocking the event loop while waiting for a response:
4544    ///
4545    /// ```compile_fail
4546    /// # use agent_client_protocol_test::*;
4547    /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4548    /// // ❌ This doesn't compile - prevents blocking the event loop
4549    /// let response = cx.send_request(MyRequest {}).await?;
4550    /// # Ok(())
4551    /// # }
4552    /// ```
4553    ///
4554    /// ```no_run
4555    /// # use agent_client_protocol_test::*;
4556    /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
4557    /// // ✅ Option 1: Register an ordered callback (safe in handlers)
4558    /// cx.send_request(MyRequest {})
4559    ///     .on_receiving_result(async |result| {
4560    ///         // Handle the response
4561    ///         Ok(())
4562    ///     })?;
4563    ///
4564    /// // ✅ Option 2: Block in spawned task (safe because task is concurrent)
4565    /// cx.spawn({
4566    ///     let cx = cx.clone();
4567    ///     async move {
4568    ///         let response = cx.send_request(MyRequest {})
4569    ///             .block_task()
4570    ///             .await?;
4571    ///         // Process response...
4572    ///         Ok(())
4573    ///     }
4574    /// })?;
4575    /// # Ok(())
4576    /// # }
4577    /// ```
4578    /// Send an outgoing request to the default counterpart peer.
4579    ///
4580    /// This is a convenience method that sends to the counterpart role `R`.
4581    /// For explicit control over the target peer, use [`send_request_to`](Self::send_request_to).
4582    pub fn send_request<Req: JsonRpcRequest>(&self, request: Req) -> SentRequest<Req::Response>
4583    where
4584        Counterpart: HasPeer<Counterpart>,
4585    {
4586        self.send_request_to(self.counterpart.clone(), request)
4587    }
4588
4589    /// Send an outgoing request to a specific peer.
4590    ///
4591    /// The message will be transformed according to the [`HasPeer`](crate::role::HasPeer)
4592    /// implementation before being sent.
4593    pub fn send_request_to<Peer: Role, Req: JsonRpcRequest>(
4594        &self,
4595        peer: Peer,
4596        request: Req,
4597    ) -> SentRequest<Req::Response>
4598    where
4599        Counterpart: HasPeer<Peer>,
4600    {
4601        self.send_request_to_with_options(peer, request, false, None, None)
4602    }
4603
4604    /// Prepare a request without sending it until response handling is selected.
4605    ///
4606    /// Unlike [`send_request`](Self::send_request), this does not register a
4607    /// pending reply or enqueue the request. A consuming method on the returned
4608    /// [`PreparedRequest`] publishes it synchronously. Callback-style methods
4609    /// select ordered consumption before publication, closing the race with a
4610    /// fast peer response even when the connection runs on another task.
4611    ///
4612    /// Dropping the prepared request sends nothing. See [`PreparedRequest`] for
4613    /// the available consumption modes and their error and cancellation behavior.
4614    ///
4615    /// # Examples
4616    ///
4617    /// ```no_run
4618    /// # use agent_client_protocol::{ConnectionTo, Error, UntypedRole};
4619    /// # use agent_client_protocol_test::MyRequest;
4620    /// # fn example(connection: ConnectionTo<UntypedRole>) -> Result<(), Error> {
4621    /// connection.prepare_request(MyRequest {}).on_receiving_result(async |result| {
4622    ///     let response = result?;
4623    ///     // Apply bounded response work before later inbound messages.
4624    ///     Ok(())
4625    /// })?;
4626    /// # Ok(())
4627    /// # }
4628    /// ```
4629    pub fn prepare_request<Req: JsonRpcRequest>(
4630        &self,
4631        request: Req,
4632    ) -> PreparedRequest<Req::Response>
4633    where
4634        Counterpart: HasPeer<Counterpart>,
4635    {
4636        self.prepare_request_to(self.counterpart.clone(), request)
4637    }
4638
4639    /// Prepare a request to a specific peer without sending it.
4640    ///
4641    /// The request is serialized now. A consuming method synchronously registers
4642    /// its pending reply and enqueues the request; peer transformation and
4643    /// transmission happen later in the connection driver.
4644    /// See [`prepare_request`](Self::prepare_request).
4645    pub fn prepare_request_to<Peer: Role, Req: JsonRpcRequest>(
4646        &self,
4647        peer: Peer,
4648        request: Req,
4649    ) -> PreparedRequest<Req::Response>
4650    where
4651        Counterpart: HasPeer<Peer>,
4652    {
4653        self.prepare_request_to_with_options(peer, request, None, None)
4654    }
4655
4656    /// Send a request and run a synchronous side effect when its valid success
4657    /// response is routed, after `before_send` completes and independently
4658    /// from how the returned request is eventually consumed.
4659    #[cfg(feature = "unstable_protocol_v2")]
4660    pub(crate) fn send_request_to_with_response_hook_after<
4661        Peer: Role,
4662        Req: JsonRpcRequest,
4663        BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
4664    >(
4665        &self,
4666        peer: Peer,
4667        request: Req,
4668        before_send: BeforeSend,
4669        response_hook: impl FnOnce(&Req::Response) -> Result<(), crate::Error> + Send + 'static,
4670    ) -> SentRequest<Req::Response>
4671    where
4672        Counterpart: HasPeer<Peer>,
4673    {
4674        let hook: ResponseRouteHook = Box::new(move |method, value| {
4675            let response = Req::Response::from_value(method, value.clone())?;
4676            response_hook(&response)
4677        });
4678        self.send_request_to_with_options(
4679            peer,
4680            request,
4681            false,
4682            Some(RequestReadiness::new(before_send)),
4683            Some(hook),
4684        )
4685    }
4686
4687    /// Send an ordered request with readiness and valid-success hooks.
4688    #[cfg(feature = "unstable_protocol_v2")]
4689    pub(crate) fn send_ordered_request_to_with_response_hook_after<
4690        Peer: Role,
4691        Req: JsonRpcRequest,
4692        BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
4693    >(
4694        &self,
4695        peer: Peer,
4696        request: Req,
4697        before_send: BeforeSend,
4698        response_hook: impl FnOnce(&Req::Response) -> Result<(), crate::Error> + Send + 'static,
4699    ) -> SentRequest<Req::Response>
4700    where
4701        Counterpart: HasPeer<Peer>,
4702    {
4703        let hook: ResponseRouteHook = Box::new(move |method, value| {
4704            let response = Req::Response::from_value(method, value.clone())?;
4705            response_hook(&response)
4706        });
4707        self.send_request_to_with_options(
4708            peer,
4709            request,
4710            true,
4711            Some(RequestReadiness::new(before_send)),
4712            Some(hook),
4713        )
4714    }
4715
4716    /// Send a request whose callback must run before later inbound messages.
4717    ///
4718    /// The ordering marker is installed before the request enters the outgoing
4719    /// queue, closing the race between a fast peer response and the immediate
4720    /// [`SentRequest::on_receiving_result`] call. Callers must consume the
4721    /// returned request with a callback-style method without yielding.
4722    pub(crate) fn send_ordered_request_to<Peer: Role, Req: JsonRpcRequest>(
4723        &self,
4724        peer: Peer,
4725        request: Req,
4726    ) -> SentRequest<Req::Response>
4727    where
4728        Counterpart: HasPeer<Peer>,
4729    {
4730        self.send_request_to_with_options(peer, request, true, None, None)
4731    }
4732
4733    /// Send an ordered request after `before_send` completes successfully.
4734    ///
4735    /// The ordering marker and readiness prerequisite are both registered
4736    /// before the request enters the outgoing queue. This is used by framework
4737    /// setup paths that must acknowledge local routing before the peer can
4738    /// observe the request.
4739    pub(crate) fn send_ordered_request_to_after<
4740        Peer: Role,
4741        Req: JsonRpcRequest,
4742        BeforeSend: Future<Output = Result<(), crate::Error>> + Send + 'static,
4743    >(
4744        &self,
4745        peer: Peer,
4746        request: Req,
4747        before_send: BeforeSend,
4748    ) -> SentRequest<Req::Response>
4749    where
4750        Counterpart: HasPeer<Peer>,
4751    {
4752        self.send_request_to_with_options(
4753            peer,
4754            request,
4755            true,
4756            Some(RequestReadiness::new(before_send)),
4757            None,
4758        )
4759    }
4760
4761    fn send_request_to_with_options<Peer: Role, Req: JsonRpcRequest>(
4762        &self,
4763        peer: Peer,
4764        request: Req,
4765        ordered: bool,
4766        readiness: Option<RequestReadiness>,
4767        response_route_hook: Option<ResponseRouteHook>,
4768    ) -> SentRequest<Req::Response>
4769    where
4770        Counterpart: HasPeer<Peer>,
4771    {
4772        self.prepare_request_to_with_options(peer, request, readiness, response_route_hook)
4773            .into_sent_request(ordered)
4774    }
4775
4776    fn prepare_request_to_with_options<Peer: Role, Req: JsonRpcRequest>(
4777        &self,
4778        peer: Peer,
4779        request: Req,
4780        readiness: Option<RequestReadiness>,
4781        response_route_hook: Option<ResponseRouteHook>,
4782    ) -> PreparedRequest<Req::Response>
4783    where
4784        Counterpart: HasPeer<Peer>,
4785    {
4786        let method = request.method().to_string();
4787        let id = RequestId::Str(uuid::Uuid::new_v4().to_string());
4788        let (response_tx, response_rx) = oneshot::channel();
4789        let response_ordering = ResponseOrdering::default();
4790        let role_id = peer.role_id();
4791        let remote_style = self.counterpart.remote_style(peer);
4792        let cancellation =
4793            SentRequestCancellation::new(self.message_tx.clone(), remote_style, id.clone());
4794        let pending_reply = PendingReply {
4795            method: method.clone(),
4796            role_id,
4797            sender: response_tx,
4798            cancellation_disarm: cancellation.disarm_handle(),
4799            ordering: response_ordering.clone(),
4800            response_route_hook,
4801            accepts_lower_initialize_version: false,
4802        };
4803        let message = if self.is_incoming_closing() {
4804            Err(incoming_transport_closed_error(&method))
4805        } else {
4806            request
4807                .to_untyped_message()
4808                .map(|untyped| OutgoingMessage::Request {
4809                    id: id.clone(),
4810                    method: method.clone(),
4811                    untyped,
4812                    remote_style,
4813                    readiness,
4814                })
4815                .map_err(|error| {
4816                    crate::util::internal_error(format!(
4817                        "failed to create untyped request for `{method}`: {error}"
4818                    ))
4819                })
4820        };
4821        let sent = SentRequest::new(
4822            id,
4823            method.clone(),
4824            self.task_tx.clone(),
4825            response_rx,
4826            cancellation,
4827            response_ordering,
4828        )
4829        .map(move |json| <Req::Response>::from_value(&method, json));
4830        PreparedRequest {
4831            sent,
4832            publication: RequestPublication {
4833                message,
4834                pending_reply,
4835                message_tx: self.message_tx.clone(),
4836                pending_replies: self.pending_replies.clone(),
4837                incoming_closed: self.incoming_closed.clone(),
4838            },
4839        }
4840    }
4841
4842    /// Send an outgoing notification to the default counterpart peer (no reply expected).
4843    ///
4844    /// Notifications are fire-and-forget messages that don't have IDs and don't expect responses.
4845    /// This method sends the notification immediately and returns.
4846    ///
4847    /// This is a convenience method that sends to the counterpart role `R`.
4848    /// For explicit control over the target peer, use [`send_notification_to`](Self::send_notification_to).
4849    ///
4850    /// ```no_run
4851    /// # use agent_client_protocol_test::*;
4852    /// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::Agent>) -> Result<(), agent_client_protocol::Error> {
4853    /// cx.send_notification(StatusUpdate {
4854    ///     message: "Processing...".into(),
4855    /// })?;
4856    /// # Ok(())
4857    /// # }
4858    /// ```
4859    pub fn send_notification<N: JsonRpcNotification>(
4860        &self,
4861        notification: N,
4862    ) -> Result<(), crate::Error>
4863    where
4864        Counterpart: HasPeer<Counterpart>,
4865    {
4866        self.send_notification_to(self.counterpart.clone(), notification)
4867    }
4868
4869    /// Send an outgoing notification to a specific peer (no reply expected).
4870    ///
4871    /// The message will be transformed according to the [`HasPeer`](crate::role::HasPeer)
4872    /// implementation before being sent.
4873    pub fn send_notification_to<Peer: Role, N: JsonRpcNotification>(
4874        &self,
4875        peer: Peer,
4876        notification: N,
4877    ) -> Result<(), crate::Error>
4878    where
4879        Counterpart: HasPeer<Peer>,
4880    {
4881        let remote_style = self.counterpart.remote_style(peer);
4882        tracing::debug!(
4883            role = std::any::type_name::<Counterpart>(),
4884            peer = std::any::type_name::<Peer>(),
4885            notification_type = std::any::type_name::<N>(),
4886            ?remote_style,
4887            original_method = notification.method(),
4888            "send_notification_to"
4889        );
4890        let transformed = remote_style.transform_outgoing_message(notification)?;
4891        tracing::debug!(
4892            transformed_method = %transformed.method,
4893            "send_notification_to transformed"
4894        );
4895        send_raw_message(
4896            &self.message_tx,
4897            OutgoingMessage::Notification {
4898                untyped: transformed,
4899            },
4900        )
4901    }
4902
4903    /// Send a `$/cancel_request` notification for an arbitrary request ID to
4904    /// the default counterpart peer.
4905    ///
4906    /// Prefer [`SentRequest::cancel`] when you have the request handle: it
4907    /// already knows the correct peer, request ID, and proxy wrapping. Use this
4908    /// low-level method only when implementing custom routing with a request ID
4909    /// that is valid on this connection.
4910    pub fn send_cancel_request(
4911        &self,
4912        request_id: impl Into<crate::schema::v1::RequestId>,
4913    ) -> Result<(), crate::Error>
4914    where
4915        Counterpart: HasPeer<Counterpart>,
4916    {
4917        self.send_cancel_request_to(self.counterpart.clone(), request_id)
4918    }
4919
4920    /// Send a `$/cancel_request` notification for an arbitrary request ID to a
4921    /// specific peer.
4922    ///
4923    /// Prefer [`SentRequest::cancel`] when you have the request handle: it
4924    /// already knows the correct peer, request ID, and proxy wrapping. Use this
4925    /// low-level method only when implementing custom routing with a request ID
4926    /// that is valid on the target peer's connection.
4927    pub fn send_cancel_request_to<Peer: Role>(
4928        &self,
4929        peer: Peer,
4930        request_id: impl Into<crate::schema::v1::RequestId>,
4931    ) -> Result<(), crate::Error>
4932    where
4933        Counterpart: HasPeer<Peer>,
4934    {
4935        self.send_notification_to(
4936            peer,
4937            crate::schema::v1::CancelRequestNotification::new(request_id),
4938        )
4939    }
4940
4941    /// Register a dynamic message handler, used to intercept messages specific to a particular session
4942    /// or some similar modal thing.
4943    ///
4944    /// Dynamic message handlers run after the handlers registered on [`Builder`] and before the
4945    /// role's default handler. They receive messages that the builder handlers decline.
4946    ///
4947    /// The handler will stay registered until the returned registration guard is dropped.
4948    pub fn add_dynamic_handler(
4949        &self,
4950        handler: impl HandleDispatchFrom<Counterpart> + 'static,
4951    ) -> Result<DynamicHandlerGuard<Counterpart>, crate::Error> {
4952        let uuid = Uuid::new_v4();
4953        let active = Arc::new(AtomicBool::new(true));
4954        self.dynamic_handler_tx
4955            .unbounded_send(DynamicHandlerMessage::AddDynamicHandler(
4956                uuid,
4957                Box::new(GuardedDynamicHandler {
4958                    active: active.clone(),
4959                    handler,
4960                }),
4961            ))
4962            .map_err(crate::util::internal_error)?;
4963
4964        Ok(DynamicHandlerGuard::new(uuid, active, self.clone()))
4965    }
4966
4967    /// Wait until every dynamic-handler update queued before this call has
4968    /// been applied by the incoming protocol actor.
4969    pub(crate) fn dynamic_handler_barrier(&self) -> BoxFuture<'static, Result<(), crate::Error>> {
4970        let (acknowledgment_tx, acknowledgment_rx) = oneshot::channel();
4971        if let Err(error) =
4972            self.dynamic_handler_tx
4973                .unbounded_send(DynamicHandlerMessage::AcknowledgedBarrier(
4974                    acknowledgment_tx,
4975                ))
4976        {
4977            return future::ready(Err(crate::Error::into_internal_error(error))).boxed();
4978        }
4979
4980        async move {
4981            acknowledgment_rx.await.map_err(|error| {
4982                crate::util::internal_error(format!(
4983                    "dynamic-handler barrier was dropped before acknowledgment: {error}"
4984                ))
4985            })
4986        }
4987        .boxed()
4988    }
4989
4990    fn remove_dynamic_handler(&self, uuid: Uuid) {
4991        // Ignore errors
4992        drop(
4993            self.dynamic_handler_tx
4994                .unbounded_send(DynamicHandlerMessage::RemoveDynamicHandler(uuid)),
4995        );
4996    }
4997}
4998
4999struct GuardedDynamicHandler<Handler> {
5000    active: Arc<AtomicBool>,
5001    handler: Handler,
5002}
5003
5004impl<Counterpart, Handler> HandleDispatchFrom<Counterpart> for GuardedDynamicHandler<Handler>
5005where
5006    Counterpart: Role,
5007    Handler: HandleDispatchFrom<Counterpart>,
5008{
5009    async fn handle_dispatch_from(
5010        &mut self,
5011        message: Dispatch,
5012        connection: ConnectionTo<Counterpart>,
5013    ) -> Result<Handled<Dispatch>, crate::Error> {
5014        if !self.active.load(Ordering::Acquire) {
5015            return Ok(Handled::No {
5016                message,
5017                retry: false,
5018            });
5019        }
5020        self.handler.handle_dispatch_from(message, connection).await
5021    }
5022
5023    fn describe_chain(&self) -> impl Debug {
5024        self.handler.describe_chain()
5025    }
5026}
5027
5028/// A guard that keeps a dynamic message handler registered.
5029///
5030/// Dropping the guard immediately deactivates the handler and queues its
5031/// removal from the connection. Use [`detach`](Self::detach) to keep the
5032/// handler registered for the remaining lifetime of the connection.
5033#[must_use = "dropping this guard unregisters the dynamic handler"]
5034#[derive(Debug)]
5035pub struct DynamicHandlerGuard<R: Role> {
5036    uuid: Option<Uuid>,
5037    active: Arc<AtomicBool>,
5038    cx: ConnectionTo<R>,
5039    cleanup: Option<Arc<dyn DynamicHandlerCleanup>>,
5040}
5041
5042/// Private registration-local cleanup, independent of connection task admission.
5043pub(crate) trait DynamicHandlerCleanup: std::fmt::Debug + Send + Sync {
5044    fn close(&self);
5045    fn wait(&self) -> futures::future::BoxFuture<'static, ()>;
5046}
5047
5048impl<R: Role> DynamicHandlerGuard<R> {
5049    fn new(uuid: Uuid, active: Arc<AtomicBool>, cx: ConnectionTo<R>) -> Self {
5050        Self {
5051            uuid: Some(uuid),
5052            active,
5053            cx,
5054            cleanup: None,
5055        }
5056    }
5057
5058    #[cfg(feature = "unstable_mcp_over_acp")]
5059    pub(crate) fn with_cleanup(mut self, cleanup: Arc<dyn DynamicHandlerCleanup>) -> Self {
5060        self.cleanup = Some(cleanup);
5061        self
5062    }
5063
5064    pub(crate) fn cleanup(&self) -> Option<Arc<dyn DynamicHandlerCleanup>> {
5065        self.cleanup.clone()
5066    }
5067
5068    /// Keep the dynamic handler registered after this guard is dropped.
5069    ///
5070    /// The handler remains registered until the connection itself shuts down.
5071    /// Unlike leaking the guard, detaching does not retain an extra
5072    /// [`ConnectionTo`] handle.
5073    pub fn detach(mut self) {
5074        self.uuid = None;
5075    }
5076}
5077
5078impl<R: Role> Drop for DynamicHandlerGuard<R> {
5079    fn drop(&mut self) {
5080        if let Some(uuid) = self.uuid {
5081            self.active.store(false, Ordering::Release);
5082            if let Some(cleanup) = &self.cleanup {
5083                cleanup.close();
5084            }
5085            self.cx.remove_dynamic_handler(uuid);
5086        }
5087    }
5088}
5089
5090/// The context to respond to an incoming request.
5091///
5092/// This context is provided to request handlers and serves a dual role:
5093///
5094/// 1. **Respond to the request** - Use [`respond`](Self::respond) or
5095///    [`respond_with_result`](Self::respond_with_result) to send the response
5096/// 2. **Send other messages** - Use the [`ConnectionTo`] parameter passed to your
5097///    handler, which provides [`send_request`](`ConnectionTo::send_request`),
5098///    [`send_notification`](`ConnectionTo::send_notification`), and
5099///    [`spawn`](`ConnectionTo::spawn`)
5100///
5101/// # Example
5102///
5103/// ```no_run
5104/// # use agent_client_protocol_test::*;
5105/// # async fn example() -> Result<(), agent_client_protocol::Error> {
5106/// # let connection = mock_connection();
5107/// connection.on_receive_request(async |req: ProcessRequest, responder, cx| {
5108///     // Send a notification while processing
5109///     cx.send_notification(StatusUpdate {
5110///         message: "processing".into(),
5111///     })?;
5112///
5113///     // Do some work...
5114///     let result = process(&req.data)?;
5115///
5116///     // Respond to the request
5117///     responder.respond(ProcessResponse { result })
5118/// }, agent_client_protocol::on_receive_request!())
5119/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
5120/// # Ok(())
5121/// # }
5122/// ```
5123///
5124/// # Event Loop Considerations
5125///
5126/// Like all handlers, request handlers run on the event loop. Use
5127/// [`spawn`](ConnectionTo::spawn) for expensive operations to avoid blocking
5128/// the connection.
5129///
5130/// See the [Event Loop and Concurrency](Builder#event-loop-and-concurrency)
5131/// section for more details.
5132///
5133/// # Drop behavior
5134///
5135/// Dropping a responder for a request that arrived in a batch completes that
5136/// slot with an Internal Error, so one abandoned request cannot withhold valid
5137/// sibling responses forever. A responder for an individual request retains
5138/// the historical behavior: dropping it does not automatically send a reply.
5139#[must_use]
5140pub struct Responder<T: JsonRpcResponse = serde_json::Value> {
5141    /// The method of the request.
5142    method: String,
5143
5144    /// The `id` of the message we are replying to.
5145    id: RequestId,
5146
5147    /// Request-local cancellation state.
5148    cancellation: ResponderCancellation,
5149
5150    /// Whether this response is emitted on its own or collected into a batch.
5151    destination: ResponseDestination,
5152
5153    /// Function to send the response to its destination.
5154    ///
5155    /// For incoming requests: serializes to JSON and sends over the wire.
5156    /// For incoming responses: sends to the waiting oneshot channel.
5157    send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
5158
5159    /// Completes an abandoned batch slot unless an explicit response disarms it.
5160    drop_guard: ResponderDropGuard,
5161}
5162
5163struct ResponderDropGuard {
5164    message_tx: OutgoingMessageTx,
5165    id: RequestId,
5166    method: String,
5167    destination: ResponseDestination,
5168    armed: bool,
5169}
5170
5171impl ResponderDropGuard {
5172    fn disarm(&mut self) {
5173        self.armed = false;
5174    }
5175}
5176
5177impl Drop for ResponderDropGuard {
5178    fn drop(&mut self) {
5179        if !self.armed || !self.destination.is_batch() {
5180            return;
5181        }
5182
5183        if let Err(error) = send_raw_message(
5184            &self.message_tx,
5185            OutgoingMessage::AbandonedBatchResponse {
5186                id: self.id.clone(),
5187                method: self.method.clone(),
5188                destination: self.destination.clone(),
5189            },
5190        ) {
5191            tracing::debug!(
5192                id = ?self.id,
5193                method = %self.method,
5194                ?error,
5195                "could not complete abandoned JSON-RPC batch response slot"
5196            );
5197        }
5198    }
5199}
5200
5201impl<T: JsonRpcResponse> std::fmt::Debug for Responder<T> {
5202    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5203        f.debug_struct("Responder")
5204            .field("method", &self.method)
5205            .field("id", &self.id)
5206            .field("response_type", &std::any::type_name::<T>())
5207            .finish_non_exhaustive()
5208    }
5209}
5210
5211impl Responder<serde_json::Value> {
5212    /// Create a new request context for an incoming request.
5213    ///
5214    /// The response will be serialized to JSON and sent over the wire.
5215    fn new(
5216        message_tx: OutgoingMessageTx,
5217        method: String,
5218        id: RequestId,
5219        cancellation_registry: &RequestCancellationRegistry,
5220        destination: ResponseDestination,
5221    ) -> Self {
5222        let id_clone = id.clone();
5223        let method_clone = method.clone();
5224        let cancellation = cancellation_registry.register(&id);
5225        let send_destination = destination.clone();
5226        let drop_guard = ResponderDropGuard {
5227            message_tx: message_tx.clone(),
5228            id: id.clone(),
5229            method: method.clone(),
5230            destination: destination.clone(),
5231            armed: true,
5232        };
5233        Self {
5234            method,
5235            id,
5236            cancellation,
5237            destination,
5238            send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
5239                send_raw_message(
5240                    &message_tx,
5241                    OutgoingMessage::Response {
5242                        id: id_clone,
5243                        method: method_clone,
5244                        response,
5245                        destination: send_destination,
5246                    },
5247                )
5248            }),
5249            drop_guard,
5250        }
5251    }
5252
5253    /// Cast this request context to a different response type.
5254    ///
5255    /// The provided type `T` will be serialized to JSON before sending.
5256    pub fn cast<T: JsonRpcResponse>(self) -> Responder<T> {
5257        self.wrap_params(move |method, value| match value {
5258            Ok(value) => T::into_json(value, method),
5259            Err(e) => Err(e),
5260        })
5261    }
5262}
5263
5264impl<T: JsonRpcResponse> Responder<T> {
5265    /// Method of the incoming request
5266    #[must_use]
5267    pub fn method(&self) -> &str {
5268        &self.method
5269    }
5270
5271    /// ID of the incoming request.
5272    #[must_use]
5273    pub fn id(&self) -> &RequestId {
5274        &self.id
5275    }
5276
5277    /// Returns the cancellation marker for this request.
5278    ///
5279    /// The marker is set when the peer sends `$/cancel_request` for this
5280    /// request's JSON-RPC ID. Cancellation is cooperative: handlers should use
5281    /// the marker to stop long-running work and then decide whether to respond
5282    /// with [`Error::request_cancelled`] or partial data.
5283    ///
5284    /// [`Error::request_cancelled`]: crate::Error::request_cancelled
5285    #[must_use]
5286    pub fn cancellation(&self) -> RequestCancellation {
5287        self.cancellation.cancellation()
5288    }
5289
5290    /// Convert to a `Responder` that expects a JSON value
5291    /// and which checks (dynamically) that the JSON value it receives
5292    /// can be converted to `T`.
5293    pub fn erase_to_json(self) -> Responder<serde_json::Value> {
5294        self.wrap_params(|method, value| T::from_value(method, value?))
5295    }
5296
5297    /// Return a new Responder with a different method name.
5298    pub fn wrap_method(mut self, method: String) -> Responder<T> {
5299        self.drop_guard.method.clone_from(&method);
5300        Responder {
5301            method,
5302            id: self.id,
5303            cancellation: self.cancellation,
5304            destination: self.destination,
5305            send_fn: self.send_fn,
5306            drop_guard: self.drop_guard,
5307        }
5308    }
5309
5310    /// Return a new Responder that expects a response of type U.
5311    ///
5312    /// `wrap_fn` will be invoked with the method name and the result to transform
5313    /// type `U` into type `T` before sending.
5314    pub fn wrap_params<U: JsonRpcResponse>(
5315        self,
5316        wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
5317    ) -> Responder<U> {
5318        let method = self.method.clone();
5319        Responder {
5320            method: self.method,
5321            id: self.id,
5322            cancellation: self.cancellation,
5323            destination: self.destination,
5324            send_fn: Box::new(move |input: Result<U, crate::Error>| {
5325                let t_value = wrap_fn(&method, input);
5326                (self.send_fn)(t_value)
5327            }),
5328            drop_guard: self.drop_guard,
5329        }
5330    }
5331
5332    /// Respond to the JSON-RPC request with either a value (`Ok`) or an error (`Err`).
5333    pub fn respond_with_result(
5334        mut self,
5335        response: Result<T, crate::Error>,
5336    ) -> Result<(), crate::Error> {
5337        tracing::debug!(id = ?self.id, "respond called");
5338        self.drop_guard.disarm();
5339        (self.send_fn)(response)
5340    }
5341
5342    /// Respond to the JSON-RPC request with a value.
5343    pub fn respond(self, response: T) -> Result<(), crate::Error> {
5344        self.respond_with_result(Ok(response))
5345    }
5346
5347    /// Respond to the JSON-RPC request with an internal error containing a message.
5348    pub fn respond_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
5349        self.respond_with_error(crate::util::internal_error(message))
5350    }
5351
5352    /// Respond to the JSON-RPC request with an error.
5353    pub fn respond_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
5354        tracing::debug!(id = ?self.id, ?error, "respond_with_error called");
5355        self.respond_with_result(Err(error))
5356    }
5357
5358    fn reply_target(&self) -> RequestReplyTarget {
5359        RequestReplyTarget {
5360            id: self.id.clone(),
5361            method: self.method.clone(),
5362            destination: self.destination.clone(),
5363        }
5364    }
5365}
5366
5367/// Context for handling an incoming JSON-RPC response.
5368///
5369/// This is the response-side counterpart to [`Responder`]. While `Responder` handles
5370/// incoming requests (where you send a response over the wire), `ResponseRouter` handles
5371/// incoming responses (where you route the response to a local task waiting for it).
5372///
5373/// Both are fundamentally "sinks" that push the message through a `send_fn`, but they
5374/// represent different points in the message lifecycle and carry different metadata.
5375///
5376/// # Drop Behavior
5377///
5378/// Dropping a `ResponseRouter` without routing the response (for example, from a
5379/// dispatch handler that claims a [`Dispatch::Response`]) discards the
5380/// response: the local awaiter observes the response as never received. The
5381/// request still counts as settled: routing a response this far disarms the
5382/// originating [`SentRequest`]'s drop-time auto-cancellation even if the router
5383/// is never invoked, since the peer has already answered.
5384#[must_use]
5385pub struct ResponseRouter<T: JsonRpcResponse = serde_json::Value> {
5386    /// The method of the original request.
5387    method: String,
5388
5389    /// The `id` of the original request.
5390    id: RequestId,
5391
5392    /// The RoleId to which the original request was sent
5393    /// (and hence from which the reply is expected).
5394    role_id: RoleId,
5395
5396    /// Function to send the response to the waiting task.
5397    send_fn: Box<dyn FnOnce(Result<T, crate::Error>) -> Result<(), crate::Error> + Send>,
5398
5399    /// Shared route used to deliver a dispatch-handler error to the same waiter.
5400    reply_target: ResponseReplyTarget,
5401}
5402
5403impl<T: JsonRpcResponse> std::fmt::Debug for ResponseRouter<T> {
5404    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5405        f.debug_struct("ResponseRouter")
5406            .field("method", &self.method)
5407            .field("id", &self.id)
5408            .field("response_type", &std::any::type_name::<T>())
5409            .finish_non_exhaustive()
5410    }
5411}
5412
5413impl ResponseRouter<serde_json::Value> {
5414    /// Create a new response context for routing a response to a local awaiter.
5415    ///
5416    /// When [`route_with_result`](Self::route_with_result) is called, the response is sent through the oneshot
5417    /// channel to the code that originally sent the request. If that receiver was
5418    /// dropped, the response is discarded because there is no local awaiter left.
5419    fn new(id: RequestId, pending_reply: PendingReply, dispatch: ResponseDispatch) -> Self {
5420        let PendingReply {
5421            method,
5422            role_id,
5423            sender,
5424            cancellation_disarm,
5425            ordering,
5426            response_route_hook,
5427            accepts_lower_initialize_version: _,
5428        } = pending_reply;
5429        let reply_target = ResponseReplyTarget {
5430            id: id.clone(),
5431            method: method.clone(),
5432            sender: Arc::new(Mutex::new(Some(sender))),
5433            ordering,
5434            dispatch,
5435        };
5436        let send_target = reply_target.clone();
5437        // A response for the request reached this router, so the request is
5438        // settled from the peer's perspective and a `$/cancel_request` could
5439        // only ever be redundant. Disarm immediately so handlers may retain
5440        // the router without leaving auto-cancellation armed.
5441        cancellation_disarm.disarm();
5442        let hook_method = method.clone();
5443        Self {
5444            method,
5445            id,
5446            role_id,
5447            send_fn: Box::new(move |response: Result<serde_json::Value, crate::Error>| {
5448                let response = match response {
5449                    Ok(value) => match response_route_hook {
5450                        Some(hook) => hook(&hook_method, &value).map(|()| value),
5451                        None => Ok(value),
5452                    },
5453                    Err(error) => Err(error),
5454                };
5455                send_target.route(response);
5456                Ok(())
5457            }),
5458            reply_target,
5459        }
5460    }
5461
5462    /// Cast this response context to a different response type.
5463    ///
5464    /// The provided type `T` will be serialized to JSON before sending.
5465    pub fn cast<T: JsonRpcResponse>(self) -> ResponseRouter<T> {
5466        self.wrap_params(move |method, value| match value {
5467            Ok(value) => T::into_json(value, method),
5468            Err(e) => Err(e),
5469        })
5470    }
5471}
5472
5473impl<T: JsonRpcResponse> ResponseRouter<T> {
5474    /// Method of the original request
5475    #[must_use]
5476    pub fn method(&self) -> &str {
5477        &self.method
5478    }
5479
5480    /// ID of the original request.
5481    #[must_use]
5482    pub fn id(&self) -> &RequestId {
5483        &self.id
5484    }
5485
5486    /// The peer to which the original request was sent.
5487    ///
5488    /// This is the peer from which we expect to receive the response.
5489    #[must_use]
5490    pub fn role_id(&self) -> RoleId {
5491        self.role_id.clone()
5492    }
5493
5494    /// Convert to a `ResponseRouter` that expects a JSON value
5495    /// and which checks (dynamically) that the JSON value it receives
5496    /// can be converted to `T`.
5497    pub fn erase_to_json(self) -> ResponseRouter<serde_json::Value> {
5498        self.wrap_params(|method, value| T::from_value(method, value?))
5499    }
5500
5501    /// Return a new ResponseRouter that expects a response of type U.
5502    ///
5503    /// `wrap_fn` will be invoked with the method name and the result to transform
5504    /// type `U` into type `T` before sending.
5505    fn wrap_params<U: JsonRpcResponse>(
5506        self,
5507        wrap_fn: impl FnOnce(&str, Result<U, crate::Error>) -> Result<T, crate::Error> + Send + 'static,
5508    ) -> ResponseRouter<U> {
5509        let method = self.method.clone();
5510        ResponseRouter {
5511            method: self.method,
5512            id: self.id,
5513            role_id: self.role_id,
5514            send_fn: Box::new(move |input: Result<U, crate::Error>| {
5515                let t_value = wrap_fn(&method, input);
5516                (self.send_fn)(t_value)
5517            }),
5518            reply_target: self.reply_target,
5519        }
5520    }
5521
5522    /// Route the response result to the waiting task.
5523    pub fn route_with_result(self, response: Result<T, crate::Error>) -> Result<(), crate::Error> {
5524        tracing::debug!(id = ?self.id, "response routed to awaiter");
5525        (self.send_fn)(response)
5526    }
5527
5528    /// Route a successful response value to the waiting task.
5529    pub fn route(self, response: T) -> Result<(), crate::Error> {
5530        self.route_with_result(Ok(response))
5531    }
5532
5533    /// Route an internal error to the waiting task.
5534    pub fn route_with_internal_error(self, message: impl ToString) -> Result<(), crate::Error> {
5535        self.route_with_error(crate::util::internal_error(message))
5536    }
5537
5538    /// Route an error response to the waiting task.
5539    pub fn route_with_error(self, error: crate::Error) -> Result<(), crate::Error> {
5540        tracing::debug!(id = ?self.id, ?error, "error routed to awaiter");
5541        self.route_with_result(Err(error))
5542    }
5543}
5544
5545/// Common bounds for any JSON-RPC message.
5546///
5547/// # Derive Macro
5548///
5549/// For simple message types, you can use the `JsonRpcRequest` or `JsonRpcNotification` derive macros
5550/// which will implement both `JsonRpcMessage` and the respective trait. See [`JsonRpcRequest`] and
5551/// [`JsonRpcNotification`] for examples.
5552pub trait JsonRpcMessage: 'static + Debug + Sized + Send + Clone {
5553    /// Check if this message type matches the given method name.
5554    fn matches_method(method: &str) -> bool;
5555
5556    /// The method name for the message.
5557    fn method(&self) -> &str;
5558
5559    /// Convert this message into an untyped message.
5560    fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error>;
5561
5562    /// Parse this type from a method name and parameters.
5563    ///
5564    /// Returns an error if the method doesn't match or deserialization fails.
5565    /// Callers should use `matches_method` first to check if this type handles the method.
5566    fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error>;
5567}
5568
5569/// Defines the "payload" of a successful response to a JSON-RPC request.
5570///
5571/// # Derive Macro
5572///
5573/// Use `#[derive(JsonRpcResponse)]` to automatically implement this trait:
5574///
5575/// ```ignore
5576/// use agent_client_protocol::JsonRpcResponse;
5577/// use serde::{Serialize, Deserialize};
5578///
5579/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcResponse)]
5580/// struct HelloResponse {
5581///     greeting: String,
5582/// }
5583/// ```
5584pub trait JsonRpcResponse: 'static + Debug + Sized + Send + Clone {
5585    /// Convert this message into a JSON value.
5586    fn into_json(self, method: &str) -> Result<serde_json::Value, crate::Error>;
5587
5588    /// Parse a JSON value into the response type.
5589    fn from_value(method: &str, value: serde_json::Value) -> Result<Self, crate::Error>;
5590}
5591
5592impl JsonRpcResponse for serde_json::Value {
5593    fn from_value(_method: &str, value: serde_json::Value) -> Result<Self, crate::Error> {
5594        Ok(value)
5595    }
5596
5597    fn into_json(self, _method: &str) -> Result<serde_json::Value, crate::Error> {
5598        Ok(self)
5599    }
5600}
5601
5602/// A struct that represents a notification (JSON-RPC message that does not expect a response).
5603///
5604/// # Derive Macro
5605///
5606/// Use `#[derive(JsonRpcNotification)]` to automatically implement both `JsonRpcMessage` and `JsonRpcNotification`:
5607///
5608/// ```ignore
5609/// use agent_client_protocol::JsonRpcNotification;
5610/// use serde::{Serialize, Deserialize};
5611///
5612/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcNotification)]
5613/// #[notification(method = "_ping")]
5614/// struct PingNotification {
5615///     timestamp: u64,
5616/// }
5617/// ```
5618pub trait JsonRpcNotification: JsonRpcMessage {}
5619
5620/// A struct that represents a request (JSON-RPC message expecting a response).
5621///
5622/// # Derive Macro
5623///
5624/// Use `#[derive(JsonRpcRequest)]` to automatically implement both `JsonRpcMessage` and `JsonRpcRequest`:
5625///
5626/// ```ignore
5627/// use agent_client_protocol::{JsonRpcRequest, JsonRpcResponse};
5628/// use serde::{Serialize, Deserialize};
5629///
5630/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcRequest)]
5631/// #[request(method = "_hello", response = HelloResponse)]
5632/// struct HelloRequest {
5633///     name: String,
5634/// }
5635///
5636/// #[derive(Debug, Clone, Serialize, Deserialize, JsonRpcResponse)]
5637/// struct HelloResponse {
5638///     greeting: String,
5639/// }
5640/// ```
5641pub trait JsonRpcRequest: JsonRpcMessage {
5642    /// The type of data expected in response.
5643    type Response: JsonRpcResponse;
5644}
5645
5646/// An incoming request, notification, or response being dispatched through handlers.
5647/// Requests include the context used to answer them; responses include the context
5648/// used to route them to the local requester.
5649///
5650/// Type parameters allow specifying the concrete request and notification types.
5651/// By default, both are `UntypedMessage` for dynamic dispatch.
5652/// The request context's response type matches the request's response type.
5653#[derive(Debug)]
5654pub enum Dispatch<Req: JsonRpcRequest = UntypedMessage, Notif: JsonRpcNotification = UntypedMessage>
5655{
5656    /// Incoming request and the context where the response should be sent.
5657    Request(Req, Responder<Req::Response>),
5658
5659    /// Incoming notification.
5660    Notification(Notif),
5661
5662    /// Incoming response to a request we sent.
5663    ///
5664    /// The first field is the response result (success or error from the remote).
5665    /// The second field is the context for forwarding the response to its destination
5666    /// (typically a waiting oneshot channel).
5667    Response(
5668        Result<Req::Response, crate::Error>,
5669        ResponseRouter<Req::Response>,
5670    ),
5671}
5672
5673impl<Req: JsonRpcRequest, Notif: JsonRpcNotification> Dispatch<Req, Notif> {
5674    /// Map the request and notification types to new types.
5675    ///
5676    /// Note: Response variants are passed through unchanged since they don't
5677    /// contain a parseable message payload.
5678    pub fn map<Req1, Notif1>(
5679        self,
5680        map_request: impl FnOnce(Req, Responder<Req::Response>) -> (Req1, Responder<Req1::Response>),
5681        map_notification: impl FnOnce(Notif) -> Notif1,
5682    ) -> Dispatch<Req1, Notif1>
5683    where
5684        Req1: JsonRpcRequest<Response = Req::Response>,
5685        Notif1: JsonRpcNotification,
5686    {
5687        match self {
5688            Dispatch::Request(request, responder) => {
5689                let (new_request, new_responder) = map_request(request, responder);
5690                Dispatch::Request(new_request, new_responder)
5691            }
5692            Dispatch::Notification(notification) => {
5693                let new_notification = map_notification(notification);
5694                Dispatch::Notification(new_notification)
5695            }
5696            Dispatch::Response(result, router) => Dispatch::Response(result, router),
5697        }
5698    }
5699
5700    /// Convert the message in self to an untyped message.
5701    ///
5702    /// Note: Response variants don't have an untyped message representation.
5703    /// This returns an error for Response variants.
5704    pub fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
5705        match self {
5706            Dispatch::Request(request, _) => request.to_untyped_message(),
5707            Dispatch::Notification(notification) => notification.to_untyped_message(),
5708            Dispatch::Response(_, _) => Err(crate::util::internal_error(
5709                "Response variant has no untyped message representation",
5710            )),
5711        }
5712    }
5713
5714    /// Convert self to an untyped message context.
5715    ///
5716    /// Note: Response variants cannot be converted. This returns an error for Response variants.
5717    pub fn into_untyped_dispatch(self) -> Result<Dispatch, crate::Error> {
5718        match self {
5719            Dispatch::Request(request, responder) => Ok(Dispatch::Request(
5720                request.to_untyped_message()?,
5721                responder.erase_to_json(),
5722            )),
5723            Dispatch::Notification(notification) => {
5724                Ok(Dispatch::Notification(notification.to_untyped_message()?))
5725            }
5726            Dispatch::Response(_, _) => Err(crate::util::internal_error(
5727                "cannot convert Response variant to untyped message context",
5728            )),
5729        }
5730    }
5731
5732    /// Returns the request ID if this is a request or response, None if notification.
5733    pub fn id(&self) -> Option<&RequestId> {
5734        match self {
5735            Dispatch::Request(_, cx) => Some(cx.id()),
5736            Dispatch::Notification(_) => None,
5737            Dispatch::Response(_, cx) => Some(cx.id()),
5738        }
5739    }
5740
5741    fn handler_error_target(&self) -> Option<HandlerErrorTarget> {
5742        match self {
5743            Dispatch::Request(_, responder) => {
5744                Some(HandlerErrorTarget::Request(responder.reply_target()))
5745            }
5746            Dispatch::Notification(_) => None,
5747            Dispatch::Response(_, router) => {
5748                Some(HandlerErrorTarget::Response(router.reply_target.clone()))
5749            }
5750        }
5751    }
5752
5753    /// Returns the method of the message.
5754    ///
5755    /// For requests and notifications, this is the method from the message payload.
5756    /// For responses, this is the method of the original request.
5757    pub fn method(&self) -> &str {
5758        match self {
5759            Dispatch::Request(msg, _) => msg.method(),
5760            Dispatch::Notification(msg) => msg.method(),
5761            Dispatch::Response(_, cx) => cx.method(),
5762        }
5763    }
5764}
5765
5766impl Dispatch {
5767    /// Attempts to parse `self` into a typed message context.
5768    ///
5769    /// # Returns
5770    ///
5771    /// * `Ok(Ok(typed))` if this dispatch matches the requested type for its variant
5772    /// * `Ok(Err(self))` if it does not match the requested type for its variant
5773    /// * `Err` if its method matches the requested type but parsing fails
5774    #[tracing::instrument(skip(self), fields(Request = ?std::any::type_name::<Req>(), Notif = ?std::any::type_name::<Notif>()), level = "trace", ret)]
5775    pub(crate) fn into_typed_dispatch<Req: JsonRpcRequest, Notif: JsonRpcNotification>(
5776        self,
5777    ) -> Result<Result<Dispatch<Req, Notif>, Dispatch>, crate::Error> {
5778        tracing::debug!(
5779            message = ?self,
5780            "into_typed_dispatch"
5781        );
5782        match self {
5783            Dispatch::Request(message, responder) => {
5784                if Req::matches_method(&message.method) {
5785                    match Req::parse_message(&message.method, &message.params) {
5786                        Ok(req) => {
5787                            tracing::trace!(?req, "parsed ok");
5788                            Ok(Ok(Dispatch::Request(req, responder.cast())))
5789                        }
5790                        Err(err) => {
5791                            tracing::trace!(?err, "parse error");
5792                            Err(err)
5793                        }
5794                    }
5795                } else {
5796                    tracing::trace!("method doesn't match");
5797                    Ok(Err(Dispatch::Request(message, responder)))
5798                }
5799            }
5800
5801            Dispatch::Notification(message) => {
5802                if Notif::matches_method(&message.method) {
5803                    match Notif::parse_message(&message.method, &message.params) {
5804                        Ok(notif) => {
5805                            tracing::trace!(?notif, "parse ok");
5806                            Ok(Ok(Dispatch::Notification(notif)))
5807                        }
5808                        Err(err) => {
5809                            tracing::trace!(?err, "parse error");
5810                            Err(err)
5811                        }
5812                    }
5813                } else {
5814                    tracing::trace!("method doesn't match");
5815                    Ok(Err(Dispatch::Notification(message)))
5816                }
5817            }
5818
5819            Dispatch::Response(result, cx) => {
5820                let method = cx.method();
5821                if Req::matches_method(method) {
5822                    // Parse the response result
5823                    let typed_result = match result {
5824                        Ok(value) => {
5825                            match <Req::Response as JsonRpcResponse>::from_value(method, value) {
5826                                Ok(parsed) => {
5827                                    tracing::trace!(?parsed, "parse ok");
5828                                    Ok(parsed)
5829                                }
5830                                Err(err) => {
5831                                    tracing::trace!(?err, "parse error");
5832                                    return Err(err);
5833                                }
5834                            }
5835                        }
5836                        Err(err) => {
5837                            tracing::trace!("error, passthrough");
5838                            Err(err)
5839                        }
5840                    };
5841                    Ok(Ok(Dispatch::Response(typed_result, cx.cast())))
5842                } else {
5843                    tracing::trace!("method doesn't match");
5844                    Ok(Err(Dispatch::Response(result, cx)))
5845                }
5846            }
5847        }
5848    }
5849
5850    /// True if this message has a field with the given name.
5851    ///
5852    /// Returns `false` for Response variants.
5853    #[must_use]
5854    pub fn has_field(&self, field_name: &str) -> bool {
5855        self.message()
5856            .and_then(|m| m.params().get(field_name))
5857            .is_some()
5858    }
5859
5860    /// Returns true if this message has a session-id field.
5861    ///
5862    /// Returns `false` for Response variants.
5863    pub(crate) fn has_session_id(&self) -> bool {
5864        self.has_field("sessionId")
5865    }
5866
5867    /// Extract the ACP session-id from this message (if any).
5868    ///
5869    /// Returns `Ok(None)` for Response variants.
5870    pub(crate) fn get_session_id(&self) -> Result<Option<SessionId>, crate::Error> {
5871        let Some(message) = self.message() else {
5872            return Ok(None);
5873        };
5874        let Some(value) = message.params().get("sessionId") else {
5875            return Ok(None);
5876        };
5877        let session_id = serde_json::from_value(value.clone())?;
5878        Ok(Some(session_id))
5879    }
5880
5881    /// Try to parse this as a notification of the given type.
5882    ///
5883    /// # Returns
5884    ///
5885    /// * `Ok(Ok(typed))` if this is a notification of the requested type
5886    /// * `Ok(Err(self))` if this is not a matching notification
5887    /// * `Err` if its method matches the requested type but parsing fails
5888    pub fn into_notification<N: JsonRpcNotification>(
5889        self,
5890    ) -> Result<Result<N, Dispatch>, crate::Error> {
5891        match self {
5892            Dispatch::Notification(msg) => {
5893                if !N::matches_method(&msg.method) {
5894                    return Ok(Err(Dispatch::Notification(msg)));
5895                }
5896                match N::parse_message(&msg.method, &msg.params) {
5897                    Ok(n) => Ok(Ok(n)),
5898                    Err(err) => Err(err),
5899                }
5900            }
5901            Dispatch::Request(..) | Dispatch::Response(..) => Ok(Err(self)),
5902        }
5903    }
5904
5905    /// Try to parse this as a request of the given type.
5906    ///
5907    /// # Returns
5908    ///
5909    /// * `Ok(Ok(typed))` if this is a request of the requested type
5910    /// * `Ok(Err(self))` if this is not a matching request
5911    /// * `Err` if its method matches the requested type but parsing fails
5912    pub fn into_request<Req: JsonRpcRequest>(
5913        self,
5914    ) -> Result<Result<(Req, Responder<Req::Response>), Dispatch>, crate::Error> {
5915        match self {
5916            Dispatch::Request(msg, responder) => {
5917                if !Req::matches_method(&msg.method) {
5918                    return Ok(Err(Dispatch::Request(msg, responder)));
5919                }
5920                match Req::parse_message(&msg.method, &msg.params) {
5921                    Ok(req) => Ok(Ok((req, responder.cast()))),
5922                    Err(err) => Err(err),
5923                }
5924            }
5925            Dispatch::Notification(..) | Dispatch::Response(..) => Ok(Err(self)),
5926        }
5927    }
5928}
5929
5930impl<M: JsonRpcRequest + JsonRpcNotification> Dispatch<M, M> {
5931    /// Returns the message payload for requests and notifications.
5932    ///
5933    /// Returns `None` for Response variants since they don't contain a message payload.
5934    pub fn message(&self) -> Option<&M> {
5935        match self {
5936            Dispatch::Request(msg, _) | Dispatch::Notification(msg) => Some(msg),
5937            Dispatch::Response(_, _) => None,
5938        }
5939    }
5940
5941    /// Map the request/notification message.
5942    ///
5943    /// Response variants pass through unchanged.
5944    pub(crate) fn try_map_message(
5945        self,
5946        map_message: impl FnOnce(M) -> Result<M, crate::Error>,
5947    ) -> Result<Dispatch<M, M>, crate::Error> {
5948        match self {
5949            Dispatch::Request(request, cx) => Ok(Dispatch::Request(map_message(request)?, cx)),
5950            Dispatch::Notification(notification) => {
5951                Ok(Dispatch::<M, M>::Notification(map_message(notification)?))
5952            }
5953            Dispatch::Response(result, cx) => Ok(Dispatch::Response(result, cx)),
5954        }
5955    }
5956}
5957
5958/// An incoming JSON message without any typing. Can be a request or a notification.
5959#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
5960pub struct UntypedMessage {
5961    /// The JSON-RPC method name
5962    pub method: String,
5963    /// The JSON-RPC parameters as a raw JSON value
5964    pub params: serde_json::Value,
5965}
5966
5967impl UntypedMessage {
5968    /// Returns an untyped message with the given method and parameters.
5969    pub fn new(method: &str, params: impl Serialize) -> Result<Self, crate::Error> {
5970        let params = serde_json::to_value(params)?;
5971        Ok(Self {
5972            method: method.to_string(),
5973            params,
5974        })
5975    }
5976
5977    /// Returns the method name
5978    #[must_use]
5979    pub fn method(&self) -> &str {
5980        &self.method
5981    }
5982
5983    /// Returns the parameters as a JSON value
5984    #[must_use]
5985    pub fn params(&self) -> &serde_json::Value {
5986        &self.params
5987    }
5988
5989    /// Consumes this message and returns the method and params
5990    #[must_use]
5991    pub fn into_parts(self) -> (String, serde_json::Value) {
5992        (self.method, self.params)
5993    }
5994
5995    /// Convert `self` to a raw JSON-RPC message.
5996    pub(crate) fn into_raw_jsonrpc_message(
5997        self,
5998        id: Option<RequestId>,
5999    ) -> Result<RawJsonRpcMessage, crate::Error> {
6000        let Self { method, params } = self;
6001        match id {
6002            Some(id) => RawJsonRpcMessage::request(method, params, id),
6003            None => RawJsonRpcMessage::notification(method, params),
6004        }
6005    }
6006}
6007
6008impl JsonRpcMessage for UntypedMessage {
6009    fn matches_method(_method: &str) -> bool {
6010        // UntypedMessage matches any method - it's the untyped fallback
6011        true
6012    }
6013
6014    fn method(&self) -> &str {
6015        &self.method
6016    }
6017
6018    fn to_untyped_message(&self) -> Result<UntypedMessage, crate::Error> {
6019        Ok(self.clone())
6020    }
6021
6022    fn parse_message(method: &str, params: &impl Serialize) -> Result<Self, crate::Error> {
6023        UntypedMessage::new(method, params)
6024    }
6025}
6026
6027impl JsonRpcRequest for UntypedMessage {
6028    type Response = serde_json::Value;
6029}
6030
6031impl JsonRpcNotification for UntypedMessage {}
6032
6033/// Represents a pending response of type `R` from an outgoing request.
6034///
6035/// Returned by [`ConnectionTo::send_request`], this type provides explicit response-consumption
6036/// modes. The API is intentionally designed to make it difficult to accidentally wait for a
6037/// response inside the dispatch loop.
6038///
6039/// # Anti-Footgun Design
6040///
6041/// You cannot directly `.await` a `SentRequest`. Instead, you must choose how to handle
6042/// the response:
6043///
6044/// ## Option 1: Register an Ordered Callback (Safe in Handlers)
6045///
6046/// Calling [`on_receiving_result`](Self::on_receiving_result) registers the callback and returns
6047/// immediately. When ordered consumption is selected before the response is routed during its
6048/// original dispatch, the loop waits for the callback to complete before processing the next
6049/// message:
6050///
6051/// ```no_run
6052/// # use agent_client_protocol_test::*;
6053/// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
6054/// cx.send_request(MyRequest {})
6055///     .on_receiving_result(async |result| {
6056///         match result {
6057///             Ok(response) => {
6058///                 // Handle successful response
6059///                 Ok(())
6060///             }
6061///             Err(error) => {
6062///                 // Handle error
6063///                 Err(error)
6064///             }
6065///         }
6066///     })?;
6067/// # Ok(())
6068/// # }
6069/// ```
6070///
6071/// ## Option 2: Wait Outside the Dispatch Loop
6072///
6073/// Use [`block_task`](Self::block_task) only when the current task already runs outside the
6074/// dispatch loop—for example, in the foreground future passed to `connect_with` or in a task
6075/// created with [`ConnectionTo::spawn`]. Never await it in a handler:
6076///
6077/// ```no_run
6078/// # use agent_client_protocol_test::*;
6079/// # async fn example(cx: agent_client_protocol::ConnectionTo<agent_client_protocol::UntypedRole>) -> Result<(), agent_client_protocol::Error> {
6080/// // ✅ Safe: Spawned task runs concurrently
6081/// cx.spawn({
6082///     let cx = cx.clone();
6083///     async move {
6084///         let response = cx.send_request(MyRequest {})
6085///             .block_task()
6086///             .await?;
6087///         // Process response...
6088///         Ok(())
6089///     }
6090/// })?;
6091/// # Ok(())
6092/// # }
6093/// ```
6094///
6095/// ```no_run
6096/// # use agent_client_protocol_test::*;
6097/// # async fn example() -> Result<(), agent_client_protocol::Error> {
6098/// # let connection = mock_connection();
6099/// // ❌ NEVER do this in a handler - blocks the event loop!
6100/// connection.on_receive_request(async |req: MyRequest, responder, cx| {
6101///     let response = cx.send_request(MyRequest {})
6102///         .block_task()  // This will deadlock!
6103///         .await?;
6104///     responder.respond(response)
6105/// }, agent_client_protocol::on_receive_request!())
6106/// # .connect_to(agent_client_protocol_test::MockTransport).await?;
6107/// # Ok(())
6108/// # }
6109/// ```
6110///
6111/// # Why This Design?
6112///
6113/// If you block the event loop while waiting for a response, the connection cannot process
6114/// the incoming response message, creating a deadlock. This API design prevents that footgun
6115/// by making blocking explicit and encouraging non-blocking patterns.
6116///
6117/// # Drop Behavior
6118///
6119/// By default, dropping a `SentRequest` before the SDK has received the
6120/// response sends a `$/cancel_request` notification asking the peer to cancel
6121/// the request, then discards the response when it arrives. Requests whose
6122/// eventual response should be ignored, but which should keep running on the
6123/// peer, should use [`detach`](Self::detach) instead.
6124///
6125/// # Incoming Transport EOF
6126///
6127/// If the incoming transport reaches clean EOF before the response arrives, every
6128/// consumption mode receives an error with the message `Incoming transport
6129/// closed` and data containing
6130/// `{"reason":"incoming_transport_closed","method":"..."}`. Requests made
6131/// after incoming EOF fail immediately with the same error. Use
6132/// [`is_incoming_transport_closed`] to identify it.
6133#[must_use = "dropping a SentRequest asks the peer to cancel the request and \
6134              discards the response; consume it with `block_task`, \
6135              `on_receiving_result`, `forward_response_to`, or `detach`"]
6136pub struct SentRequest<T> {
6137    id: RequestId,
6138    method: String,
6139    task_tx: TaskTx,
6140    response_rx: oneshot::Receiver<ResponsePayload>,
6141    to_result: Box<dyn FnOnce(serde_json::Value) -> Result<T, crate::Error> + Send>,
6142    cancellation: SentRequestCancellation,
6143    response_ordering: ResponseOrdering,
6144    /// Cancellation markers of other (incoming) requests whose cancellation
6145    /// should be forwarded to this request. See
6146    /// [`forward_cancellation_from`](Self::forward_cancellation_from).
6147    cancellation_sources: Vec<RequestCancellation>,
6148}
6149
6150/// A request that has not been published to its connection.
6151///
6152/// Created by [`ConnectionTo::prepare_request`] or
6153/// [`ConnectionTo::prepare_request_to`]. Preparation serializes the request but
6154/// does not register a pending reply or enqueue outgoing traffic. Dropping this
6155/// value sends neither the request nor a cancellation notification.
6156///
6157/// A consuming method publishes the request synchronously:
6158///
6159/// - [`on_receiving_result`](Self::on_receiving_result),
6160///   [`on_receiving_ok_result`](Self::on_receiving_ok_result), and
6161///   [`forward_response_to`](Self::forward_response_to) register ordered response
6162///   handling before publication. When a peer response is routed during its
6163///   original dispatch, later inbound messages wait for that handling to finish.
6164/// - [`block_task`](Self::block_task) publishes immediately and returns an
6165///   unordered response future. Publication does not wait for its first poll.
6166/// - [`detach`](Self::detach) publishes immediately and discards the response.
6167///
6168/// Ordered callbacks must do bounded work and must not await later inbound
6169/// traffic on the same connection. EOF failures and responses routed through a
6170/// retained [`ResponseRouter`] after their original dispatch have no ordering
6171/// barrier. See [`crate::concepts::ordering`].
6172///
6173/// # Errors
6174///
6175/// Preparation and publication failures are delivered to the selected response
6176/// consumer. Callback-style methods return an error if their task cannot be
6177/// registered; in that case the request is not published. [`detach`](Self::detach)
6178/// returns preparation or publication errors directly because it has no response
6179/// consumer. A callback returning an error terminates the connection.
6180#[must_use = "a prepared request is not sent until consumed with `block_task`, \
6181              `on_receiving_result`, `forward_response_to`, or `detach`"]
6182pub struct PreparedRequest<T> {
6183    sent: SentRequest<T>,
6184    publication: RequestPublication,
6185}
6186
6187struct RequestPublication {
6188    message: Result<OutgoingMessage, crate::Error>,
6189    pending_reply: PendingReply,
6190    message_tx: OutgoingMessageTx,
6191    pending_replies: PendingRepliesRegistrar,
6192    incoming_closed: IncomingClosed,
6193}
6194
6195impl RequestPublication {
6196    fn publish(self) -> Result<(), crate::Error> {
6197        let message = if self.incoming_closed.is_closing() {
6198            Err(incoming_transport_closed_error(&self.pending_reply.method))
6199        } else {
6200            self.message
6201        };
6202        let message = match message {
6203            Ok(message) => message,
6204            Err(error) => {
6205                self.pending_reply.fail(error.clone());
6206                return Err(error);
6207            }
6208        };
6209        let OutgoingMessage::Request { id, method, .. } = &message else {
6210            unreachable!();
6211        };
6212        let id = id.clone();
6213        let method = method.clone();
6214        let cancellation_disarm = self.pending_reply.cancellation_disarm.clone();
6215        // Register before enqueueing so incoming EOF can fail every observable
6216        // request before close callbacks begin. The outgoing actor checks that
6217        // the registration still exists before sending the request.
6218        self.pending_replies
6219            .subscribe(id.clone(), self.pending_reply, &self.incoming_closed)?;
6220        if self.message_tx.unbounded_send(message).is_err() {
6221            let error = if self.incoming_closed.is_closing() {
6222                incoming_transport_closed_error(&method)
6223            } else {
6224                crate::util::internal_error(format!("failed to send outgoing request `{method}`"))
6225            };
6226            if let Some(pending_reply) = self.pending_replies.remove(&id) {
6227                pending_reply.fail(error.clone());
6228            }
6229            return Err(error);
6230        }
6231        // An escaped cancellation handle must not enqueue cancellation before
6232        // the request. A fast response or EOF may already have disarmed it.
6233        cancellation_disarm.arm();
6234        Ok(())
6235    }
6236}
6237
6238impl<T: Debug> Debug for PreparedRequest<T> {
6239    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6240        formatter
6241            .debug_struct("PreparedRequest")
6242            .field("request", &self.sent)
6243            .finish_non_exhaustive()
6244    }
6245}
6246
6247impl<T> PreparedRequest<T> {
6248    /// The ID reserved for this request, which has not been sent yet.
6249    #[must_use]
6250    pub fn id(&self) -> &RequestId {
6251        self.sent.id()
6252    }
6253
6254    /// The method of the prepared request.
6255    #[must_use]
6256    pub fn method(&self) -> &str {
6257        self.sent.method()
6258    }
6259
6260    /// Retain explicit cancellation control without publishing this request.
6261    ///
6262    /// The handle can outlive consumption of this request by an ordered callback
6263    /// or response future. Calling it before publication is a no-op, not a
6264    /// cancellation to apply when the request is later published. Dropping the
6265    /// handle does not cancel. See [`RequestCancellationHandle`].
6266    #[must_use]
6267    pub fn cancellation_handle(&self) -> RequestCancellationHandle {
6268        self.sent.cancellation_handle()
6269    }
6270
6271    /// Map a successful response without publishing the request.
6272    ///
6273    /// The mapper has the same contract as [`SentRequest::map`].
6274    pub fn map<U>(
6275        self,
6276        map_fn: impl FnOnce(T) -> Result<U, crate::Error> + 'static + Send,
6277    ) -> PreparedRequest<U>
6278    where
6279        T: 'static,
6280    {
6281        PreparedRequest {
6282            sent: self.sent.map(map_fn),
6283            publication: self.publication,
6284        }
6285    }
6286
6287    /// Register a cancellation source without publishing the request.
6288    ///
6289    /// After publication, cancellation is forwarded while awaiting the response,
6290    /// as described by [`SentRequest::forward_cancellation_from`].
6291    pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
6292        self.sent = self.sent.forward_cancellation_from(source);
6293        self
6294    }
6295
6296    /// Publish now and return an unordered future for the response.
6297    ///
6298    /// The request is enqueued during this call, not when the future is first
6299    /// polled. Dropping that future asks the peer to cancel a still-outstanding
6300    /// request. Await it only outside the dispatch loop; awaiting it in an
6301    /// incoming handler deadlocks just like [`SentRequest::block_task`].
6302    ///
6303    /// # Errors
6304    ///
6305    /// The returned future delivers preparation, publication, and response errors.
6306    pub fn block_task(self) -> impl Future<Output = Result<T, crate::Error>> {
6307        self.into_sent_request(false).block_task()
6308    }
6309
6310    /// Publish now and discard the eventual response without cancelling.
6311    ///
6312    /// # Errors
6313    ///
6314    /// Returns immediate preparation or enqueue failures. Later local
6315    /// transformation errors and peer response errors are discarded along with
6316    /// successful responses. Transport failures still propagate through the
6317    /// connection future. Retained [`RequestCancellationHandle`] values can
6318    /// still explicitly cancel the request while it remains pending.
6319    pub fn detach(self) -> Result<(), crate::Error> {
6320        let result = self.publication.publish();
6321        self.sent.detach();
6322        result
6323    }
6324
6325    /// Register an ordered callback, then publish the request.
6326    ///
6327    /// Ordering is selected before publication, even when the connection runs
6328    /// concurrently. See [`PreparedRequest`] for barrier limits and deadlock risks.
6329    ///
6330    /// # Errors
6331    ///
6332    /// Returns an error if the callback task cannot be registered, without
6333    /// publishing the request. Preparation and publication errors are delivered
6334    /// to the callback. Returning an error from the callback ends the connection.
6335    #[track_caller]
6336    pub fn on_receiving_result<F>(
6337        self,
6338        task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
6339    ) -> Result<(), crate::Error>
6340    where
6341        T: 'static,
6342        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6343    {
6344        self.consume_with(move |response| match response {
6345            Ok(result) => Either::Left(task(result)),
6346            Err(error) => Either::Right(future::ready(Err(error))),
6347        })
6348    }
6349
6350    /// Register an ordered success callback, then publish the request.
6351    ///
6352    /// Errors are forwarded to `responder`, as with
6353    /// [`SentRequest::on_receiving_ok_result`].
6354    ///
6355    /// # Errors
6356    ///
6357    /// Returns a task-registration error without publishing the request.
6358    /// Preparation, publication, and response errors are forwarded to `responder`.
6359    /// Returning an error from the callback ends the connection.
6360    #[track_caller]
6361    pub fn on_receiving_ok_result<F>(
6362        self,
6363        responder: Responder<T>,
6364        task: impl FnOnce(T, Responder<T>) -> F + 'static + Send,
6365    ) -> Result<(), crate::Error>
6366    where
6367        T: JsonRpcResponse,
6368        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6369    {
6370        self.on_receiving_result(async move |result| match result {
6371            Ok(value) => task(value, responder).await,
6372            Err(error) => responder.respond_with_error(error),
6373        })
6374    }
6375
6376    /// Register ordered response forwarding, then publish the request.
6377    ///
6378    /// Cancellation and response errors propagate as with
6379    /// [`SentRequest::forward_response_to`].
6380    ///
6381    /// # Errors
6382    ///
6383    /// Returns a task-registration error without publishing the request.
6384    /// Preparation, publication, and response errors are forwarded to `responder`.
6385    #[track_caller]
6386    pub fn forward_response_to(self, responder: Responder<T>) -> Result<(), crate::Error>
6387    where
6388        T: JsonRpcResponse,
6389    {
6390        self.forward_cancellation_from(responder.cancellation())
6391            .consume_with(async move |response| {
6392                responder.respond_with_result(response.unwrap_or_else(Err))
6393            })
6394    }
6395
6396    fn into_sent_request(self, ordered: bool) -> SentRequest<T> {
6397        if ordered {
6398            self.sent.response_ordering.mark_ordered();
6399        }
6400        // Publication errors also settle the response channel.
6401        drop(self.publication.publish());
6402        self.sent
6403    }
6404
6405    #[track_caller]
6406    fn consume_with<F>(
6407        self,
6408        handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6409    ) -> Result<(), crate::Error>
6410    where
6411        T: 'static,
6412        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6413    {
6414        let published_tx = self.register_consumer(handle)?;
6415        drop(self.publication.publish());
6416        // Keep the cancellation guard here until publication completes. Even
6417        // destruction of the registered task cannot cancel before enqueueing.
6418        drop(published_tx.send(self.sent));
6419        Ok(())
6420    }
6421
6422    #[track_caller]
6423    fn register_consumer<F>(
6424        &self,
6425        handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6426    ) -> Result<oneshot::Sender<SentRequest<T>>, crate::Error>
6427    where
6428        T: 'static,
6429        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6430    {
6431        self.sent.response_ordering.mark_ordered();
6432        let (published_tx, published_rx) = oneshot::channel::<SentRequest<T>>();
6433        Task::new(Location::caller(), async move {
6434            match published_rx.await {
6435                Ok(sent) => sent.handle_response(handle).await,
6436                // Publication was abandoned before the consumer took ownership.
6437                Err(_) => Ok(()),
6438            }
6439        })
6440        .spawn(&self.sent.task_tx)?;
6441        Ok(published_tx)
6442    }
6443}
6444
6445#[derive(Clone, Debug)]
6446pub(crate) struct SentRequestCancellationDisarm {
6447    state: Arc<AtomicU8>,
6448}
6449
6450#[repr(u8)]
6451enum OutgoingCancellationState {
6452    Unpublished,
6453    Armed,
6454    Disarmed,
6455}
6456
6457impl SentRequestCancellationDisarm {
6458    fn new() -> Self {
6459        Self {
6460            state: Arc::new(AtomicU8::new(OutgoingCancellationState::Unpublished as u8)),
6461        }
6462    }
6463
6464    fn disarm(&self) {
6465        self.state
6466            .store(OutgoingCancellationState::Disarmed as u8, Ordering::Release);
6467    }
6468
6469    fn arm(&self) -> bool {
6470        self.state
6471            .compare_exchange(
6472                OutgoingCancellationState::Unpublished as u8,
6473                OutgoingCancellationState::Armed as u8,
6474                Ordering::AcqRel,
6475                Ordering::Acquire,
6476            )
6477            .is_ok()
6478    }
6479
6480    fn take_armed(&self) -> bool {
6481        self.state
6482            .compare_exchange(
6483                OutgoingCancellationState::Armed as u8,
6484                OutgoingCancellationState::Disarmed as u8,
6485                Ordering::AcqRel,
6486                Ordering::Acquire,
6487            )
6488            .is_ok()
6489    }
6490
6491    fn is_armed(&self) -> bool {
6492        self.state.load(Ordering::Acquire) == OutgoingCancellationState::Armed as u8
6493    }
6494}
6495
6496/// Explicit cancellation control for one outgoing request.
6497///
6498/// Obtain this handle from [`PreparedRequest::cancellation_handle`] or
6499/// [`SentRequest::cancellation_handle`] before consuming the request. It retains
6500/// neither the response consumer nor application callback, so cancellation can
6501/// be requested without discarding the eventual response. It does not keep the
6502/// connection driver or response consumer alive, or abort local callback work.
6503///
6504/// Clones share the request's cancellation state with [`SentRequest::cancel`],
6505/// forwarded cancellation, and request-drop automatic cancellation. At most one
6506/// cancellation notification is attempted, with the original peer and proxy
6507/// wrapping. Dropping this handle neither cancels the request nor disables its
6508/// automatic cancellation.
6509///
6510/// Cancellation before publication is a no-op and is not remembered for later
6511/// publication. A call racing publication may also be a no-op; call after the
6512/// publishing method returns to target a pending request. Once the SDK routes a
6513/// response or fails the request, subsequent cancellation calls are no-ops,
6514/// even if its callback has not yet run. Detaching the request suppresses only
6515/// automatic cancellation; retained handles can still explicitly cancel it.
6516///
6517/// This controls outgoing requests, unlike [`RequestCancellation`], which
6518/// observes a peer's cancellation of an incoming request.
6519#[derive(Clone)]
6520pub struct RequestCancellationHandle {
6521    message_tx: OutgoingMessageTx,
6522    remote_style: crate::role::RemoteStyle,
6523    request_id: RequestId,
6524    disarm: SentRequestCancellationDisarm,
6525}
6526
6527impl RequestCancellationHandle {
6528    /// Ask the peer to cancel this request without discarding its response.
6529    ///
6530    /// Cancellation is cooperative: the peer may respond normally or with a
6531    /// cancellation error. Repeated calls, including calls through other clones
6532    /// or the original request, return `Ok(())` without sending another
6533    /// notification. Calls before publication or after settlement are no-ops.
6534    /// An attempt begun before settlement may still enqueue afterward.
6535    ///
6536    /// `Ok(())` means this call encountered no immediate error, not that a
6537    /// notification was sent or the peer stopped work. This method does not
6538    /// wait for transmission or acknowledgment.
6539    ///
6540    /// # Errors
6541    ///
6542    /// Only the call that attempts to send reports serialization or enqueue
6543    /// failure. A failed send is not retried by later cancellation calls.
6544    pub fn cancel(&self) -> Result<(), crate::Error> {
6545        if !self.disarm.take_armed() {
6546            return Ok(());
6547        }
6548
6549        // Build the notification lazily: most requests are never cancelled,
6550        // so this avoids serializing a notification per outgoing request.
6551        let untyped = self.remote_style.transform_outgoing_message(
6552            crate::schema::v1::CancelRequestNotification::new(self.request_id.clone()),
6553        )?;
6554
6555        send_raw_message(&self.message_tx, OutgoingMessage::Notification { untyped })
6556    }
6557}
6558
6559impl Debug for RequestCancellationHandle {
6560    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6561        formatter
6562            .debug_struct("RequestCancellationHandle")
6563            .field("request_id", &self.request_id)
6564            .field("remote_style", &self.remote_style)
6565            .field("armed", &self.disarm.is_armed())
6566            .finish_non_exhaustive()
6567    }
6568}
6569
6570#[derive(Debug)]
6571struct SentRequestCancellation {
6572    handle: RequestCancellationHandle,
6573    cancel_on_drop: bool,
6574}
6575
6576impl SentRequestCancellation {
6577    fn new(
6578        message_tx: OutgoingMessageTx,
6579        remote_style: crate::role::RemoteStyle,
6580        request_id: RequestId,
6581    ) -> Self {
6582        Self {
6583            handle: RequestCancellationHandle {
6584                message_tx,
6585                remote_style,
6586                request_id,
6587                disarm: SentRequestCancellationDisarm::new(),
6588            },
6589            cancel_on_drop: true,
6590        }
6591    }
6592
6593    fn disarm(&self) {
6594        self.handle.disarm.disarm();
6595    }
6596
6597    fn disarm_handle(&self) -> SentRequestCancellationDisarm {
6598        self.handle.disarm.clone()
6599    }
6600
6601    fn send(&self) -> Result<(), crate::Error> {
6602        self.handle.cancel()
6603    }
6604}
6605
6606impl Drop for SentRequestCancellation {
6607    fn drop(&mut self) {
6608        if !self.cancel_on_drop {
6609            return;
6610        }
6611        if let Err(error) = self.send() {
6612            tracing::debug!(?error, "failed to auto-cancel dropped request");
6613        }
6614    }
6615}
6616
6617/// Await the response payload for an outgoing request, watching `sources` for
6618/// cancellation of the upstream requests it was registered with.
6619///
6620/// When any source reports cancellation, a `$/cancel_request` is forwarded to
6621/// the outgoing request (at most once, shared with [`SentRequest::cancel`] and
6622/// drop-time auto-cancellation), and the response is *still* awaited: the peer
6623/// always answers, with normal data or a cancellation error.
6624///
6625/// Watching is deliberately bounded by response arrival so that completed
6626/// requests do not leak waiters on markers that will never fire.
6627async fn await_response_forwarding_cancellation(
6628    response_rx: oneshot::Receiver<ResponsePayload>,
6629    cancellation: &SentRequestCancellation,
6630    sources: &[RequestCancellation],
6631) -> Result<ResponsePayload, oneshot::Canceled> {
6632    // Failing to forward the cancellation must not abort the wait: the
6633    // response (normal data or a cancellation error) may still arrive and
6634    // must still be processed.
6635    let forward_cancellation = || {
6636        if let Err(error) = cancellation.send() {
6637            tracing::debug!(
6638                ?error,
6639                "failed to forward cancellation to downstream request"
6640            );
6641        }
6642    };
6643
6644    let response = if sources.is_empty() {
6645        response_rx.await
6646    } else if sources.iter().any(RequestCancellation::is_cancelled) {
6647        forward_cancellation();
6648        response_rx.await
6649    } else {
6650        let cancelled = sources.iter().map(|source| source.state.signal_rx.clone());
6651        match future::select(future::select_all(cancelled), response_rx).await {
6652            Either::Left((_, response_rx)) => {
6653                forward_cancellation();
6654                response_rx.await
6655            }
6656            Either::Right((response, _)) => response,
6657        }
6658    };
6659
6660    cancellation.disarm();
6661    response
6662}
6663
6664impl<T: Debug> Debug for SentRequest<T> {
6665    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
6666        let mut debug = f.debug_struct("SentRequest");
6667        debug
6668            .field("id", &self.id)
6669            .field("method", &self.method)
6670            .field("task_tx", &self.task_tx)
6671            .field("response_rx", &self.response_rx);
6672        debug
6673            .field("cancellation", &self.cancellation)
6674            .field("cancellation_sources", &self.cancellation_sources);
6675        debug.finish_non_exhaustive()
6676    }
6677}
6678
6679impl SentRequest<serde_json::Value> {
6680    fn new(
6681        id: RequestId,
6682        method: String,
6683        task_tx: mpsc::UnboundedSender<Task>,
6684        response_rx: oneshot::Receiver<ResponsePayload>,
6685        cancellation: SentRequestCancellation,
6686        response_ordering: ResponseOrdering,
6687    ) -> Self {
6688        Self {
6689            id,
6690            method,
6691            response_rx,
6692            task_tx,
6693            to_result: Box::new(Ok),
6694            cancellation,
6695            response_ordering,
6696            cancellation_sources: Vec::new(),
6697        }
6698    }
6699}
6700
6701impl<T> SentRequest<T> {
6702    /// Detach this request handle without waiting for its response.
6703    ///
6704    /// The response will be discarded when it arrives. This also disables the
6705    /// drop-time automatic cancellation described in
6706    /// [Drop Behavior](Self#drop-behavior), so use it for requests whose
6707    /// eventual response should be ignored, but which should keep running on
6708    /// the peer. The peer is still expected to answer the JSON-RPC request
6709    /// eventually; use a notification instead when no response is expected at
6710    /// all.
6711    ///
6712    /// To ask the peer to stop the request, call `cancel` instead, or drop the
6713    /// handle while automatic cancellation is enabled. A retained
6714    /// [`RequestCancellationHandle`] can still explicitly cancel the detached
6715    /// request until the SDK receives its response or fails it.
6716    pub fn detach(mut self) {
6717        self.cancellation.cancel_on_drop = false;
6718    }
6719
6720    /// Send a `$/cancel_request` notification for this outgoing request.
6721    ///
6722    /// This uses the same peer and message wrapping that were used to send the
6723    /// original request, so it is the preferred way to cancel a [`SentRequest`]
6724    /// when the request handle is still available.
6725    ///
6726    /// At most one cancellation attempt is made per request, shared with
6727    /// retained handles, forwarded cancellation, and automatic cancellation
6728    /// described in [Drop Behavior](Self#drop-behavior). Later calls return
6729    /// `Ok(())` without another attempt, including when the first attempt failed.
6730    /// Once the SDK has routed the response, a new call is a no-op; an attempt
6731    /// begun before settlement may still enqueue afterward.
6732    ///
6733    /// `Ok(())` means this call encountered no immediate error, not that a
6734    /// notification was sent or the peer stopped work.
6735    ///
6736    /// Errors are only reported by the call that attempts to send the
6737    /// notification.
6738    pub fn cancel(&self) -> Result<(), crate::Error> {
6739        self.cancellation.send()
6740    }
6741
6742    /// Obtain a handle that remains usable after this request is consumed.
6743    ///
6744    /// The handle shares the once-only cancellation state used by
6745    /// [`cancel`](Self::cancel), response routing, and automatic request-drop
6746    /// cancellation, but has no cancel-on-drop behavior of its own.
6747    #[must_use]
6748    pub fn cancellation_handle(&self) -> RequestCancellationHandle {
6749        self.cancellation.handle.clone()
6750    }
6751
6752    /// Forward cancellation of another request to this one.
6753    ///
6754    /// When the request that `source` belongs to is cancelled by its peer,
6755    /// a `$/cancel_request` for *this* request is sent to its peer, using the
6756    /// same wrapping as the original request. The response is still awaited
6757    /// and delivered as usual (normal data or a cancellation error), so this
6758    /// composes with [`block_task`](Self::block_task) and
6759    /// [`on_receiving_result`](Self::on_receiving_result).
6760    ///
6761    /// This is the building block for proxies that forward a request with
6762    /// custom logic instead of [`forward_response_to`](Self::forward_response_to)
6763    /// (which wires this up automatically from its responder). Without it,
6764    /// custom forwarding *absorbs* cancellation: the upstream marker is still
6765    /// set, but nothing is sent downstream.
6766    ///
6767    /// ```
6768    /// # use agent_client_protocol::{ConnectionTo, Error, Responder, UntypedRole};
6769    /// # use agent_client_protocol_test::{MyRequest, MyResponse};
6770    /// # async fn example(request: MyRequest, responder: Responder<MyResponse>, backend: ConnectionTo<UntypedRole>) -> Result<(), Error> {
6771    /// backend
6772    ///     .send_request(request)
6773    ///     .forward_cancellation_from(responder.cancellation())
6774    ///     .on_receiving_result(async move |result| {
6775    ///         // Custom result handling, e.g. bookkeeping or rewriting.
6776    ///         responder.respond_with_result(result)
6777    ///     })?;
6778    /// # Ok(())
6779    /// # }
6780    /// ```
6781    ///
6782    /// May be called multiple times; cancellation of any registered source
6783    /// triggers the forwarding (at most one `$/cancel_request` is ever sent
6784    /// per request). Sources are observed while the response is being
6785    /// awaited — that is, once the handle is consumed with
6786    /// [`block_task`](Self::block_task),
6787    /// [`on_receiving_result`](Self::on_receiving_result), or
6788    /// [`forward_response_to`](Self::forward_response_to); a source that was
6789    /// already cancelled by then is honored immediately.
6790    pub fn forward_cancellation_from(mut self, source: RequestCancellation) -> Self {
6791        self.cancellation_sources.push(source);
6792        self
6793    }
6794}
6795
6796impl<T> SentRequest<T> {
6797    /// The id of the outgoing request.
6798    #[must_use]
6799    pub fn id(&self) -> &RequestId {
6800        &self.id
6801    }
6802
6803    /// The method of the request this is in response to.
6804    #[must_use]
6805    pub fn method(&self) -> &str {
6806        &self.method
6807    }
6808
6809    /// Map a successful JSON-RPC response into an application type.
6810    ///
6811    /// The mapped type does not need to implement [`JsonRpcResponse`]. The
6812    /// mapper runs at most once and may consume captured state. JSON-RPC error
6813    /// responses bypass the mapper. The mapped type may carry a non-`'static`
6814    /// lifetime when it is consumed with [`block_task`](Self::block_task);
6815    /// callback-style consumption still requires a `'static` mapped type
6816    /// because its work is spawned onto the connection.
6817    pub fn map<U>(
6818        self,
6819        map_fn: impl FnOnce(T) -> Result<U, crate::Error> + 'static + Send,
6820    ) -> SentRequest<U>
6821    where
6822        T: 'static,
6823    {
6824        SentRequest {
6825            id: self.id,
6826            method: self.method,
6827            response_rx: self.response_rx,
6828            task_tx: self.task_tx,
6829            to_result: Box::new(move |value| map_fn((self.to_result)(value)?)),
6830            cancellation: self.cancellation,
6831            response_ordering: self.response_ordering,
6832            cancellation_sources: self.cancellation_sources,
6833        }
6834    }
6835
6836    /// Forward the response (success or error) to a request context when it arrives.
6837    ///
6838    /// This is a convenience method for proxying messages between connections. When the
6839    /// response arrives, it will be automatically sent to the provided request context,
6840    /// whether it's a successful response or an error.
6841    ///
6842    /// # Example: Proxying requests
6843    ///
6844    /// ```
6845    /// # use agent_client_protocol::UntypedRole;
6846    /// # use agent_client_protocol::{Builder, ConnectionTo};
6847    /// # use agent_client_protocol_test::*;
6848    /// # async fn example(cx: ConnectionTo<UntypedRole>) -> Result<(), agent_client_protocol::Error> {
6849    /// // Set up backend connection builder
6850    /// let backend = UntypedRole.builder()
6851    ///     .on_receive_request(async |req: MyRequest, responder, cx| {
6852    ///         responder.respond(MyResponse { status: "ok".into() })
6853    ///     }, agent_client_protocol::on_receive_request!());
6854    ///
6855    /// // Spawn backend and get a context to send to it
6856    /// let backend_connection = cx.spawn_connection(backend, MockTransport)?;
6857    ///
6858    /// // Set up proxy that forwards requests to backend
6859    /// UntypedRole.builder()
6860    ///     .on_receive_request({
6861    ///         let backend_connection = backend_connection.clone();
6862    ///         async move |req: MyRequest, responder, cx| {
6863    ///             // Forward the request to backend and proxy the response back
6864    ///             backend_connection.send_request(req)
6865    ///                 .forward_response_to(responder)?;
6866    ///             Ok(())
6867    ///         }
6868    ///     }, agent_client_protocol::on_receive_request!());
6869    /// # Ok(())
6870    /// # }
6871    /// ```
6872    ///
6873    /// # Type Safety
6874    ///
6875    /// The request context's response type must match the request's response type,
6876    /// ensuring type-safe message forwarding.
6877    ///
6878    /// # When to Use
6879    ///
6880    /// Use this when:
6881    /// - You're implementing a proxy or gateway pattern
6882    /// - You want to forward responses without processing them
6883    /// - The response types match between the outgoing request and incoming request
6884    ///
6885    /// This is equivalent to calling `on_receiving_result` and manually forwarding
6886    /// the result, with two proxy-specific additions:
6887    ///
6888    /// - If the pending response cannot be delivered, the incoming request is
6889    ///   answered with an internal error instead of being left unanswered.
6890    ///   Known clean incoming EOF is delivered like any other response
6891    ///   error; an unexpected response-channel loss is forwarded as an outer
6892    ///   consumption error.
6893    /// - When the peer cancels the incoming request, the cancellation is
6894    ///   forwarded to the outgoing request, and the downstream response
6895    ///   (normal data or a cancellation error) is still forwarded back. This is
6896    ///   equivalent to registering the responder's marker with
6897    ///   `forward_cancellation_from`.
6898    #[track_caller]
6899    pub fn forward_response_to(self, responder: Responder<T>) -> Result<(), crate::Error>
6900    where
6901        T: JsonRpcResponse,
6902    {
6903        let this = self.forward_cancellation_from(responder.cancellation());
6904
6905        this.consume_with(async move |response| {
6906            // An unexpected response-channel loss (outer `Err`) is forwarded
6907            // as an error: the incoming request must not be left unanswered.
6908            responder.respond_with_result(response.unwrap_or_else(Err))
6909        })
6910    }
6911
6912    /// Spawn the response-consumption task shared by
6913    /// [`on_receiving_result`](Self::on_receiving_result) and
6914    /// [`forward_response_to`](Self::forward_response_to).
6915    ///
6916    /// The task awaits the response (forwarding cancellation from registered
6917    /// sources while waiting, converts the payload, and invokes `handle` with
6918    /// the typed result (`Ok(Result<T, _>)`). The dispatch loop's ack, if any,
6919    /// is sent after `handle` completes.
6920    ///
6921    /// Clean incoming EOF is delivered as `Ok(Err(error))`, just like
6922    /// a peer response error, so callback-style consumers still run. If the
6923    /// response channel disappears for another reason, `handle` receives an
6924    /// outer `Err` describing that unexpected loss; there is no ack then.
6925    #[track_caller]
6926    fn consume_with<F>(
6927        self,
6928        handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6929    ) -> Result<(), crate::Error>
6930    where
6931        T: 'static,
6932        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6933    {
6934        self.response_ordering.mark_ordered();
6935        let task_tx = self.task_tx.clone();
6936        Task::new(Location::caller(), self.handle_response(handle)).spawn(&task_tx)
6937    }
6938
6939    fn handle_response<F>(
6940        self,
6941        handle: impl FnOnce(Result<Result<T, crate::Error>, crate::Error>) -> F + 'static + Send,
6942    ) -> impl Future<Output = Result<(), crate::Error>> + Send
6943    where
6944        T: 'static,
6945        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
6946    {
6947        let method = self.method;
6948        let response_rx = self.response_rx;
6949        let to_result = self.to_result;
6950        let cancellation = self.cancellation;
6951        let cancellation_sources = self.cancellation_sources;
6952        async move {
6953            let response = await_response_forwarding_cancellation(
6954                response_rx,
6955                &cancellation,
6956                &cancellation_sources,
6957            )
6958            .await;
6959
6960            match response {
6961                Ok(ResponsePayload { result, ack_tx }) => {
6962                    // Convert the result using to_result for Ok values
6963                    let typed_result = match result {
6964                        Ok(json_value) => to_result(json_value),
6965                        Err(err) => Err(err),
6966                    };
6967
6968                    let outcome = handle(Ok(typed_result)).await;
6969
6970                    // Ack AFTER the handler completes - this is the key
6971                    // difference from block_task. The dispatch loop waits for
6972                    // this ack.
6973                    if let Some(tx) = ack_tx {
6974                        let _ = tx.send(());
6975                    }
6976
6977                    outcome
6978                }
6979                Err(err) => {
6980                    handle(Err(crate::util::internal_error(format!(
6981                        "response to `{method}` never received: {err}"
6982                    ))))
6983                    .await
6984                }
6985            }
6986        }
6987    }
6988
6989    /// Block the current task until the response is received.
6990    ///
6991    /// **Warning:** This method blocks the current async task. It is safe only when that task
6992    /// already runs outside the dispatch loop, such as the foreground future passed to
6993    /// `connect_with` or a task created with [`ConnectionTo::spawn`]. Using it directly in a
6994    /// handler callback will deadlock the connection.
6995    ///
6996    /// # Safe Usage (outside the dispatch loop)
6997    ///
6998    /// ```no_run
6999    /// # use agent_client_protocol_test::*;
7000    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7001    /// # let connection = mock_connection();
7002    /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
7003    ///     // Spawn a task to handle the request
7004    ///     cx.spawn({
7005    ///         let connection = cx.clone();
7006    ///         async move {
7007    ///             // Safe: We're in a spawned task, not blocking the event loop
7008    ///             let response = connection.send_request(OtherRequest {})
7009    ///                 .block_task()
7010    ///                 .await?;
7011    ///
7012    ///             // Process the response...
7013    ///             Ok(())
7014    ///         }
7015    ///     })?;
7016    ///
7017    ///     // Respond immediately
7018    ///     responder.respond(MyResponse { status: "ok".into() })
7019    /// }, agent_client_protocol::on_receive_request!())
7020    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7021    /// # Ok(())
7022    /// # }
7023    /// ```
7024    ///
7025    /// # Unsafe Usage (in handlers - will deadlock!)
7026    ///
7027    /// ```no_run
7028    /// # use agent_client_protocol_test::*;
7029    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7030    /// # let connection = mock_connection();
7031    /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
7032    ///     // ❌ DEADLOCK: Handler blocks event loop, which can't process the response
7033    ///     let response = cx.send_request(OtherRequest {})
7034    ///         .block_task()
7035    ///         .await?;
7036    ///
7037    ///     responder.respond(MyResponse { status: response.value })
7038    /// }, agent_client_protocol::on_receive_request!())
7039    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7040    /// # Ok(())
7041    /// # }
7042    /// ```
7043    ///
7044    /// # When to Use
7045    ///
7046    /// Use this method when:
7047    /// - Your current task already runs outside the dispatch loop
7048    /// - You need the response value to proceed with your logic
7049    /// - Linear control flow is more natural than callbacks
7050    ///
7051    /// For handler callbacks, use [`on_receiving_result`](Self::on_receiving_result) instead.
7052    pub async fn block_task(self) -> Result<T, crate::Error> {
7053        let response = await_response_forwarding_cancellation(
7054            self.response_rx,
7055            &self.cancellation,
7056            &self.cancellation_sources,
7057        )
7058        .await;
7059
7060        match response {
7061            Ok(ResponsePayload {
7062                result: Ok(json_value),
7063                ack_tx,
7064            }) => {
7065                // Blocking consumers ack before converting or returning the
7066                // value, so dispatch can continue while the caller processes it.
7067                if let Some(tx) = ack_tx {
7068                    let _ = tx.send(());
7069                }
7070                match (self.to_result)(json_value) {
7071                    Ok(value) => Ok(value),
7072                    Err(err) => Err(err),
7073                }
7074            }
7075            Ok(ResponsePayload {
7076                result: Err(err),
7077                ack_tx,
7078            }) => {
7079                if let Some(tx) = ack_tx {
7080                    let _ = tx.send(());
7081                }
7082                Err(err)
7083            }
7084            Err(err) => Err(crate::util::internal_error(format!(
7085                "response to `{}` never received: {}",
7086                self.method, err
7087            ))),
7088        }
7089    }
7090
7091    /// Block the current task and transform the typed result before releasing
7092    /// the ordered-response barrier.
7093    ///
7094    /// Framework lifecycle code uses this when success transfers local state
7095    /// to the returned value while an error must drop that state before later
7096    /// messages from the same transport frame are dispatched. The synchronous
7097    /// transform must not wait for additional connection traffic.
7098    pub(crate) async fn block_task_with_ordered_result<U>(
7099        self,
7100        transform: impl FnOnce(Result<T, crate::Error>) -> Result<U, crate::Error>,
7101    ) -> Result<U, crate::Error> {
7102        let response = await_response_forwarding_cancellation(
7103            self.response_rx,
7104            &self.cancellation,
7105            &self.cancellation_sources,
7106        )
7107        .await;
7108
7109        let (result, ack_tx) = match response {
7110            Ok(ResponsePayload { result, ack_tx }) => {
7111                let typed_result = match result {
7112                    Ok(json_value) => (self.to_result)(json_value),
7113                    Err(error) => Err(error),
7114                };
7115                (typed_result, ack_tx)
7116            }
7117            Err(error) => (
7118                Err(crate::util::internal_error(format!(
7119                    "response to `{}` never received: {error}",
7120                    self.method
7121                ))),
7122                None,
7123            ),
7124        };
7125
7126        let outcome = transform(result);
7127        if let Some(acknowledgment) = ack_tx {
7128            let _ = acknowledgment.send(());
7129        }
7130        outcome
7131    }
7132
7133    /// Schedule an async task to run when a successful response is received.
7134    ///
7135    /// This is a convenience wrapper around [`on_receiving_result`](Self::on_receiving_result)
7136    /// for the common pattern of forwarding errors to a request context while only processing
7137    /// successful responses.
7138    ///
7139    /// # Behavior
7140    ///
7141    /// - If the response is `Ok(value)`, your task receives the value and the request context
7142    /// - If the response is `Err(error)`, the error is automatically sent to `responder`
7143    ///   and your task is not called
7144    ///
7145    /// # Example: Chaining requests
7146    ///
7147    /// ```no_run
7148    /// # use agent_client_protocol_test::*;
7149    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7150    /// # let connection = mock_connection();
7151    /// connection.on_receive_request(async |req: ValidateRequest, responder, cx| {
7152    ///     // Send initial request
7153    ///     cx.send_request(ValidateRequest { data: req.data.clone() })
7154    ///         .on_receiving_ok_result(responder, async |validation, responder| {
7155    ///             // Only runs if validation succeeded
7156    ///             if validation.is_valid {
7157    ///                 // Respond to original request
7158    ///                 responder.respond(ValidateResponse { is_valid: true, error: None })
7159    ///             } else {
7160    ///                 responder.respond_with_error(agent_client_protocol::util::internal_error("validation failed"))
7161    ///             }
7162    ///         })?;
7163    ///
7164    ///     Ok(())
7165    /// }, agent_client_protocol::on_receive_request!())
7166    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7167    /// # Ok(())
7168    /// # }
7169    /// ```
7170    ///
7171    /// # Ordering
7172    ///
7173    /// Like [`on_receiving_result`](Self::on_receiving_result), response handling holds the
7174    /// dispatch loop through callback completion when ordered consumption is selected before a
7175    /// peer response is routed during its original dispatch. Pending-request failures delivered
7176    /// without an incoming response and delayed routes do not carry that barrier. The callback
7177    /// must not await later inbound traffic on the same connection. See the
7178    /// [`ordering`](crate::concepts::ordering) module for details.
7179    ///
7180    /// # When to Use
7181    ///
7182    /// Use this when:
7183    /// - You need to respond to a request based on another request's result
7184    /// - You want errors to automatically propagate to the request context
7185    /// - You only care about the success case
7186    ///
7187    /// For more control over error handling, use [`on_receiving_result`](Self::on_receiving_result).
7188    #[track_caller]
7189    pub fn on_receiving_ok_result<F>(
7190        self,
7191        responder: Responder<T>,
7192        task: impl FnOnce(T, Responder<T>) -> F + 'static + Send,
7193    ) -> Result<(), crate::Error>
7194    where
7195        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
7196        T: JsonRpcResponse,
7197    {
7198        self.on_receiving_result(async move |result| match result {
7199            Ok(value) => task(value, responder).await,
7200            Err(err) => responder.respond_with_error(err),
7201        })
7202    }
7203
7204    /// Register an async callback to run when the response is received.
7205    ///
7206    /// This is the recommended way to select response handling from inside a handler because
7207    /// registration returns immediately. The response-consumption task waits concurrently for
7208    /// the response; once the response is dispatched, the ordered callback may hold the dispatch
7209    /// loop until it completes.
7210    ///
7211    /// # Example: Handle response in callback
7212    ///
7213    /// ```no_run
7214    /// # use agent_client_protocol_test::*;
7215    /// # async fn example() -> Result<(), agent_client_protocol::Error> {
7216    /// # let connection = mock_connection();
7217    /// connection.on_receive_request(async |req: MyRequest, responder, cx| {
7218    ///     // Send a request and schedule a callback for the response
7219    ///     cx.send_request(QueryRequest { id: 22 })
7220    ///         .on_receiving_result({
7221    ///             let connection = cx.clone();
7222    ///             async move |result| {
7223    ///                 match result {
7224    ///                     Ok(response) => {
7225    ///                         println!("Got response: {:?}", response);
7226    ///                         // Can send more messages here
7227    ///                         connection.send_notification(QueryComplete {})?;
7228    ///                         Ok(())
7229    ///                 }
7230    ///                     Err(error) => {
7231    ///                         eprintln!("Request failed: {}", error);
7232    ///                         Err(error)
7233    ///                     }
7234    ///                 }
7235    ///             }
7236    ///         })?;
7237    ///
7238    ///     // Handler continues immediately after registering the callback
7239    ///     responder.respond(MyResponse { status: "processing".into() })
7240    /// }, agent_client_protocol::on_receive_request!())
7241    /// # .connect_to(agent_client_protocol_test::MockTransport).await?;
7242    /// # Ok(())
7243    /// # }
7244    /// ```
7245    ///
7246    /// # Ordering
7247    ///
7248    /// When ordered consumption is selected before a peer response is routed during its original
7249    /// dispatch, the callback runs in a connection-managed task and the dispatch loop waits for
7250    /// it to complete before processing the next message.
7251    ///
7252    /// The barrier does not apply when the pending request is failed without an incoming response,
7253    /// such as on EOF. If the response was already routed, or an interceptor routes a retained
7254    /// [`ResponseRouter`] after its original dispatch, the callback still runs but cannot
7255    /// retroactively block messages that were already released.
7256    ///
7257    /// While the barrier is held, the callback must not await a later response, notification, or
7258    /// other inbound traffic on the same connection: that traffic cannot be dispatched until the
7259    /// callback completes. Spawn follow-up work with [`ConnectionTo::spawn`] and return, or use
7260    /// [`block_task`](Self::block_task) from a task already outside the dispatch loop.
7261    ///
7262    /// This differs from [`block_task`](Self::block_task), which does not select ordered
7263    /// consumption: dispatch remains free while the caller processes the delivered response.
7264    ///
7265    /// See the [`ordering`](crate::concepts::ordering) module for details on ordering guarantees
7266    /// and how to avoid deadlocks.
7267    ///
7268    /// # Error Handling
7269    ///
7270    /// If the scheduled task returns `Err`, the entire server will shut down. Make sure to handle
7271    /// errors appropriately within your task.
7272    ///
7273    /// # When to Use
7274    ///
7275    /// Use this method when:
7276    /// - You need to register response handling from a handler callback
7277    /// - You want a peer response callback to complete before later messages are dispatched
7278    /// - The callback performs bounded work that does not depend on later inbound traffic
7279    ///
7280    /// When already outside the dispatch loop and you do not need ordering guarantees, consider
7281    /// [`block_task`](Self::block_task).
7282    #[track_caller]
7283    pub fn on_receiving_result<F>(
7284        self,
7285        task: impl FnOnce(Result<T, crate::Error>) -> F + 'static + Send,
7286    ) -> Result<(), crate::Error>
7287    where
7288        T: 'static,
7289        F: Future<Output = Result<(), crate::Error>> + 'static + Send,
7290    {
7291        self.consume_with(move |response| match response {
7292            // Invoke the callback before constructing its future so the
7293            // response value does not need to be `Send` across an await.
7294            Ok(result) => Either::Left(task(result)),
7295            // A response that was never delivered fails the consuming
7296            // task instead of invoking the callback.
7297            Err(err) => Either::Right(future::ready(Err(err))),
7298        })
7299    }
7300}
7301
7302// ============================================================================
7303// IntoJrConnectionTransport Implementations
7304// ============================================================================
7305
7306/// A component that communicates over line streams.
7307///
7308/// `Lines` implements the [`ConnectTo`] trait for any pair of line-based streams
7309/// (a `Stream<Item = io::Result<String>>` for incoming and a `Sink<String>` for outgoing),
7310/// handling serialization of JSON-RPC messages to/from newline-delimited JSON.
7311/// An incoming line may contain one JSON-RPC message or a non-empty batch array. Batch
7312/// entries are dispatched individually in source order, and responses to the batch are
7313/// collected into one response-array line. SDK-initiated requests and notifications remain
7314/// individual messages.
7315///
7316/// This is a lower-level primitive than [`ByteStreams`] that enables interception and
7317/// transformation of individual lines before they are parsed or after they are serialized.
7318/// This is particularly useful for debugging, logging, or implementing custom line-based
7319/// protocols.
7320///
7321/// # Use Cases
7322///
7323/// - **Line-by-line logging**: Intercept and log each line before parsing
7324/// - **Custom protocols**: Transform lines before/after JSON-RPC processing
7325/// - **Debugging**: Inspect raw message strings
7326/// - **Line filtering**: Skip or modify specific messages
7327///
7328/// Most users should use [`ByteStreams`] instead, which provides a simpler interface
7329/// for byte-based I/O.
7330///
7331/// [`ConnectTo`]: crate::ConnectTo
7332#[derive(Debug)]
7333pub struct Lines<OutgoingSink, IncomingStream> {
7334    outgoing: OutgoingSink,
7335    incoming: IncomingStream,
7336}
7337
7338impl<OutgoingSink, IncomingStream> Lines<OutgoingSink, IncomingStream>
7339where
7340    OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
7341    IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
7342{
7343    /// Create a new line stream transport.
7344    pub fn new(outgoing: OutgoingSink, incoming: IncomingStream) -> Self {
7345        Self { outgoing, incoming }
7346    }
7347
7348    fn into_channel_transport(self) -> (Channel, crate::ConnectionDriver) {
7349        let Self { outgoing, incoming } = self;
7350        let (channel_for_caller, channel_for_lines) = Channel::duplex();
7351        let Channel { mut rx, tx } = channel_for_lines;
7352        let (finish_tx, finish_rx) = oneshot::channel();
7353        let finish = async move {
7354            // Losing a finish handle is not a shutdown request.
7355            if finish_rx.await.is_err() {
7356                future::pending::<()>().await;
7357            }
7358        }
7359        .boxed()
7360        .shared();
7361        let outgoing_frames = futures::stream::poll_fn({
7362            let mut finish = finish.clone();
7363            let mut finishing = false;
7364            move |cx| {
7365                if !finishing && std::pin::Pin::new(&mut finish).poll(cx).is_ready() {
7366                    rx.close();
7367                    finishing = true;
7368                }
7369                rx.poll_next_unpin(cx)
7370            }
7371        });
7372        let discard_incoming = Arc::new(AtomicBool::new(false));
7373        let incoming = incoming.filter_map({
7374            let discard_incoming = discard_incoming.clone();
7375            move |item| {
7376                let discard = discard_incoming.load(Ordering::Acquire);
7377                future::ready((!discard || item.is_err()).then_some(item))
7378            }
7379        });
7380        let outgoing = transport_actor::transport_outgoing_lines_actor(outgoing_frames, outgoing)
7381            .boxed()
7382            .shared();
7383        let serve_self = Box::pin({
7384            let outgoing = outgoing.clone();
7385            async move {
7386                futures::try_join!(
7387                    outgoing,
7388                    transport_actor::transport_incoming_lines_actor(incoming, tx),
7389                )?;
7390                Ok(())
7391            }
7392        });
7393        let server_future = crate::ConnectionDriver::with_finish(
7394            async move {
7395                match future::select(finish, serve_self).await {
7396                    Either::Left(((), serve_self)) => {
7397                        discard_incoming.store(true, Ordering::Release);
7398                        // Keep reading while flushing, but do not require remote
7399                        // read EOF. Poll incoming errors before clean sink drain.
7400                        match future::select(serve_self, outgoing).await {
7401                            Either::Left((result, _)) | Either::Right((result, _)) => result,
7402                        }
7403                    }
7404                    Either::Right((result, _)) => result,
7405                }
7406            },
7407            move || {
7408                let _ = finish_tx.send(());
7409            },
7410        );
7411
7412        (channel_for_caller, server_future)
7413    }
7414}
7415
7416impl<OutgoingSink, IncomingStream, R: Role> ConnectTo<R> for Lines<OutgoingSink, IncomingStream>
7417where
7418    OutgoingSink: futures::Sink<String, Error = std::io::Error> + Send + 'static,
7419    IncomingStream: futures::Stream<Item = std::io::Result<String>> + Send + 'static,
7420{
7421    async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
7422        let (channel, mut serve_self) = self.into_channel_transport();
7423        let mut finish = serve_self
7424            .take_finish()
7425            .expect("built-in Lines transport supports explicit finishing");
7426        let client_future = Box::pin(ConnectTo::<R>::connect_to(channel, client));
7427
7428        match futures::future::select(client_future, serve_self).await {
7429            Either::Left((result, serve_self)) => {
7430                result?;
7431                // The local bridge has transferred all accepted client output.
7432                // Finish the physical sink without waiting for remote read EOF.
7433                finish.request();
7434                serve_self.await
7435            }
7436            Either::Right((result, _)) => result,
7437        }
7438    }
7439
7440    fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
7441        let (channel, driver) = self.into_channel_transport();
7442        (channel, Some(driver))
7443    }
7444}
7445
7446/// A component that communicates over byte streams (stdin/stdout, sockets, pipes, etc.).
7447///
7448/// `ByteStreams` implements the [`ConnectTo`] trait for any pair of `AsyncRead` and `AsyncWrite`
7449/// streams, handling serialization of JSON-RPC messages to/from newline-delimited JSON.
7450/// This is the standard way to communicate with external processes or network connections.
7451///
7452/// # Use Cases
7453///
7454/// - **Stdio communication**: Connect to agents or proxies via stdin/stdout
7455/// - **Network sockets**: TCP, Unix domain sockets, or other stream-based protocols
7456/// - **Named pipes**: Cross-process communication on the same machine
7457/// - **File I/O**: Reading from and writing to file descriptors
7458///
7459/// # Example
7460///
7461/// Connecting to an agent via stdio:
7462///
7463/// ```no_run
7464/// use agent_client_protocol::UntypedRole;
7465/// # use agent_client_protocol::{ByteStreams};
7466/// use tokio_util::compat::{TokioAsyncReadCompatExt, TokioAsyncWriteCompatExt};
7467///
7468/// # async fn example() -> Result<(), agent_client_protocol::Error> {
7469/// let component = ByteStreams::new(
7470///     tokio::io::stdout().compat_write(),
7471///     tokio::io::stdin().compat(),
7472/// );
7473///
7474/// // Use as a component in a connection
7475/// agent_client_protocol::UntypedRole.builder()
7476///     .name("my-client")
7477///     .connect_to(component)
7478///     .await?;
7479/// # Ok(())
7480/// # }
7481/// ```
7482///
7483/// [`ConnectTo`]: crate::ConnectTo
7484#[derive(Debug)]
7485pub struct ByteStreams<OB, IB> {
7486    outgoing: OB,
7487    incoming: IB,
7488}
7489
7490impl<OB, IB> ByteStreams<OB, IB>
7491where
7492    OB: AsyncWrite + Send + 'static,
7493    IB: AsyncRead + Send + 'static,
7494{
7495    /// Create a new byte stream transport.
7496    pub fn new(outgoing: OB, incoming: IB) -> Self {
7497        Self { outgoing, incoming }
7498    }
7499
7500    fn into_lines(
7501        self,
7502    ) -> Lines<
7503        impl futures::Sink<String, Error = std::io::Error> + Send + 'static,
7504        impl futures::Stream<Item = std::io::Result<String>> + Send + 'static,
7505    > {
7506        use futures::AsyncBufReadExt;
7507        use futures::io::BufReader;
7508        let Self { outgoing, incoming } = self;
7509
7510        let incoming_lines = Box::pin(BufReader::new(incoming).lines());
7511        let outgoing_lines = transport_actor::LineWriter::new(outgoing);
7512
7513        Lines::new(outgoing_lines, incoming_lines)
7514    }
7515}
7516
7517#[cfg(any(
7518    all(
7519        any(feature = "process", feature = "stdio"),
7520        not(target_family = "wasm")
7521    ),
7522    test
7523))]
7524pub(crate) async fn write_line<W>(writer: &mut W, line: String) -> std::io::Result<()>
7525where
7526    W: AsyncWrite + Unpin + ?Sized,
7527{
7528    use futures::AsyncWriteExt as _;
7529
7530    let mut bytes = line.into_bytes();
7531    bytes.push(b'\n');
7532    writer.write_all(&bytes).await?;
7533    writer.flush().await
7534}
7535
7536impl<OB, IB, R: Role> ConnectTo<R> for ByteStreams<OB, IB>
7537where
7538    OB: AsyncWrite + Send + 'static,
7539    IB: AsyncRead + Send + 'static,
7540{
7541    async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
7542        ConnectTo::<R>::connect_to(self.into_lines(), client).await
7543    }
7544
7545    fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
7546        ConnectTo::<R>::into_channel_and_future(self.into_lines())
7547    }
7548}
7549
7550/// A channel endpoint representing one side of a bidirectional JSON-RPC transport.
7551///
7552/// A channel carries complete TransportFrame values, preserving batch boundaries
7553/// across in-process components and transport adapters. Malformed wire input is an
7554/// explicit frame; failures while driving a physical transport are returned by that
7555/// transport's future.
7556///
7557/// # Example
7558///
7559/// ```no_run
7560/// # use agent_client_protocol::UntypedRole;
7561/// # use agent_client_protocol::Channel;
7562/// # async fn example() -> Result<(), agent_client_protocol::Error> {
7563/// let (channel_a, _channel_b) = Channel::duplex();
7564///
7565/// UntypedRole.builder()
7566///     .name("connection-a")
7567///     .connect_to(channel_a)
7568///     .await?;
7569/// # Ok(())
7570/// # }
7571/// ```
7572#[derive(Debug)]
7573pub struct Channel {
7574    /// Receives frames from the counterpart.
7575    pub rx: mpsc::UnboundedReceiver<TransportFrame>,
7576    /// Sends frames to the counterpart.
7577    pub tx: mpsc::UnboundedSender<TransportFrame>,
7578}
7579
7580impl Channel {
7581    /// Create a pair of connected channel endpoints.
7582    ///
7583    /// Frames sent through either endpoint are received by the other endpoint.
7584    #[must_use]
7585    pub fn duplex() -> (Self, Self) {
7586        let (a_tx, b_rx) = mpsc::unbounded();
7587        let (b_tx, a_rx) = mpsc::unbounded();
7588
7589        (Self { rx: a_rx, tx: a_tx }, Self { rx: b_rx, tx: b_tx })
7590    }
7591
7592    /// Copy frames from `rx` to `tx` until the input closes.
7593    ///
7594    /// # Errors
7595    ///
7596    /// Returns an error if the receiving endpoint closes before the input.
7597    pub(crate) async fn copy(mut self) -> Result<(), crate::Error> {
7598        while let Some(frame) = self.rx.next().await {
7599            self.tx
7600                .unbounded_send(frame)
7601                .map_err(crate::util::internal_error)?;
7602        }
7603        Ok(())
7604    }
7605
7606    /// Copy output concurrently with its owning driver, then drain accepted frames.
7607    /// Passive endpoints instead retain the channel's independent half-close lifetime.
7608    pub(crate) async fn copy_with_driver(
7609        self,
7610        driver: Option<crate::ConnectionDriver>,
7611    ) -> Result<(), crate::Error> {
7612        self.copy_with_driver_until(driver, future::pending()).await
7613    }
7614
7615    /// After the destination's owned foreground finishes, keep driving source
7616    /// errors and sink work, but never deliver queued or new input to it.
7617    pub(crate) async fn copy_with_driver_until(
7618        mut self,
7619        mut driver: Option<crate::ConnectionDriver>,
7620        stop_delivery: impl Future<Output = ()>,
7621    ) -> Result<(), crate::Error> {
7622        let mut stop_delivery = pin!(stop_delivery);
7623        let mut delivering = true;
7624        let mut done = false;
7625        loop {
7626            let event = future::poll_fn(|cx| {
7627                if delivering && stop_delivery.as_mut().poll(cx).is_ready() {
7628                    delivering = false;
7629                }
7630                // Driver errors remain authoritative even when stop or EOF is ready.
7631                if !done
7632                    && let Some(driver) = driver.as_mut()
7633                    && let std::task::Poll::Ready(result) = std::pin::Pin::new(driver).poll(cx)
7634                {
7635                    return std::task::Poll::Ready(Either::Left(result));
7636                }
7637                if !delivering && driver.is_none() {
7638                    return std::task::Poll::Ready(Either::Right(None));
7639                }
7640                self.rx.poll_next_unpin(cx).map(Either::Right)
7641            })
7642            .await;
7643            let frame = match event {
7644                Either::Left(result) => {
7645                    result?;
7646                    done = true;
7647                    self.rx.close();
7648                    continue;
7649                }
7650                Either::Right(frame) => frame,
7651            };
7652            let Some(frame) = frame else {
7653                break;
7654            };
7655            if delivering {
7656                self.tx
7657                    .unbounded_send(frame)
7658                    .map_err(crate::util::internal_error)?;
7659            }
7660        }
7661        // Propagate this half-close before waiting for a still-running driver.
7662        drop(self);
7663        if !done && let Some(driver) = driver {
7664            driver.await?;
7665        }
7666        Ok(())
7667    }
7668
7669    /// Bridge two endpoints while inspecting every valid message.
7670    ///
7671    /// Observers are invoked in source order, including for each valid member of
7672    /// a batch. The original frame is forwarded unchanged after inspection.
7673    ///
7674    /// # Errors
7675    ///
7676    /// Returns an observer error or an error if a destination closes before its
7677    /// source.
7678    pub async fn bridge_with_inspection(
7679        left: Self,
7680        right: Self,
7681        mut left_to_right: impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error> + Send,
7682        mut right_to_left: impl FnMut(&RawJsonRpcMessage) -> Result<(), crate::Error> + Send,
7683    ) -> Result<(), crate::Error> {
7684        let Self {
7685            rx: mut left_rx,
7686            tx: left_tx,
7687        } = left;
7688        let Self {
7689            rx: mut right_rx,
7690            tx: right_tx,
7691        } = right;
7692
7693        let left_to_right = async move {
7694            while let Some(frame) = left_rx.next().await {
7695                frame.inspect_messages(&mut left_to_right)?;
7696                right_tx
7697                    .unbounded_send(frame)
7698                    .map_err(crate::util::internal_error)?;
7699            }
7700            Ok::<(), crate::Error>(())
7701        };
7702        let right_to_left = async move {
7703            while let Some(frame) = right_rx.next().await {
7704                frame.inspect_messages(&mut right_to_left)?;
7705                left_tx
7706                    .unbounded_send(frame)
7707                    .map_err(crate::util::internal_error)?;
7708            }
7709            Ok::<(), crate::Error>(())
7710        };
7711
7712        futures::try_join!(left_to_right, right_to_left)?;
7713        Ok(())
7714    }
7715}
7716
7717impl<R: Role> ConnectTo<R> for Channel {
7718    async fn connect_to(self, client: impl ConnectTo<R::Counterpart>) -> Result<(), crate::Error> {
7719        let (client_channel, client_future) = client.into_channel_and_future();
7720
7721        let passive = client_future.is_none();
7722        let outgoing = Box::pin(
7723            Channel {
7724                rx: client_channel.rx,
7725                tx: self.tx,
7726            }
7727            .copy_with_driver(client_future),
7728        );
7729        let incoming = Box::pin(
7730            Channel {
7731                rx: self.rx,
7732                tx: client_channel.tx,
7733            }
7734            .copy(),
7735        );
7736        if passive {
7737            futures::try_join!(outgoing, incoming)?;
7738            return Ok(());
7739        }
7740
7741        match future::select(outgoing, incoming).await {
7742            Either::Left((result, _)) => result,
7743            Either::Right((result, outgoing)) => {
7744                result?;
7745                outgoing.await
7746            }
7747        }
7748    }
7749
7750    fn into_channel_and_future(self) -> (Channel, Option<crate::ConnectionDriver>) {
7751        (self, None)
7752    }
7753}
7754
7755#[cfg(test)]
7756mod tests {
7757    use super::*;
7758
7759    #[test]
7760    fn protected_cleanup_keeps_scoped_runners_polled_on_every_shutdown_path() {
7761        #[derive(Clone, Copy, Debug)]
7762        enum Stop {
7763            ForegroundSuccess,
7764            ForegroundError,
7765            InputEof,
7766            TransportError,
7767            TaskError,
7768            RunnerError,
7769            SupervisorError,
7770        }
7771
7772        struct Dropped(Arc<AtomicBool>);
7773        impl Drop for Dropped {
7774            fn drop(&mut self) {
7775                self.0.store(true, Ordering::Release);
7776            }
7777        }
7778
7779        for stop in [
7780            Stop::ForegroundSuccess,
7781            Stop::ForegroundError,
7782            Stop::InputEof,
7783            Stop::TransportError,
7784            Stop::TaskError,
7785            Stop::RunnerError,
7786            Stop::SupervisorError,
7787        ] {
7788            let cleaned = Arc::new(AtomicBool::new(false));
7789            let disposable_dropped = Arc::new(AtomicBool::new(false));
7790            let close_finished = Arc::new(AtomicBool::new(false));
7791            let (cleanup_tx, cleanup_rx) = oneshot::channel::<()>();
7792            let (scoped_done_tx, scoped_done_rx) = completion_signal();
7793            let (stop_tx, stop_rx) = oneshot::channel::<()>();
7794            let stop_signal = stop_rx.map(|_| ()).boxed().shared();
7795            let (incoming_tx, incoming_rx) = mpsc::unbounded();
7796            let outgoing = futures::sink::unfold((), |(), _line: String| {
7797                future::ready(Ok::<_, std::io::Error>(()))
7798            });
7799            let builder = Client
7800                .builder()
7801                .with_spawned({
7802                    let cleaned = cleaned.clone();
7803                    async move |cx: ConnectionTo<Agent>| {
7804                        cx.shutdown_requested().await;
7805                        // This stands in for the actual scoped native operation:
7806                        // its async cleanup only advances if this runner is polled.
7807                        cleanup_rx.await.unwrap();
7808                        cleaned.store(true, Ordering::Release);
7809                        let _ = scoped_done_tx.send(());
7810                        Ok(())
7811                    }
7812                })
7813                .with_spawned({
7814                    let stop_signal = stop_signal.clone();
7815                    async move |_cx| {
7816                        stop_signal.await;
7817                        if matches!(stop, Stop::RunnerError) {
7818                            Err(crate::Error::internal_error().data("runner failure"))
7819                        } else {
7820                            future::pending().await
7821                        }
7822                    }
7823                })
7824                .on_close({
7825                    let close_finished = close_finished.clone();
7826                    let scoped_done = scoped_done_rx.clone();
7827                    async move |cx: ConnectionTo<Agent>| {
7828                        // EOF cancellation must precede, not await, close callbacks.
7829                        cx.shutdown_requested().await;
7830                        assert!(!cx.is_incoming_closed());
7831                        scoped_done.await;
7832                        close_finished.store(true, Ordering::Release);
7833                        Ok(())
7834                    }
7835                });
7836            let (connection, driver) =
7837                builder.into_connection_and_future(Lines::new(outgoing, incoming_rx), false, {
7838                    let stop_signal = stop_signal.clone();
7839                    async move |cx| {
7840                        if matches!(stop, Stop::InputEof) {
7841                            cx.incoming_closed().await;
7842                            return Ok(());
7843                        }
7844                        stop_signal.await;
7845                        match stop {
7846                            Stop::ForegroundSuccess | Stop::SupervisorError => Ok(()),
7847                            Stop::ForegroundError => {
7848                                Err(crate::Error::internal_error().data("foreground failure"))
7849                            }
7850                            _ => future::pending().await,
7851                        }
7852                    }
7853                });
7854            let disposable = Dropped(disposable_dropped.clone());
7855            connection
7856                .spawn(async move {
7857                    let _disposable = disposable;
7858                    future::pending().await
7859                })
7860                .unwrap();
7861            connection
7862                .spawn({
7863                    let stop_signal = stop_signal.clone();
7864                    async move {
7865                        stop_signal.await;
7866                        if matches!(stop, Stop::TaskError) {
7867                            Err(crate::Error::internal_error().data("task failure"))
7868                        } else {
7869                            future::pending().await
7870                        }
7871                    }
7872                })
7873                .unwrap();
7874            connection
7875                .spawn_protected({
7876                    let connection = connection.clone();
7877                    async move {
7878                        connection.shutdown_requested().await;
7879                        scoped_done_rx.await;
7880                        if matches!(stop, Stop::SupervisorError) {
7881                            Err(crate::Error::internal_error().data("supervisor failure"))
7882                        } else {
7883                            Ok(())
7884                        }
7885                    }
7886                })
7887                .unwrap();
7888            let mut driver = Box::pin(driver);
7889            assert!(driver.as_mut().now_or_never().is_none(), "{stop:?}");
7890            assert!(connection.shutdown_requested().now_or_never().is_none());
7891            let _ = stop_tx.send(());
7892            let incoming_tx = match stop {
7893                Stop::InputEof => {
7894                    drop(incoming_tx);
7895                    None
7896                }
7897                Stop::TransportError => {
7898                    incoming_tx
7899                        .unbounded_send(Err(std::io::Error::other("transport failure")))
7900                        .unwrap();
7901                    Some(incoming_tx)
7902                }
7903                _ => Some(incoming_tx),
7904            };
7905            for _ in 0..10 {
7906                assert!(driver.as_mut().now_or_never().is_none(), "{stop:?}");
7907                if connection.shutdown_requested().now_or_never().is_some() {
7908                    break;
7909                }
7910            }
7911            assert!(
7912                connection.shutdown_requested().now_or_never().is_some(),
7913                "{stop:?}"
7914            );
7915            assert!(!cleaned.load(Ordering::Acquire), "{stop:?}");
7916            assert!(!disposable_dropped.load(Ordering::Acquire), "{stop:?}");
7917            cleanup_tx.send(()).unwrap();
7918            // Task acknowledgments may wake an actor already polled in this turn.
7919            // Bound the probe so a broken scoped-runner join fails, not hangs.
7920            let mut result = None;
7921            for _ in 0..10 {
7922                result = driver.as_mut().now_or_never();
7923                if result.is_some() {
7924                    break;
7925                }
7926            }
7927            let result =
7928                result.unwrap_or_else(|| panic!("driver did not finish owned cleanup: {stop:?}"));
7929            match stop {
7930                Stop::ForegroundSuccess | Stop::InputEof => result.unwrap(),
7931                _ => {
7932                    let error = result.expect_err("shutdown must preserve the first error");
7933                    let expected = match stop {
7934                        Stop::ForegroundError => "foreground failure",
7935                        Stop::TransportError => "transport failure",
7936                        Stop::TaskError => "task failure",
7937                        Stop::RunnerError => "runner failure",
7938                        Stop::SupervisorError => "supervisor failure",
7939                        _ => unreachable!(),
7940                    };
7941                    assert!(
7942                        error.data.unwrap().to_string().contains(expected),
7943                        "{stop:?}"
7944                    );
7945                }
7946            }
7947            assert!(cleaned.load(Ordering::Acquire), "{stop:?}");
7948            assert!(disposable_dropped.load(Ordering::Acquire), "{stop:?}");
7949            assert_eq!(
7950                close_finished.load(Ordering::Acquire),
7951                matches!(stop, Stop::InputEof),
7952                "{stop:?}",
7953            );
7954            assert!(connection.spawn_protected(async { Ok(()) }).is_err());
7955            drop(incoming_tx);
7956        }
7957    }
7958
7959    #[test]
7960    fn protected_operation_acknowledgments_are_reaped_and_join_seals_registration() {
7961        let (connection, _message_rx, _pending_replies) = connection_for_response_hook_tests();
7962        // The helper drops its task receiver, so use a live receiver for this probe.
7963        let (task_tx, mut task_rx) = mpsc::unbounded();
7964        let connection = ConnectionTo {
7965            task_tx,
7966            ..connection
7967        };
7968        for _ in 0..100 {
7969            connection.spawn_protected(async { Ok(()) }).unwrap();
7970            assert_eq!(
7971                connection
7972                    .protected_operations
7973                    .lock()
7974                    .unwrap()
7975                    .pending
7976                    .len(),
7977                1
7978            );
7979            let task = task_rx.next().now_or_never().unwrap().unwrap();
7980            futures::executor::block_on(task.run_for_test()).unwrap();
7981        }
7982        assert!(
7983            connection
7984                .wait_protected_operations()
7985                .now_or_never()
7986                .is_some()
7987        );
7988        assert!(
7989            connection
7990                .wait_protected_operations()
7991                .now_or_never()
7992                .is_some()
7993        );
7994        assert!(
7995            connection
7996                .protected_operations
7997                .lock()
7998                .unwrap()
7999                .pending
8000                .is_empty()
8001        );
8002        assert!(connection.spawn_protected(async { Ok(()) }).is_err());
8003        assert!(task_rx.next().now_or_never().is_none());
8004    }
8005
8006    #[test]
8007    fn dropping_unused_finish_signal_preserves_physical_half_closes() {
8008        let outgoing = futures::sink::unfold((), |(), _line: String| {
8009            future::ready(Ok::<_, std::io::Error>(()))
8010        });
8011        let (incoming_tx, incoming_rx) = mpsc::unbounded();
8012        let (Channel { mut rx, tx }, mut driver) =
8013            Lines::new(outgoing, incoming_rx).into_channel_transport();
8014
8015        drop(
8016            driver
8017                .take_finish()
8018                .expect("built-in Lines driver is finishable"),
8019        );
8020        drop(tx);
8021        assert!((&mut driver).now_or_never().is_none());
8022        incoming_tx
8023            .unbounded_send(Ok(
8024                r#"{"jsonrpc":"2.0","method":"test/after-output-eof"}"#.into()
8025            ))
8026            .unwrap();
8027        assert!((&mut driver).now_or_never().is_none());
8028        assert!(rx.next().now_or_never().unwrap().is_some());
8029
8030        drop(incoming_tx);
8031        futures::executor::block_on(driver).unwrap();
8032        assert!(rx.next().now_or_never().unwrap().is_none());
8033    }
8034
8035    #[test]
8036    fn explicit_physical_finish_does_not_hide_a_ready_read_error() {
8037        let outgoing = futures::sink::unfold((), |(), _line: String| {
8038            future::ready(Ok::<_, std::io::Error>(()))
8039        });
8040        let incoming = futures::stream::iter([Err(std::io::Error::other("finish read failed"))]);
8041        let (_channel, mut driver) = Lines::new(outgoing, incoming).into_channel_transport();
8042        assert!(driver.request_finish());
8043
8044        let error = futures::executor::block_on(driver).unwrap_err();
8045        assert_eq!(
8046            error
8047                .data
8048                .and_then(|value| value.as_str().map(str::to_owned)),
8049            Some("finish read failed".into())
8050        );
8051    }
8052
8053    #[cfg(feature = "unstable_protocol_v2")]
8054    fn connection_with_task_receiver() -> (
8055        ConnectionTo<crate::role::UntypedRole>,
8056        mpsc::UnboundedReceiver<Task>,
8057    ) {
8058        let (message_tx, _message_rx) = mpsc::unbounded();
8059        let (task_tx, task_rx) = mpsc::unbounded();
8060        let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8061        let transport_completion: SharedTransportCompletion =
8062            future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8063        let pending_replies = PendingReplies::default();
8064
8065        (
8066            ConnectionTo::new(
8067                crate::role::UntypedRole,
8068                message_tx,
8069                task_tx,
8070                dynamic_handler_tx,
8071                transport_completion,
8072                pending_replies.registrar(),
8073                ProtocolMode::disabled(),
8074            ),
8075            task_rx,
8076        )
8077    }
8078
8079    #[cfg(feature = "unstable_protocol_v2")]
8080    #[test]
8081    fn v2_builder_exposes_typed_context_to_user_callbacks() {
8082        fn assert_v2_context(_connection: &V2ConnectionTo<Agent>) {}
8083
8084        let _builder = Client
8085            .v2()
8086            .on_receive_request(
8087                async |_request: UntypedMessage, _responder, connection| {
8088                    assert_v2_context(&connection);
8089                    Ok(())
8090                },
8091                crate::on_receive_request!(),
8092            )
8093            .on_receive_notification(
8094                async |_notification: UntypedMessage, connection| {
8095                    assert_v2_context(&connection);
8096                    Ok(())
8097                },
8098                crate::on_receive_notification!(),
8099            )
8100            .on_receive_dispatch(
8101                async |_dispatch: Dispatch<UntypedMessage, UntypedMessage>, connection| {
8102                    assert_v2_context(&connection);
8103                    Ok(())
8104                },
8105                crate::on_receive_dispatch!(),
8106            )
8107            .on_receive_request_from(
8108                Agent,
8109                async |_request: UntypedMessage, _responder, connection| {
8110                    assert_v2_context(&connection);
8111                    Ok(())
8112                },
8113                crate::on_receive_request!(),
8114            )
8115            .on_receive_notification_from(
8116                Agent,
8117                async |_notification: UntypedMessage, connection| {
8118                    assert_v2_context(&connection);
8119                    Ok(())
8120                },
8121                crate::on_receive_notification!(),
8122            )
8123            .on_receive_dispatch_from(
8124                Agent,
8125                async |_dispatch: Dispatch<UntypedMessage, UntypedMessage>, connection| {
8126                    assert_v2_context(&connection);
8127                    Ok(())
8128                },
8129                crate::on_receive_dispatch!(),
8130            )
8131            .with_spawned(async |connection| {
8132                assert_v2_context(&connection);
8133                Ok(())
8134            })
8135            .on_close(async |connection| {
8136                assert_v2_context(&connection);
8137                Ok(())
8138            });
8139    }
8140
8141    #[cfg(feature = "unstable_protocol_v2")]
8142    #[test]
8143    fn proxy_builders_select_exact_proxy_protocol_guards() -> Result<(), crate::Error> {
8144        use crate::schema::ProtocolVersion;
8145
8146        for (mode, selected, unsupported) in [
8147            (
8148                Proxy.builder().protocol_mode,
8149                ProtocolVersion::V1,
8150                ProtocolVersion::V2,
8151            ),
8152            (
8153                Proxy.v2().protocol_mode,
8154                ProtocolVersion::V2,
8155                ProtocolVersion::V1,
8156            ),
8157        ] {
8158            assert_eq!(mode.api_protocol_version(), Some(selected));
8159
8160            let error = ProtocolCompat::new(mode)
8161                .incoming_message(UntypedMessage::new(
8162                    "_proxy/initialize",
8163                    serde_json::json!({ "protocolVersion": unsupported }),
8164                )?)
8165                .expect_err("a proxy builder must reject the other protocol version");
8166            let data = error
8167                .data
8168                .as_ref()
8169                .and_then(|data| data.as_str())
8170                .unwrap_or_default();
8171            assert!(
8172                data.contains(&format!("only supports ACP protocol version {selected}")),
8173                "{error:?}"
8174            );
8175        }
8176
8177        Ok(())
8178    }
8179
8180    #[cfg(feature = "unstable_protocol_v2")]
8181    #[test]
8182    fn v2_proxy_rejects_explicitly_prewrapped_initialize_request() {
8183        let (message_tx, message_rx) = mpsc::unbounded();
8184        let (task_tx, _task_rx) = mpsc::unbounded();
8185        let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8186        let transport_completion: SharedTransportCompletion =
8187            future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8188        let pending_replies = PendingReplies::default();
8189        let connection = ConnectionTo::new(
8190            crate::Conductor,
8191            message_tx,
8192            task_tx,
8193            dynamic_handler_tx,
8194            transport_completion,
8195            pending_replies.registrar(),
8196            ProtocolMode::v2_proxy(),
8197        );
8198
8199        let request = crate::schema::SuccessorMessage {
8200            message: UntypedMessage::new(
8201                "initialize",
8202                serde_json::json!({ "protocolVersion": crate::schema::ProtocolVersion::V1 }),
8203            )
8204            .expect("test initialize request should serialize"),
8205            meta: None,
8206        };
8207        let sent = connection.send_request_to(Agent, request);
8208
8209        let (transport_tx, mut transport_rx) = mpsc::unbounded();
8210        let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
8211            message_rx,
8212            pending_replies,
8213            transport_tx,
8214            ProtocolCompat::new(ProtocolMode::v2_proxy()),
8215            future::pending::<()>().boxed().shared(),
8216        ));
8217        assert!(
8218            actor.as_mut().now_or_never().is_none(),
8219            "the outgoing actor should continue after rejecting the request"
8220        );
8221        assert!(
8222            transport_rx.next().now_or_never().is_none(),
8223            "an explicitly prewrapped initialize must not reach the transport"
8224        );
8225
8226        let error = futures::executor::block_on(sent.block_task())
8227            .expect_err("connection routing must own successor wrapping");
8228        let data = error
8229            .data
8230            .as_ref()
8231            .and_then(|data| data.as_str())
8232            .unwrap_or_default();
8233        assert!(data.contains("logical `initialize`"), "{error:?}");
8234        assert!(data.contains("_proxy/successor"), "{error:?}");
8235    }
8236
8237    #[cfg(feature = "unstable_protocol_v2")]
8238    #[test]
8239    fn v2_proxy_builder_exposes_typed_context_to_user_callbacks() {
8240        fn assert_v2_context(_connection: &V2ConnectionTo<crate::Conductor>) {}
8241
8242        let _builder = Proxy
8243            .v2()
8244            .on_receive_request_from(
8245                Client,
8246                async |_request: UntypedMessage, _responder, connection| {
8247                    assert_v2_context(&connection);
8248                    Ok(())
8249                },
8250                crate::on_receive_request!(),
8251            )
8252            .on_receive_notification_from(
8253                Agent,
8254                async |_notification: UntypedMessage, connection| {
8255                    assert_v2_context(&connection);
8256                    Ok(())
8257                },
8258                crate::on_receive_notification!(),
8259            )
8260            .on_receive_dispatch_from(
8261                Client,
8262                async |_dispatch: Dispatch<UntypedMessage, UntypedMessage>, connection| {
8263                    assert_v2_context(&connection);
8264                    Ok(())
8265                },
8266                crate::on_receive_dispatch!(),
8267            )
8268            .with_spawned(async |connection| {
8269                assert_v2_context(&connection);
8270                Ok(())
8271            })
8272            .on_close(async |connection| {
8273                assert_v2_context(&connection);
8274                Ok(())
8275            });
8276    }
8277
8278    #[cfg(feature = "unstable_protocol_v2")]
8279    #[test]
8280    fn raw_connection_spawns_v2_builder_with_typed_child_callback() {
8281        let (parent, mut task_rx) = connection_with_task_receiver();
8282        let (transport, _peer) = Channel::duplex();
8283        let (callback_tx, callback_rx) = oneshot::channel();
8284
8285        let child: ConnectionTo<Agent> = parent
8286            .spawn_connection::<Client>(
8287                Client
8288                    .v2()
8289                    .with_spawned(async move |_connection: V2ConnectionTo<Agent>| {
8290                        callback_tx.send(()).map_err(|()| {
8291                            crate::util::internal_error("typed child callback receiver was dropped")
8292                        })
8293                    }),
8294                transport,
8295            )
8296            .expect("v2 child connection should be spawned");
8297
8298        let task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
8299            .expect("child connection task should already be queued")
8300            .expect("parent task queue should remain open");
8301        futures::executor::block_on(async {
8302            match future::select(Box::pin(task.run_for_test()), Box::pin(callback_rx)).await {
8303                Either::Right((Ok(()), child_task)) => drop(child_task),
8304                Either::Right((Err(error), _)) => {
8305                    panic!("typed child callback sender was dropped: {error}")
8306                }
8307                Either::Left((result, _)) => {
8308                    panic!("child connection stopped before its typed callback ran: {result:?}")
8309                }
8310            }
8311        });
8312
8313        drop(child);
8314    }
8315
8316    #[cfg(feature = "unstable_protocol_v2")]
8317    #[test]
8318    fn raw_connection_can_return_v2_context_for_spawned_builder() {
8319        let (parent, mut task_rx) = connection_with_task_receiver();
8320        let (transport, _peer) = Channel::duplex();
8321
8322        let child: V2ConnectionTo<Agent> = parent
8323            .spawn_connection_with_context(Client.v2(), transport)
8324            .expect("v2 child connection should be spawned");
8325
8326        let child_task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
8327            .expect("child connection task should already be queued")
8328            .expect("parent task queue should remain open");
8329
8330        drop((child, child_task));
8331    }
8332
8333    fn connection_with_dynamic_handler_receiver() -> (
8334        ConnectionTo<crate::role::UntypedRole>,
8335        mpsc::UnboundedReceiver<DynamicHandlerMessage<crate::role::UntypedRole>>,
8336    ) {
8337        let (message_tx, _message_rx) = mpsc::unbounded();
8338        let (task_tx, _task_rx) = mpsc::unbounded();
8339        let (dynamic_handler_tx, dynamic_handler_rx) = mpsc::unbounded();
8340        let transport_completion: SharedTransportCompletion =
8341            future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8342        let pending_replies = PendingReplies::default();
8343
8344        (
8345            ConnectionTo::new(
8346                crate::role::UntypedRole,
8347                message_tx,
8348                task_tx,
8349                dynamic_handler_tx,
8350                transport_completion,
8351                pending_replies.registrar(),
8352                ProtocolMode::disabled(),
8353            ),
8354            dynamic_handler_rx,
8355        )
8356    }
8357
8358    struct ClaimingDynamicHandler;
8359
8360    impl HandleDispatchFrom<crate::role::UntypedRole> for ClaimingDynamicHandler {
8361        fn handle_dispatch_from(
8362            &mut self,
8363            _message: Dispatch,
8364            _connection: ConnectionTo<crate::role::UntypedRole>,
8365        ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
8366            future::ready(Ok(Handled::Yes))
8367        }
8368
8369        fn describe_chain(&self) -> impl Debug {
8370            "ClaimingDynamicHandler"
8371        }
8372    }
8373
8374    fn connection_for_response_hook_tests() -> (
8375        ConnectionTo<crate::role::UntypedRole>,
8376        mpsc::UnboundedReceiver<OutgoingMessage>,
8377        PendingReplies,
8378    ) {
8379        let (message_tx, message_rx) = mpsc::unbounded();
8380        let (task_tx, _task_rx) = mpsc::unbounded();
8381        let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8382        let transport_completion: SharedTransportCompletion =
8383            future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8384        let pending_replies = PendingReplies::default();
8385
8386        (
8387            ConnectionTo::new(
8388                crate::role::UntypedRole,
8389                message_tx,
8390                task_tx,
8391                dynamic_handler_tx,
8392                transport_completion,
8393                pending_replies.registrar(),
8394                ProtocolMode::disabled(),
8395            ),
8396            message_rx,
8397            pending_replies,
8398        )
8399    }
8400
8401    #[cfg(feature = "unstable_protocol_v2")]
8402    fn route_test_response(
8403        request_id: RequestId,
8404        pending_replies: &PendingReplies,
8405        result: Result<serde_json::Value, crate::Error>,
8406    ) {
8407        let pending_reply = pending_replies
8408            .remove(&request_id)
8409            .expect("the request should have a pending reply");
8410        let (dispatch, _) =
8411            incoming_actor::dispatch_from_response(request_id, pending_reply, result);
8412        let Dispatch::Response(result, router) = dispatch else {
8413            panic!("expected a response dispatch");
8414        };
8415        router
8416            .route_with_result(result)
8417            .expect("response should route to the pending request");
8418    }
8419
8420    #[cfg(feature = "unstable_protocol_v2")]
8421    #[test]
8422    fn response_hook_runs_when_success_is_routed_before_consumption() {
8423        let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8424        let hook_ran = Arc::new(AtomicBool::new(false));
8425        let sent = connection.send_request_to_with_response_hook_after(
8426            crate::role::UntypedRole,
8427            UntypedMessage::new("hooked", serde_json::json!({}))
8428                .expect("test request should serialize"),
8429            future::ready(Ok(())),
8430            {
8431                let hook_ran = hook_ran.clone();
8432                move |response| {
8433                    assert_eq!(response, &serde_json::json!({"ok": true}));
8434                    hook_ran.store(true, Ordering::Release);
8435                    Ok(())
8436                }
8437            },
8438        );
8439        let request_id = sent.id().clone();
8440
8441        route_test_response(
8442            request_id,
8443            &pending_replies,
8444            Ok(serde_json::json!({"ok": true})),
8445        );
8446
8447        assert!(hook_ran.load(Ordering::Acquire));
8448        assert_eq!(
8449            futures::executor::block_on(sent.block_task())
8450                .expect("routed response should remain consumable"),
8451            serde_json::json!({"ok": true})
8452        );
8453    }
8454
8455    #[cfg(feature = "unstable_protocol_v2")]
8456    #[test]
8457    fn response_hook_skips_errors_but_outlives_a_dropped_consumer() {
8458        let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8459        let peer_error_hook_ran = Arc::new(AtomicBool::new(false));
8460        let peer_error = connection.send_request_to_with_response_hook_after(
8461            crate::role::UntypedRole,
8462            UntypedMessage::new("peer-error", serde_json::json!({}))
8463                .expect("test request should serialize"),
8464            future::ready(Ok(())),
8465            {
8466                let hook_ran = peer_error_hook_ran.clone();
8467                move |_| {
8468                    hook_ran.store(true, Ordering::Release);
8469                    Ok(())
8470                }
8471            },
8472        );
8473        let peer_error_id = peer_error.id().clone();
8474        route_test_response(
8475            peer_error_id,
8476            &pending_replies,
8477            Err(crate::Error::invalid_request()),
8478        );
8479        assert!(
8480            futures::executor::block_on(peer_error.block_task()).is_err(),
8481            "the peer error should reach the consumer"
8482        );
8483        assert!(!peer_error_hook_ran.load(Ordering::Acquire));
8484
8485        let dropped_hook_ran = Arc::new(AtomicBool::new(false));
8486        let dropped = connection.send_request_to_with_response_hook_after(
8487            crate::role::UntypedRole,
8488            UntypedMessage::new("dropped", serde_json::json!({}))
8489                .expect("test request should serialize"),
8490            future::ready(Ok(())),
8491            {
8492                let hook_ran = dropped_hook_ran.clone();
8493                move |_| {
8494                    hook_ran.store(true, Ordering::Release);
8495                    Ok(())
8496                }
8497            },
8498        );
8499        let dropped_id = dropped.id().clone();
8500        drop(dropped);
8501        route_test_response(
8502            dropped_id,
8503            &pending_replies,
8504            Ok(serde_json::json!({"ok": true})),
8505        );
8506        assert!(dropped_hook_ran.load(Ordering::Acquire));
8507    }
8508
8509    #[cfg(feature = "unstable_protocol_v2")]
8510    #[test]
8511    fn response_hook_failure_replaces_the_success_result() {
8512        let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8513        let sent = connection.send_request_to_with_response_hook_after(
8514            crate::role::UntypedRole,
8515            UntypedMessage::new("hook-failure", serde_json::json!({}))
8516                .expect("test request should serialize"),
8517            future::ready(Ok(())),
8518            |_| Err(crate::Error::internal_error().data("response hook failed")),
8519        );
8520        let request_id = sent.id().clone();
8521        route_test_response(
8522            request_id,
8523            &pending_replies,
8524            Ok(serde_json::json!({"ok": true})),
8525        );
8526
8527        let error = futures::executor::block_on(sent.block_task())
8528            .expect_err("the hook failure should replace the successful response");
8529        assert_eq!(error.code, crate::ErrorCode::InternalError);
8530        assert_eq!(error.data, Some(serde_json::json!("response hook failed")));
8531    }
8532
8533    #[test]
8534    fn ordered_request_waits_for_readiness_before_publication() {
8535        let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
8536        let (ready_tx, ready_rx) = oneshot::channel();
8537        let sent = connection.send_ordered_request_to_after(
8538            crate::role::UntypedRole,
8539            UntypedMessage::new("after-ready", serde_json::json!({}))
8540                .expect("test request should serialize"),
8541            async move { ready_rx.await.map_err(crate::Error::into_internal_error) },
8542        );
8543
8544        let (transport_tx, mut transport_rx) = mpsc::unbounded();
8545        let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
8546            message_rx,
8547            pending_replies,
8548            transport_tx,
8549            ProtocolCompat::new(ProtocolMode::disabled()),
8550            future::pending::<()>().boxed().shared(),
8551        ));
8552
8553        assert!(
8554            actor.as_mut().now_or_never().is_none(),
8555            "the outgoing actor should wait for readiness"
8556        );
8557        assert!(
8558            transport_rx.next().now_or_never().is_none(),
8559            "the request must not be published before readiness"
8560        );
8561
8562        ready_tx
8563            .send(())
8564            .expect("the readiness receiver should remain active");
8565        assert!(
8566            actor.as_mut().now_or_never().is_none(),
8567            "the outgoing actor should continue serving after publication"
8568        );
8569        let frame = transport_rx
8570            .next()
8571            .now_or_never()
8572            .expect("the ready request should be published")
8573            .expect("the transport queue should remain open");
8574        assert!(matches!(
8575            frame,
8576            TransportFrame::Single(RawJsonRpcMessage::Request(_))
8577        ));
8578
8579        drop(sent);
8580    }
8581
8582    #[test]
8583    fn foreground_finish_settles_unready_requests_and_preserves_ready_output_fifo() {
8584        let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
8585        let unready = connection.send_ordered_request_to_after(
8586            crate::role::UntypedRole,
8587            UntypedMessage::new("unready", serde_json::json!({})).unwrap(),
8588            future::pending(),
8589        );
8590        let unready_id = unready.id().clone();
8591        send_raw_message(
8592            &connection.message_tx,
8593            OutgoingMessage::Notification {
8594                untyped: UntypedMessage::new("first", serde_json::json!({})).unwrap(),
8595            },
8596        )
8597        .unwrap();
8598        let ready = connection.send_ordered_request_to_after(
8599            crate::role::UntypedRole,
8600            UntypedMessage::new("ready", serde_json::json!({})).unwrap(),
8601            future::ready(Ok(())),
8602        );
8603        let unready_after = connection.send_ordered_request_to_after(
8604            crate::role::UntypedRole,
8605            UntypedMessage::new("unready-after", serde_json::json!({})).unwrap(),
8606            future::pending(),
8607        );
8608        let unready_after_id = unready_after.id().clone();
8609        send_raw_message(
8610            &connection.message_tx,
8611            OutgoingMessage::Notification {
8612                untyped: UntypedMessage::new("last", serde_json::json!({})).unwrap(),
8613            },
8614        )
8615        .unwrap();
8616        let (done_tx, done_rx) = oneshot::channel();
8617        send_raw_message(
8618            &connection.message_tx,
8619            OutgoingMessage::CloseAfterDraining { done: done_tx },
8620        )
8621        .unwrap();
8622        let (transport_tx, transport_rx) = mpsc::unbounded();
8623        futures::executor::block_on(outgoing_actor::outgoing_protocol_actor(
8624            message_rx,
8625            pending_replies.clone(),
8626            transport_tx,
8627            ProtocolCompat::new(ProtocolMode::disabled()),
8628            future::ready(()).boxed().shared(),
8629        ))
8630        .unwrap();
8631        futures::executor::block_on(done_rx).unwrap();
8632        let error = futures::executor::block_on(unready.block_task())
8633            .expect_err("an unresolved gate must explicitly fail its consumer");
8634        assert!(
8635            error
8636                .data
8637                .unwrap()
8638                .to_string()
8639                .contains("foreground completed before outgoing request readiness")
8640        );
8641        assert!(!pending_replies.contains(&unready_id));
8642        let error = futures::executor::block_on(unready_after.block_task())
8643            .expect_err("each unresolved gate must fail without repolling a consumed signal");
8644        assert!(
8645            error
8646                .data
8647                .unwrap()
8648                .to_string()
8649                .contains("foreground completed before outgoing request readiness")
8650        );
8651        assert!(!pending_replies.contains(&unready_after_id));
8652        assert!(pending_replies.contains(ready.id()));
8653        let frames = futures::executor::block_on(transport_rx.collect::<Vec<_>>());
8654        let methods = frames
8655            .into_iter()
8656            .map(|frame| match frame {
8657                TransportFrame::Single(RawJsonRpcMessage::Notification(message)) => {
8658                    message.method.to_string()
8659                }
8660                TransportFrame::Single(RawJsonRpcMessage::Request(message)) => {
8661                    message.method.to_string()
8662                }
8663                _ => panic!("expected ready request/notification output"),
8664            })
8665            .collect::<Vec<_>>();
8666        assert_eq!(methods, ["first", "ready", "last"]);
8667    }
8668
8669    #[test]
8670    fn ordered_blocking_transform_precedes_response_acknowledgment() {
8671        let (connection, _message_rx, pending_replies) = connection_for_response_hook_tests();
8672        let sent = connection.send_ordered_request_to(
8673            crate::role::UntypedRole,
8674            UntypedMessage::new("ordered-transform", serde_json::json!({}))
8675                .expect("test request should serialize"),
8676        );
8677        let request_id = sent.id().clone();
8678        let pending_reply = pending_replies
8679            .remove(&request_id)
8680            .expect("the request should have a pending reply");
8681        let (dispatch, response_dispatch) = incoming_actor::dispatch_from_response(
8682            request_id,
8683            pending_reply,
8684            Err(crate::Error::invalid_params()),
8685        );
8686        let Dispatch::Response(result, router) = dispatch else {
8687            panic!("expected a response dispatch");
8688        };
8689        router
8690            .route_with_result(result)
8691            .expect("response should route to the pending request");
8692        let acknowledgment = response_dispatch
8693            .complete()
8694            .expect("an ordered response should wait for acknowledgment");
8695        let acknowledgment = Arc::new(Mutex::new(Some(acknowledgment)));
8696        let acknowledgment_probe = acknowledgment.clone();
8697
8698        let error =
8699            futures::executor::block_on(sent.block_task_with_ordered_result(move |result| {
8700                assert_eq!(
8701                    acknowledgment_probe
8702                        .lock()
8703                        .expect("acknowledgment mutex poisoned")
8704                        .as_mut()
8705                        .expect("acknowledgment receiver should remain available")
8706                        .try_recv()
8707                        .expect("acknowledgment sender should remain open"),
8708                    None,
8709                    "the ordered response was acknowledged before its transform"
8710                );
8711                result
8712            }))
8713            .expect_err("the peer error should survive the ordered transform");
8714        assert_eq!(error.code, crate::ErrorCode::InvalidParams);
8715
8716        let acknowledgment = acknowledgment
8717            .lock()
8718            .expect("acknowledgment mutex poisoned")
8719            .take()
8720            .expect("acknowledgment receiver should remain available");
8721        futures::executor::block_on(acknowledgment)
8722            .expect("the transform should release the ordered response");
8723    }
8724
8725    #[cfg(feature = "unstable_protocol_v2")]
8726    #[test]
8727    fn outgoing_request_readiness_failure_rejects_without_publication() {
8728        let (connection, message_rx, pending_replies) = connection_for_response_hook_tests();
8729        let hook_ran = Arc::new(AtomicBool::new(false));
8730        let sent = connection.send_request_to_with_response_hook_after(
8731            crate::role::UntypedRole,
8732            UntypedMessage::new("never-published", serde_json::json!({}))
8733                .expect("test request should serialize"),
8734            future::ready(Err(crate::Error::internal_error().data("readiness failed"))),
8735            {
8736                let hook_ran = hook_ran.clone();
8737                move |_| {
8738                    hook_ran.store(true, Ordering::Release);
8739                    Ok(())
8740                }
8741            },
8742        );
8743
8744        let (transport_tx, mut transport_rx) = mpsc::unbounded();
8745        let mut actor = Box::pin(outgoing_actor::outgoing_protocol_actor(
8746            message_rx,
8747            pending_replies,
8748            transport_tx,
8749            ProtocolCompat::new(ProtocolMode::disabled()),
8750            future::pending::<()>().boxed().shared(),
8751        ));
8752
8753        assert!(
8754            actor.as_mut().now_or_never().is_none(),
8755            "the outgoing actor should continue serving after rejecting the request"
8756        );
8757        assert!(
8758            transport_rx.next().now_or_never().is_none(),
8759            "a request whose readiness failed must not be published"
8760        );
8761        let error = futures::executor::block_on(sent.block_task())
8762            .expect_err("the readiness error should reach the request consumer");
8763        assert_eq!(error.code, crate::ErrorCode::InternalError);
8764        assert_eq!(error.data, Some(serde_json::json!("readiness failed")));
8765        assert!(!hook_ran.load(Ordering::Acquire));
8766    }
8767
8768    #[test]
8769    fn ordered_request_is_marked_before_entering_outgoing_queue() {
8770        let (message_tx, mut message_rx) = mpsc::unbounded();
8771        let (task_tx, mut task_rx) = mpsc::unbounded();
8772        let (dynamic_handler_tx, _dynamic_handler_rx) = mpsc::unbounded();
8773        let transport_completion: SharedTransportCompletion =
8774            future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8775        let pending_replies = PendingReplies::default();
8776        let connection = ConnectionTo::new(
8777            crate::role::UntypedRole,
8778            message_tx,
8779            task_tx,
8780            dynamic_handler_tx,
8781            transport_completion,
8782            pending_replies.registrar(),
8783            ProtocolMode::disabled(),
8784        );
8785
8786        let sent = connection.send_ordered_request_to(
8787            crate::role::UntypedRole,
8788            UntypedMessage::new("ordered", serde_json::json!({}))
8789                .expect("test request should serialize"),
8790        );
8791        let request_id = sent.id().clone();
8792        let message = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut message_rx))
8793            .expect("outgoing request should already be queued")
8794            .expect("outgoing request queue should remain open");
8795        let OutgoingMessage::Request { id, .. } = message else {
8796            panic!("expected an outgoing request");
8797        };
8798        assert_eq!(id, request_id);
8799
8800        let pending_reply = pending_replies
8801            .remove(&request_id)
8802            .expect("the request should have a pending reply");
8803        assert!(
8804            pending_reply.ordering.is_ordered(),
8805            "the response ordering barrier must be installed before publication"
8806        );
8807
8808        // Route the response before the callback is registered. The pre-set
8809        // ordering marker must hold dispatch until the callback task is
8810        // subsequently installed and completes.
8811        let (dispatch, response_dispatch) = incoming_actor::dispatch_from_response(
8812            request_id,
8813            pending_reply,
8814            Ok(serde_json::json!({"ok": true})),
8815        );
8816        let Dispatch::Response(result, router) = dispatch else {
8817            panic!("expected a response dispatch");
8818        };
8819        router
8820            .route_with_result(result)
8821            .expect("response should route to the pending request");
8822        let acknowledgment = response_dispatch
8823            .complete()
8824            .expect("an ordered response should require acknowledgment");
8825
8826        let callback_ran = Arc::new(AtomicBool::new(false));
8827        sent.on_receiving_result({
8828            let callback_ran = callback_ran.clone();
8829            async move |result| {
8830                assert_eq!(result?, serde_json::json!({"ok": true}));
8831                callback_ran.store(true, Ordering::Release);
8832                Ok(())
8833            }
8834        })
8835        .expect("ordered callback should be scheduled");
8836
8837        let task = futures::FutureExt::now_or_never(futures::StreamExt::next(&mut task_rx))
8838            .expect("callback task should already be queued")
8839            .expect("callback task queue should remain open");
8840        futures::executor::block_on(task.run_for_test()).expect("callback task should succeed");
8841        futures::executor::block_on(acknowledgment)
8842            .expect("callback completion should acknowledge dispatch");
8843        assert!(callback_ran.load(Ordering::Acquire));
8844    }
8845
8846    fn next_dynamic_handler_message<Counterpart: Role>(
8847        receiver: &mut mpsc::UnboundedReceiver<DynamicHandlerMessage<Counterpart>>,
8848    ) -> Option<DynamicHandlerMessage<Counterpart>> {
8849        futures::FutureExt::now_or_never(futures::StreamExt::next(receiver))
8850            .expect("dynamic-handler receiver should be ready")
8851    }
8852
8853    #[cfg(feature = "unstable_protocol_v2")]
8854    #[test]
8855    fn v2_dynamic_handler_guard_registers_and_removes_handler() {
8856        let (message_tx, _message_rx) = mpsc::unbounded();
8857        let (task_tx, _task_rx) = mpsc::unbounded();
8858        let (dynamic_handler_tx, mut dynamic_handler_rx) = mpsc::unbounded();
8859        let transport_completion: SharedTransportCompletion =
8860            future::ready(Ok::<(), crate::Error>(())).boxed().shared();
8861        let pending_replies = PendingReplies::default();
8862        let connection = V2ConnectionTo {
8863            inner: ConnectionTo::new(
8864                Agent,
8865                message_tx,
8866                task_tx,
8867                dynamic_handler_tx,
8868                transport_completion,
8869                pending_replies.registrar(),
8870                ProtocolMode::v2_client(),
8871            ),
8872        };
8873
8874        let guard = connection
8875            .add_dynamic_handler(NullHandler)
8876            .expect("v2 dynamic handler should register");
8877        let added_uuid = match next_dynamic_handler_message(&mut dynamic_handler_rx) {
8878            Some(DynamicHandlerMessage::AddDynamicHandler(uuid, _)) => uuid,
8879            other => panic!("expected v2 handler registration, got {other:?}"),
8880        };
8881
8882        drop(guard);
8883
8884        match next_dynamic_handler_message(&mut dynamic_handler_rx) {
8885            Some(DynamicHandlerMessage::RemoveDynamicHandler(uuid)) => {
8886                assert_eq!(uuid, added_uuid);
8887            }
8888            other => panic!("expected v2 handler removal, got {other:?}"),
8889        }
8890    }
8891
8892    #[test]
8893    fn dropping_dynamic_handler_guard_unregisters_handler() {
8894        let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8895        let guard = connection.add_dynamic_handler(NullHandler).unwrap();
8896
8897        let added_uuid = match next_dynamic_handler_message(&mut receiver) {
8898            Some(DynamicHandlerMessage::AddDynamicHandler(uuid, _)) => uuid,
8899            other => panic!("expected handler registration, got {other:?}"),
8900        };
8901
8902        drop(guard);
8903
8904        match next_dynamic_handler_message(&mut receiver) {
8905            Some(DynamicHandlerMessage::RemoveDynamicHandler(uuid)) => {
8906                assert_eq!(uuid, added_uuid);
8907            }
8908            other => panic!("expected handler removal, got {other:?}"),
8909        }
8910    }
8911
8912    #[test]
8913    fn dropping_dynamic_handler_guard_deactivates_queued_handler_immediately() {
8914        let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8915        let guard = connection
8916            .add_dynamic_handler(ClaimingDynamicHandler)
8917            .expect("dynamic handler should register");
8918        let mut handler = match next_dynamic_handler_message(&mut receiver) {
8919            Some(DynamicHandlerMessage::AddDynamicHandler(_, handler)) => handler,
8920            other => panic!("expected handler registration, got {other:?}"),
8921        };
8922
8923        drop(guard);
8924
8925        let message = Dispatch::Notification(
8926            UntypedMessage::new("stale", serde_json::json!({}))
8927                .expect("test notification should serialize"),
8928        );
8929        let handled =
8930            futures::executor::block_on(handler.dyn_handle_dispatch_from(message, connection))
8931                .expect("inactive handler should decline cleanly");
8932        assert!(matches!(handled, Handled::No { retry: false, .. }));
8933    }
8934
8935    #[test]
8936    fn dynamic_handler_barrier_acknowledges_prior_messages() {
8937        let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8938        let _guard = connection.add_dynamic_handler(NullHandler).unwrap();
8939        let mut barrier = Box::pin(connection.dynamic_handler_barrier());
8940
8941        assert!(matches!(
8942            next_dynamic_handler_message(&mut receiver),
8943            Some(DynamicHandlerMessage::AddDynamicHandler(_, _))
8944        ));
8945        assert!(
8946            barrier.as_mut().now_or_never().is_none(),
8947            "the barrier must wait for the incoming actor"
8948        );
8949
8950        let acknowledgment = match next_dynamic_handler_message(&mut receiver) {
8951            Some(DynamicHandlerMessage::AcknowledgedBarrier(acknowledgment)) => acknowledgment,
8952            other => panic!("expected acknowledged barrier, got {other:?}"),
8953        };
8954        acknowledgment
8955            .send(())
8956            .expect("the barrier receiver should remain active");
8957        futures::executor::block_on(barrier)
8958            .expect("the acknowledged dynamic-handler barrier should complete");
8959    }
8960
8961    #[test]
8962    fn detaching_dynamic_handler_guard_does_not_leak_connection() {
8963        let (connection, mut receiver) = connection_with_dynamic_handler_receiver();
8964        let guard = connection.add_dynamic_handler(NullHandler).unwrap();
8965
8966        assert!(matches!(
8967            next_dynamic_handler_message(&mut receiver),
8968            Some(DynamicHandlerMessage::AddDynamicHandler(_, _))
8969        ));
8970
8971        drop(connection);
8972        guard.detach();
8973
8974        assert!(
8975            next_dynamic_handler_message(&mut receiver).is_none(),
8976            "detach should retain the handler without retaining a connection sender"
8977        );
8978    }
8979
8980    #[tokio::test]
8981    async fn write_line_flushes_buffered_writers() {
8982        let mut writer =
8983            futures::io::BufWriter::with_capacity(4096, futures::io::Cursor::new(Vec::new()));
8984
8985        write_line(&mut writer, "message".into()).await.unwrap();
8986
8987        assert_eq!(writer.into_inner().into_inner(), b"message\n");
8988    }
8989
8990    #[test]
8991    fn peel_successor_envelopes_returns_plain_messages_unchanged() {
8992        let params = serde_json::json!({ "key": "value" });
8993        let (method, peeled) = peel_successor_envelopes("session/update", &params);
8994        assert_eq!(method, "session/update");
8995        assert_eq!(peeled, &params);
8996    }
8997
8998    #[test]
8999    fn peel_successor_envelopes_unwraps_nested_envelopes() {
9000        let params = serde_json::json!({
9001            "method": "_proxy/successor",
9002            "params": {
9003                "method": "$/cancel_request",
9004                "params": { "requestId": "req-1" }
9005            }
9006        });
9007        let (method, peeled) = peel_successor_envelopes("_proxy/successor", &params);
9008        assert_eq!(method, "$/cancel_request");
9009        assert_eq!(peeled, &serde_json::json!({ "requestId": "req-1" }));
9010    }
9011
9012    #[test]
9013    fn peel_successor_envelopes_leaves_malformed_envelopes_intact() {
9014        // No string `method` field: the envelope cannot be peeled, so the
9015        // message is returned as-is for the handler chain to deal with.
9016        let params = serde_json::json!({ "unexpected": true });
9017        let (method, peeled) = peel_successor_envelopes("_proxy/successor", &params);
9018        assert_eq!(method, "_proxy/successor");
9019        assert_eq!(peeled, &params);
9020    }
9021
9022    mod cancel_request {
9023        use super::super::*;
9024
9025        fn notification(method: &str, params: serde_json::Value) -> UntypedMessage {
9026            UntypedMessage::new(method, params).expect("well-formed JSON")
9027        }
9028
9029        #[test]
9030        fn cancellation_request_id_is_extracted_from_wrapped_notifications() {
9031            let message = notification(
9032                "_proxy/successor",
9033                serde_json::json!({
9034                    "method": "$/cancel_request",
9035                    "params": { "requestId": "req-1" }
9036                }),
9037            );
9038            let request_id = cancellation_request_id_from_message(&message)
9039                .expect("wrapped cancel should parse");
9040            assert_eq!(request_id, Some(RequestId::Str("req-1".into())));
9041        }
9042
9043        #[test]
9044        fn malformed_successor_envelope_is_not_treated_as_cancellation() {
9045            // The envelope cannot be peeled; the message must flow on to the
9046            // handler chain instead of erroring the dispatch.
9047            let message = notification("_proxy/successor", serde_json::json!({ "bogus": true }));
9048            let request_id = cancellation_request_id_from_message(&message)
9049                .expect("malformed envelope should be left to the handler chain");
9050            assert_eq!(request_id, None);
9051        }
9052
9053        #[test]
9054        fn cancel_request_notifications_are_detected_even_when_wrapped() {
9055            let plain = notification("$/cancel_request", serde_json::json!({ "requestId": 1 }));
9056            assert!(is_cancel_request_notification(&plain));
9057
9058            let wrapped = notification(
9059                "_proxy/successor",
9060                serde_json::json!({
9061                    "method": "$/cancel_request",
9062                    "params": { "requestId": 1 }
9063                }),
9064            );
9065            assert!(is_cancel_request_notification(&wrapped));
9066
9067            let other_wrapped = notification(
9068                "_proxy/successor",
9069                serde_json::json!({
9070                    "method": "session/update",
9071                    "params": {}
9072                }),
9073            );
9074            assert!(!is_cancel_request_notification(&other_wrapped));
9075
9076            let malformed_envelope =
9077                notification("_proxy/successor", serde_json::json!({ "bogus": true }));
9078            assert!(!is_cancel_request_notification(&malformed_envelope));
9079        }
9080
9081        #[test]
9082        fn malformed_cancel_request_params_error() {
9083            let message = notification(
9084                "$/cancel_request",
9085                serde_json::json!({ "requestId": { "not": "an id" } }),
9086            );
9087            cancellation_request_id_from_message(&message)
9088                .expect_err("malformed cancel params should error");
9089        }
9090
9091        #[test]
9092        fn registry_marks_and_removes_requests() {
9093            let registry = RequestCancellationRegistry::new();
9094            let id = RequestId::Str("req-1".into());
9095
9096            let responder_cancellation = registry.register(&id);
9097            let marker = responder_cancellation.cancellation();
9098            assert!(!marker.is_cancelled());
9099
9100            assert!(registry.cancel(&id));
9101            assert!(marker.is_cancelled());
9102            assert!(responder_cancellation.cancellation().is_cancelled());
9103
9104            drop(responder_cancellation);
9105            assert!(!registry.cancel(&id), "slot should be removed on drop");
9106        }
9107
9108        #[test]
9109        fn reused_request_id_does_not_cross_wire_cancellation_state() {
9110            let registry = RequestCancellationRegistry::new();
9111            let id = RequestId::Str("dup".into());
9112
9113            // A protocol-violating peer reuses an in-flight request ID.
9114            let first = registry.register(&id);
9115            let first_marker = first.cancellation();
9116            let second = registry.register(&id);
9117            let second_marker = second.cancellation();
9118
9119            // A cancellation targets whichever request currently owns the ID.
9120            assert!(registry.cancel(&id));
9121            assert!(second_marker.is_cancelled());
9122            assert!(
9123                !first_marker.is_cancelled(),
9124                "the stale request must not observe the newer request's cancellation"
9125            );
9126
9127            // The stale responder must hand out detached markers, not the
9128            // newer request's marker.
9129            assert!(!first.cancellation().is_cancelled());
9130
9131            // Dropping the stale responder must not remove the newer
9132            // request's slot.
9133            drop(first);
9134            assert!(registry.cancel(&id), "newer slot should still be present");
9135
9136            drop(second);
9137            assert!(!registry.cancel(&id), "slot should be removed on drop");
9138        }
9139    }
9140}