Skip to main content

agent_client_protocol/
jsonrpc.rs

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