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