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