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