Skip to main content

agent_client_protocol/role/
acp.rs

1use std::{fmt::Debug, future::Future, hash::Hash};
2
3#[cfg(feature = "unstable_protocol_v2")]
4use futures::{StreamExt as _, future};
5#[cfg(feature = "unstable_protocol_v2")]
6use serde::{Serialize, de::DeserializeOwned};
7
8use crate::Raw;
9#[cfg(feature = "unstable_protocol_v2")]
10use crate::RawJsonRpcId;
11use crate::jsonrpc::{Builder, handlers::NullHandler, run::NullRun};
12#[cfg(feature = "unstable_protocol_v2")]
13use crate::jsonrpc::{TransportFrame, raw_is_response_only_shape};
14use crate::role::{HasPeer, RemoteStyle};
15use crate::util::MatchDispatchFrom;
16#[cfg(feature = "unstable_protocol_v2")]
17use crate::v1::schema::InitializeRequest;
18use crate::v1::schema::PROXY_METHOD_NAMES;
19use crate::v1::schema::SessionId;
20#[cfg(feature = "unstable_protocol_v2")]
21use crate::{
22    Channel, RawJsonRpcError, RawJsonRpcMessage, RawJsonRpcParams,
23    RawJsonRpcResponse as RpcResponse,
24};
25#[cfg(feature = "unstable_protocol_v2")]
26use crate::{ConnectTo, DynConnectTo};
27use crate::{ConnectionTo, Dispatch, HandleDispatchFrom, Handled, Role, RoleId};
28#[cfg(feature = "unstable_protocol_v2")]
29use crate::{schema::ProtocolVersion, v2::schema as v2};
30
31#[derive(serde::Deserialize)]
32struct NewSessionResponseEnvelope {
33    #[serde(rename = "sessionId")]
34    session_id: SessionId,
35}
36
37/// The client role - typically an IDE or CLI that controls an agent.
38///
39/// Clients send prompts and receive responses from agents.
40#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
41pub struct Client;
42
43impl Role for Client {
44    type Counterpart = Agent;
45
46    /// Handles the requests and notifications an agent receives from its
47    /// client that no handler claimed.
48    ///
49    /// A v2 agent (`Agent.v2()`) answers an unhandled `tool_call/cancel` or
50    /// `terminal/cancel` with `-32602`: it has no tool call or terminal it can
51    /// cancel. Every other message is left to the generic `-32601` fallback.
52    fn default_handle_dispatch_from(
53        &self,
54        message: Dispatch,
55        connection: ConnectionTo<Client>,
56    ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
57        #[cfg(feature = "unstable_protocol_v2")]
58        let message = match message {
59            Dispatch::Request(request, responder)
60                if connection.protocol_version() == Some(ProtocolVersion::V2) =>
61            {
62                match unhandled_v2_entity_cancel(&request) {
63                    Some(error) => {
64                        return std::future::ready(
65                            responder.respond_with_error(error).map(|()| Handled::Yes),
66                        );
67                    }
68                    None => Dispatch::Request(request, responder),
69                }
70            }
71            message => message,
72        };
73        #[cfg(not(feature = "unstable_protocol_v2"))]
74        drop(connection);
75        std::future::ready(Ok(Handled::No {
76            message,
77            retry: false,
78        }))
79    }
80
81    fn role_id(&self) -> RoleId {
82        RoleId::from_singleton(self)
83    }
84
85    fn counterpart(&self) -> Self::Counterpart {
86        Agent
87    }
88}
89
90impl Client {
91    /// Create a client builder that uses the ACP protocol v1 API.
92    ///
93    /// Callbacks receive a [`v1::ConnectionTo`](crate::v1::ConnectionTo) and
94    /// typed handlers receive v1 request handles. With the
95    /// `unstable_protocol_v2` feature, use `Client::v2` for a protocol v2
96    /// client, or `Client::protocol_connector` to support both.
97    pub fn v1(self) -> crate::v1::Builder<Client> {
98        Builder::new(self).v1_client()
99    }
100
101    /// Create a client builder that requires an ACP protocol v2 agent.
102    ///
103    /// Initialize the connection with
104    /// [`v2::ConnectionTo::send_initialize`](crate::v2::ConnectionTo::send_initialize).
105    /// If the agent selects v1, it resolves to
106    /// [`InitializeOutcome::LowerVersion`](crate::v2::InitializeOutcome::LowerVersion)
107    /// rather than an error, and the connection stays uninitialized: fall back
108    /// to a v1 client implementation if that is acceptable. A plain
109    /// `send_request` of a v2 `InitializeRequest` resolves with an error
110    /// instead. To let the SDK do the fallback, use
111    /// [`Client::protocol_connector`].
112    ///
113    /// Requires the `unstable_protocol_v2` crate feature.
114    #[cfg(feature = "unstable_protocol_v2")]
115    pub fn v2(self) -> crate::v2::Builder<Client> {
116        Builder::new(self).v2_client()
117    }
118
119    /// Create a connector that chooses between configured protocol implementations.
120    ///
121    /// Add implementation factories with [`ClientProtocolConnector::with_v1`]
122    /// and [`ClientProtocolConnector::with_v2`]. The resulting connector starts
123    /// the highest configured protocol implementation. If a v2 implementation
124    /// successfully negotiates v1 and a v1 implementation is configured, the
125    /// connector reuses the connection only when the v1 implementation's
126    /// complete `initialize` parameters match what the agent already saw;
127    /// otherwise it opens a fresh agent connection and restarts with v1.
128    ///
129    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
130    /// stabilizes.
131    #[cfg(feature = "unstable_protocol_v2")]
132    #[must_use]
133    pub fn protocol_connector(self) -> ClientProtocolConnector {
134        ClientProtocolConnector::new()
135    }
136}
137
138/// Client connector that opens an agent connection with a configured protocol implementation.
139///
140/// Use [`Client::protocol_connector`] to start the builder, then add each
141/// supported protocol version independently. Implementations and the agent
142/// connection are provided as factories because fallback from v2 to v1 may
143/// require a fresh connection initialized by the v1 implementation.
144///
145/// The `with_v1` and `with_v2` slots take any `ConnectTo<Agent>` factory; nothing checks that an
146/// implementation speaks the slot's version. Put a `.v1()` implementation in
147/// `with_v1` and a `.v2()` implementation in `with_v2`.
148#[cfg(feature = "unstable_protocol_v2")]
149#[derive(Debug, Default)]
150pub struct ClientProtocolConnector {
151    v1: Option<DynConnectToFactory<Agent>>,
152    v2: Option<DynConnectToFactory<Agent>>,
153}
154
155#[cfg(feature = "unstable_protocol_v2")]
156impl ClientProtocolConnector {
157    /// Create an empty client protocol connector.
158    #[must_use]
159    pub fn new() -> Self {
160        Self::default()
161    }
162
163    /// Return this connector with an ACP v1 implementation factory configured.
164    #[must_use]
165    pub fn with_v1<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
166    where
167        C: ConnectTo<Agent>,
168    {
169        self.v1 = Some(DynConnectToFactory::new(client));
170        self
171    }
172
173    /// Return this connector with an ACP v2 implementation factory configured.
174    #[must_use]
175    pub fn with_v2<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
176    where
177        C: ConnectTo<Agent>,
178    {
179        self.v2 = Some(DynConnectToFactory::new(client));
180        self
181    }
182
183    /// Connect to an agent produced by `agent` using the highest configured
184    /// compatible protocol implementation.
185    pub async fn connect_to<C>(
186        mut self,
187        mut agent: impl FnMut() -> C + Send + 'static,
188    ) -> Result<(), crate::Error>
189    where
190        C: ConnectTo<Client>,
191    {
192        let supported = SupportedClientProtocols {
193            v1: self.v1.is_some(),
194            v2: self.v2.is_some(),
195        };
196        let Some(selected) = supported.highest_configured() else {
197            return Err(crate::Error::invalid_request()
198                .data("client protocol connector has no configured ACP protocol implementations"));
199        };
200
201        match selected {
202            ClientProtocol::V1 => {
203                let client = self
204                    .v1
205                    .as_mut()
206                    .expect("selected protocol is configured")
207                    .create();
208                connect_client_protocol(ClientProtocol::V1, client, agent()).await
209            }
210            ClientProtocol::V2 => {
211                let client = self
212                    .v2
213                    .as_mut()
214                    .expect("selected protocol is configured")
215                    .create();
216                let agent_connection = RunningProtocolPeer::new(agent());
217                let (client, initialize) =
218                    start_client_protocol(ClientProtocol::V2, client).await?;
219                // This normalization is only a probe for the connection-reuse
220                // optimization. A request that cannot be represented in v1
221                // can still be valid v2 traffic and must reach the agent.
222                let v2_initialize_as_v1 = normalize_v2_initialize_params_for_reuse(&initialize);
223                let (client, agent_connection, initialize_response) =
224                    send_initialize_and_receive(client, agent_connection, initialize).await?;
225
226                if initialize_response_negotiated_v1(&initialize_response)
227                    && let Some(v1) = self.v1.as_mut()
228                {
229                    let fallback_client = v1.create();
230                    let (fallback_client, fallback_initialize) =
231                        start_client_protocol(ClientProtocol::V1, fallback_client).await?;
232                    let v1_initialize =
233                        validated_initialize_params::<InitializeRequest>(&fallback_initialize)?;
234
235                    if v2_initialize_as_v1
236                        .as_ref()
237                        .is_ok_and(|v2_initialize| v2_initialize == &v1_initialize)
238                    {
239                        let fallback_response = initialize_response.with_id(
240                            initialize_request_id(&fallback_initialize)
241                                .expect("validated initialize request has an id"),
242                        );
243                        // The v2 implementation will never receive its initialize response once
244                        // the matching v1 implementation takes over this connection. Drop its
245                        // future and channels before running the v1 session so any resources it
246                        // owns are released promptly.
247                        drop(client);
248                        fallback_client.send(fallback_response)?;
249                        return pipe_protocol_peers_until_done(fallback_client, agent_connection)
250                            .await;
251                    }
252
253                    // Neither probe can continue on the replacement connection. Release both
254                    // client implementations and the original agent connection before starting
255                    // the real v1 session so they cannot retain resources for its lifetime.
256                    drop((
257                        client,
258                        fallback_client,
259                        agent_connection,
260                        initialize_response,
261                    ));
262                    return connect_client_protocol(ClientProtocol::V1, v1.create(), agent()).await;
263                }
264
265                client.send(initialize_response.into_message())?;
266                pipe_protocol_peers_until_done(client, agent_connection).await
267            }
268        }
269    }
270}
271
272#[cfg(feature = "unstable_protocol_v2")]
273struct DynConnectToFactory<R: Role> {
274    inner: Box<dyn FnMut() -> DynConnectTo<R> + Send>,
275}
276
277#[cfg(feature = "unstable_protocol_v2")]
278impl<R: Role> DynConnectToFactory<R> {
279    fn new<C>(mut factory: impl FnMut() -> C + Send + 'static) -> Self
280    where
281        C: ConnectTo<R>,
282    {
283        Self {
284            inner: Box::new(move || DynConnectTo::new(factory())),
285        }
286    }
287
288    fn create(&mut self) -> DynConnectTo<R> {
289        (self.inner)()
290    }
291}
292
293#[cfg(feature = "unstable_protocol_v2")]
294impl<R: Role> Debug for DynConnectToFactory<R> {
295    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296        f.debug_struct("DynConnectToFactory")
297            .finish_non_exhaustive()
298    }
299}
300
301impl HasPeer<Client> for Client {
302    fn remote_style(&self, _peer: Client) -> RemoteStyle {
303        RemoteStyle::Counterpart
304    }
305}
306
307/// The agent role - typically an LLM that responds to prompts.
308///
309/// Agents receive prompts from clients and respond with answers,
310/// potentially invoking tools along the way.
311#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
312pub struct Agent;
313
314impl Role for Agent {
315    type Counterpart = Client;
316
317    fn role_id(&self) -> RoleId {
318        RoleId::from_singleton(self)
319    }
320
321    fn counterpart(&self) -> Self::Counterpart {
322        Client
323    }
324
325    async fn default_handle_dispatch_from(
326        &self,
327        message: Dispatch,
328        connection: ConnectionTo<Agent>,
329    ) -> Result<Handled<Dispatch>, crate::Error> {
330        MatchDispatchFrom::new(message, &connection)
331            .if_dispatch_from(Agent, async |message: Dispatch| {
332                // Stable v1 session helpers install a dynamic handler after
333                // `session/new`. Retry session messages to close the race
334                // between the response and that registration. An unguarded
335                // connection (`Builder::new`) can use the v1 helpers through
336                // `into_protocol::<V1>()`, so it retries too.
337                //
338                // The incoming actor bounds this retry: it parks at most
339                // `MAX_PARKED_MESSAGES`, and parks a request only while a
340                // deactivated session handler awaits removal or replacement.
341                // Any other unhandled request is answered with `-32601`.
342                //
343                // Every other version (v2 and later) uses typed handlers
344                // installed before the connection starts. Retrying an
345                // unhandled message there would retain it forever because no
346                // per-session dynamic handler is expected.
347                let retry = message.has_session_id()
348                    && connection
349                        .protocol_version()
350                        .is_none_or(|version| version == crate::schema::ProtocolVersion::V1);
351                Ok(Handled::No { message, retry })
352            })
353            .await
354            .done()
355    }
356}
357
358/// The error for an unhandled v2 `tool_call/cancel` or `terminal/cancel`
359/// request, or `None` for every other request.
360///
361/// Neither method has an initialize-time capability. An Agent cancels only a
362/// tool call or terminal that advertises `capabilities.cancel`, and the spec
363/// answers a request for an unknown or non-cancellable one with `-32602`
364/// (Invalid params), not `-32601` (Method not found). The error's `data`
365/// holds the `toolCallId` or `terminalId` from the request when it is a
366/// string.
367#[cfg(feature = "unstable_protocol_v2")]
368fn unhandled_v2_entity_cancel(request: &crate::UntypedMessage) -> Option<crate::Error> {
369    let method = request.method();
370    let (key, label) = if method == v2::AGENT_METHOD_NAMES.tool_call_cancel {
371        ("toolCallId", "tool call")
372    } else if method == v2::AGENT_METHOD_NAMES.terminal_cancel {
373        ("terminalId", "terminal")
374    } else {
375        return None;
376    };
377    let error = crate::Error::new(
378        crate::ErrorCode::INVALID_PARAMS,
379        format!("the {label} is unknown or not cancellable"),
380    );
381    Some(
382        match request
383            .params()
384            .get(key)
385            .and_then(serde_json::Value::as_str)
386        {
387            Some(id) => error.data(serde_json::json!({ key: id })),
388            None => error,
389        },
390    )
391}
392
393impl Agent {
394    /// Create an agent builder that uses the ACP protocol v1 API.
395    ///
396    /// Callbacks receive a [`v1::ConnectionTo`](crate::v1::ConnectionTo) and
397    /// typed handlers receive v1 request handles. With the
398    /// `unstable_protocol_v2` feature, use `Agent::v2` for a protocol v2
399    /// agent, or `Agent::protocol_router` to serve both.
400    pub fn v1(self) -> crate::v1::Builder<Agent> {
401        Builder::new(self).v1_agent()
402    }
403
404    /// Create an agent builder that uses the ACP protocol v2 API.
405    ///
406    /// This builder selects protocol v2 for clients requesting v2 or a newer
407    /// version. A request for exactly v2 must be a valid v2 initialize
408    /// request. A request for a newer version is accepted with what parses
409    /// under v2: unparsable capabilities and `_meta` are dropped, and a missing
410    /// or malformed `info` is replaced by an implementation named `unknown`
411    /// with version `unknown`. Use [`Self::protocol_router`] with a v1
412    /// implementation for v1 clients.
413    ///
414    /// The client may repeat `initialize` after the previous one completed
415    /// and before it sends any other request. Each repeat is negotiated like
416    /// the first one and dispatched to the initialize handler, and the latest
417    /// successful one is authoritative. A repeat requesting a version below v2
418    /// gets `-32022` (Unsupported protocol version), as a first request would.
419    /// A repeat after any other request or while an initialize is in flight
420    /// is rejected with `-32600`. A repeat that fails keeps the previous
421    /// initialization in place. Requests with a `null` id are rejected with
422    /// `-32600`.
423    ///
424    /// An initialized agent without a handler for `tool_call/cancel` or
425    /// `terminal/cancel` answers them with `-32602` (Invalid params), not
426    /// `-32601` (Method not found): the spec answers a request for an unknown
427    /// or non-cancellable tool call or terminal with `-32602`, and an agent
428    /// that handles neither method cancels nothing. A typed handler or a
429    /// catch-all handler for these requests takes precedence.
430    ///
431    /// Requires the `unstable_protocol_v2` crate feature.
432    #[cfg(feature = "unstable_protocol_v2")]
433    pub fn v2(self) -> crate::v2::Builder<Agent> {
434        Builder::new(self).v2_agent()
435    }
436
437    /// Create a router that chooses between configured protocol implementations.
438    ///
439    /// Add implementations with [`AgentProtocolRouter::with_v1`] and
440    /// [`AgentProtocolRouter::with_v2`].
441    /// The resulting router reads the initial
442    /// `initialize` request and selects a version-specific implementation. A
443    /// request for v2 or newer selects the v2 implementation. With a v1
444    /// implementation configured, everything else, including a request below
445    /// v1 and a missing or malformed first request, is handed unchanged to the
446    /// v1 implementation, so a v1 client sees exactly the behavior of the v1
447    /// implementation alone (including v1's fallback-to-latest rule). A
448    /// v2-only router rejects a request below v2 with `-32022`. The router
449    /// does not convert traffic after initialization.
450    ///
451    /// The selected implementation owns the connection. A repeated
452    /// `initialize` is forwarded to it and is never re-routed, so it is
453    /// negotiated like a first request against the selected version alone. A
454    /// v2 implementation answers a repeat requesting v1 with `-32022` listing
455    /// only v2, since the connection cannot switch to the v1 API, and the v2
456    /// initialization stays in effect. A v1 implementation selects v1 for a
457    /// repeat requesting a newer version. A first `initialize` with a
458    /// `null` id that would select v2 is rejected with `-32600`, like any
459    /// other invalid first request.
460    ///
461    /// A rejected first message, including a version mismatch (`-32022`),
462    /// leaves the connection uninitialized and open: the client may send
463    /// `initialize` again, for example after correcting its parameters.
464    /// Notifications before initialization are ignored.
465    ///
466    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
467    /// stabilizes.
468    #[cfg(feature = "unstable_protocol_v2")]
469    #[must_use]
470    pub fn protocol_router(self) -> AgentProtocolRouter {
471        AgentProtocolRouter::new()
472    }
473}
474
475/// Agent component that routes each connection to a configured protocol implementation.
476///
477/// Use [`Agent::protocol_router`] to start the builder, then add each supported
478/// protocol version independently. The selected implementation owns the
479/// connection after the initial `initialize` negotiation.
480///
481/// The `with_v1` and `with_v2` slots take any `ConnectTo<Client>`; nothing checks that an
482/// implementation speaks the slot's version. Put a `.v1()` implementation in
483/// `with_v1` and a `.v2()` implementation in `with_v2`.
484#[cfg(feature = "unstable_protocol_v2")]
485#[derive(Debug, Default)]
486pub struct AgentProtocolRouter {
487    v1: Option<DynConnectTo<Client>>,
488    v2: Option<DynConnectTo<Client>>,
489}
490
491#[cfg(feature = "unstable_protocol_v2")]
492impl AgentProtocolRouter {
493    /// Create an empty agent protocol router.
494    #[must_use]
495    pub fn new() -> Self {
496        Self::default()
497    }
498
499    /// Return this router with an ACP v1 implementation configured.
500    #[must_use]
501    pub fn with_v1(mut self, agent: impl ConnectTo<Client>) -> Self {
502        self.v1 = Some(DynConnectTo::new(agent));
503        self
504    }
505
506    /// Return this router with an ACP v2 implementation configured.
507    #[must_use]
508    pub fn with_v2(mut self, agent: impl ConnectTo<Client>) -> Self {
509        self.v2 = Some(DynConnectTo::new(agent));
510        self
511    }
512}
513
514#[cfg(feature = "unstable_protocol_v2")]
515impl ConnectTo<Client> for AgentProtocolRouter {
516    async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), crate::Error> {
517        let supported = SupportedProtocols {
518            v1: self.v1.is_some(),
519            v2: self.v2.is_some(),
520        };
521        let client = RunningProtocolPeer::new(client);
522        let Some((first_frame, client, selected)) =
523            select_protocol_peer(client, "initialize", supported).await?
524        else {
525            return Ok(());
526        };
527        let agent = selected
528            .take_agent(self)
529            .expect("the router only selects a configured implementation");
530
531        let agent = RunningProtocolPeer::new(agent);
532        agent.send_frame(first_frame)?;
533        pipe_protocol_peers_until_done(client, agent).await
534    }
535}
536
537/// Read frames from `peer` until an `initialize_method` request selects a
538/// configured protocol version, and return that request's frame, the peer and
539/// the selection. Returns `None` if the peer closes first.
540///
541/// A failed initialize leaves the connection uninitialized, and the peer may
542/// send `initialize_method` again. Each rejected frame is answered and the
543/// loop keeps reading, so [`AgentProtocolRouter`] and [`ProxyProtocolRouter`]
544/// treat rejections the same way.
545#[cfg(feature = "unstable_protocol_v2")]
546async fn select_protocol_peer(
547    mut peer: RunningProtocolPeer,
548    initialize_method: &str,
549    supported: SupportedProtocols,
550) -> Result<Option<(TransportFrame, RunningProtocolPeer, SelectedProtocol)>, crate::Error> {
551    loop {
552        let Some((mut frame, next_peer)) = peer.next_frame().await? else {
553            return Ok(None);
554        };
555        peer = next_peer;
556        let message = match initialize_message_mut(&mut frame) {
557            Ok(Some(message)) => message,
558            Ok(None) => continue,
559            // A malformed frame, such as invalid JSON or a batch, gets the
560            // frame's own error with `id: null`, exactly as a v1 connection
561            // answers it. It selects no version, so the peer can still
562            // initialize either one.
563            Err(error) => {
564                send_initialize_error(&peer.tx, &frame, error)?;
565                continue;
566            }
567        };
568        let selected = match select_initialize_protocol(message, initialize_method, supported) {
569            Ok(selected) => selected,
570            // A v1 implementation answers any other message that does not
571            // select v2 itself, exactly as it would without the router.
572            Err(_)
573                if supported.v1 && !requests_protocol_v2(message, initialize_method, supported) =>
574            {
575                SelectedProtocol::V1
576            }
577            Err(error) => {
578                send_initialize_error(&peer.tx, &frame, error)?;
579                continue;
580            }
581        };
582        if !selected.is_configured(supported) {
583            send_initialize_error(&peer.tx, &frame, selected.unsupported_error(supported))?;
584            continue;
585        }
586        return Ok(Some((frame, peer, selected)));
587    }
588}
589
590#[cfg(feature = "unstable_protocol_v2")]
591#[derive(Debug, Clone, Copy, PartialEq, Eq)]
592enum SelectedProtocol {
593    V1,
594    V2,
595}
596
597#[cfg(feature = "unstable_protocol_v2")]
598impl SelectedProtocol {
599    fn take_agent(self, agent: AgentProtocolRouter) -> Option<DynConnectTo<Client>> {
600        match self {
601            Self::V1 => agent.v1,
602            Self::V2 => agent.v2,
603        }
604    }
605
606    fn is_configured(self, supported: SupportedProtocols) -> bool {
607        match self {
608            Self::V1 => supported.v1,
609            Self::V2 => supported.v2,
610        }
611    }
612
613    fn version(self) -> ProtocolVersion {
614        match self {
615            Self::V1 => ProtocolVersion::V1,
616            Self::V2 => ProtocolVersion::V2,
617        }
618    }
619
620    fn name(self) -> &'static str {
621        match self {
622            Self::V1 => "1",
623            Self::V2 => "2",
624        }
625    }
626
627    fn unsupported_error(self, supported: SupportedProtocols) -> crate::Error {
628        crate::Error::invalid_request().data(format!(
629            "ACP protocol version {} is not configured; this endpoint supports {}",
630            self.name(),
631            supported.description()
632        ))
633    }
634}
635
636#[cfg(feature = "unstable_protocol_v2")]
637#[derive(Debug, Clone, Copy, PartialEq, Eq)]
638struct SupportedProtocols {
639    v1: bool,
640    v2: bool,
641}
642
643#[cfg(feature = "unstable_protocol_v2")]
644impl SupportedProtocols {
645    fn select(self, requested: ProtocolVersion) -> Option<SelectedProtocol> {
646        if self.v2 && requested >= ProtocolVersion::V2 {
647            return Some(SelectedProtocol::V2);
648        }
649        // Any request that does not select v2 keeps v1's fallback-to-latest
650        // rule, so a v1 implementation behaves as it does without the router.
651        if self.v1 {
652            return Some(SelectedProtocol::V1);
653        }
654
655        None
656    }
657
658    fn versions(self) -> Vec<ProtocolVersion> {
659        [
660            self.v1.then_some(ProtocolVersion::V1),
661            self.v2.then_some(ProtocolVersion::V2),
662        ]
663        .into_iter()
664        .flatten()
665        .collect()
666    }
667
668    fn description(self) -> String {
669        match (self.v1, self.v2) {
670            (true, true) => "ACP protocol versions 1 and 2".into(),
671            (true, false) => "ACP protocol version 1".into(),
672            (false, true) => "ACP protocol version 2".into(),
673            (false, false) => "no ACP protocol versions".into(),
674        }
675    }
676}
677
678/// Select the configured protocol for the first `initialize_method` request
679/// (`initialize` for an agent, `proxy/initialize` for a proxy) and rewrite its
680/// params for the selected version.
681///
682/// Agents and proxies negotiate the same way: the highest configured version
683/// at or below the requested one, v1's fallback-to-latest rule below v1, and a
684/// request for a newer version keeps what parses under the selected one.
685#[cfg(feature = "unstable_protocol_v2")]
686fn select_initialize_protocol(
687    message: &mut RawJsonRpcMessage,
688    initialize_method: &str,
689    supported: SupportedProtocols,
690) -> Result<SelectedProtocol, crate::Error> {
691    let RawJsonRpcMessage::Request(request) = message else {
692        return Err(crate::Error::invalid_request().data(format!(
693            "first ACP message must be a `{initialize_method}` request"
694        )));
695    };
696
697    if request.method.as_ref() != initialize_method {
698        return Err(crate::Error::invalid_request()
699            .data(format!("first ACP request must be `{initialize_method}`")));
700    }
701
702    let Some(RawJsonRpcParams::Object(params)) = &mut request.params else {
703        return Err(invalid_initialize_protocol_version());
704    };
705    let Some(protocol_version) = params.get("protocolVersion") else {
706        return Err(invalid_initialize_protocol_version());
707    };
708
709    let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
710        .map_err(|_| invalid_initialize_protocol_version())?;
711    let selected = select_configured_protocol(requested, supported)?;
712    // V2 request ids must be strings or integers; v1 still accepts null.
713    if selected == SelectedProtocol::V2 && request.id == RawJsonRpcId::Null {
714        return Err(crate::jsonrpc::raw_id::null_v2_request_id());
715    }
716    rewrite_initialize_params(params, requested, selected)?;
717
718    Ok(selected)
719}
720
721/// The requested protocol version of an `initialize` (or, for a proxy,
722/// `proxy/initialize`) request, if it has a valid one.
723#[cfg(feature = "unstable_protocol_v2")]
724fn requested_initialize_version(
725    message: &RawJsonRpcMessage,
726    initialize_method: &str,
727) -> Option<ProtocolVersion> {
728    let RawJsonRpcMessage::Request(request) = message else {
729        return None;
730    };
731    if request.method.as_ref() != initialize_method {
732        return None;
733    }
734    let Some(RawJsonRpcParams::Object(params)) = &request.params else {
735        return None;
736    };
737    serde_json::from_value(params.get("protocolVersion")?.clone()).ok()
738}
739
740/// Whether `message` is an `initialize_method` request that selects a
741/// configured v2 implementation. Everything else is answered by a configured
742/// v1 implementation.
743#[cfg(feature = "unstable_protocol_v2")]
744fn requests_protocol_v2(
745    message: &RawJsonRpcMessage,
746    initialize_method: &str,
747    supported: SupportedProtocols,
748) -> bool {
749    supported.v2
750        && requested_initialize_version(message, initialize_method)
751            .is_some_and(|requested| requested >= ProtocolVersion::V2)
752}
753
754#[cfg(feature = "unstable_protocol_v2")]
755fn initialize_request_params(
756    message: &RawJsonRpcMessage,
757) -> Result<&serde_json::Map<String, serde_json::Value>, crate::Error> {
758    let RawJsonRpcMessage::Request(request) = message else {
759        return Err(
760            crate::Error::invalid_request().data("first ACP message must be an initialize request")
761        );
762    };
763
764    if request.method.as_ref() != "initialize" {
765        return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
766    }
767
768    let Some(RawJsonRpcParams::Object(params)) = &request.params else {
769        return Err(invalid_initialize_protocol_version());
770    };
771    if !params.contains_key("protocolVersion") {
772        return Err(invalid_initialize_protocol_version());
773    }
774    Ok(params)
775}
776
777#[cfg(feature = "unstable_protocol_v2")]
778fn validated_initialize_params<T: DeserializeOwned>(
779    message: &RawJsonRpcMessage,
780) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
781    let params = initialize_request_params(message)?;
782    parse_initialize_params::<T>(params)?;
783    Ok(params.clone())
784}
785
786#[cfg(feature = "unstable_protocol_v2")]
787fn normalize_v2_initialize_params_for_reuse(
788    message: &RawJsonRpcMessage,
789) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
790    let params = initialize_request_params(message)?;
791    let requested = params
792        .get("protocolVersion")
793        .cloned()
794        .ok_or_else(invalid_initialize_protocol_version)
795        .and_then(|version| {
796            serde_json::from_value::<ProtocolVersion>(version)
797                .map_err(|_| invalid_initialize_protocol_version())
798        })?;
799    if requested == ProtocolVersion::V1 {
800        parse_initialize_params::<InitializeRequest>(params)?;
801        return Ok(params.clone());
802    }
803    normalize_v2_initialize_params_for_v1(params, true)
804}
805
806#[cfg(feature = "unstable_protocol_v2")]
807fn rewrite_initialize_params(
808    params: &mut serde_json::Map<String, serde_json::Value>,
809    requested: ProtocolVersion,
810    selected: SelectedProtocol,
811) -> Result<(), crate::Error> {
812    // Validate exact-version initialization without replacing its raw
813    // parameters. Reserializing through the SDK's pinned schema would discard
814    // fields added by newer compatible peers.
815    if requested == selected.version() {
816        match selected {
817            SelectedProtocol::V1 => {
818                parse_initialize_params::<InitializeRequest>(params)?;
819            }
820            SelectedProtocol::V2 => {
821                parse_initialize_params::<v2::InitializeRequest>(params)?;
822            }
823        }
824        return Ok(());
825    }
826
827    match selected {
828        SelectedProtocol::V1 => {
829            if requested > ProtocolVersion::V1 {
830                *params = normalize_v2_initialize_params_for_v1(params, false)?;
831            } else {
832                let mut initialize = parse_initialize_params::<InitializeRequest>(params)?;
833                initialize.protocol_version = ProtocolVersion::V1;
834                *params = serialize_initialize_params(initialize)?;
835            }
836            Ok(())
837        }
838        SelectedProtocol::V2 => {
839            // The request was written for a newer version. Keep what parses
840            // under v2 instead of rejecting it, as a standalone v2 agent does.
841            *params = crate::jsonrpc::normalize_newer_v2_initialize_request(params)?;
842            Ok(())
843        }
844    }
845}
846
847#[cfg(feature = "unstable_protocol_v2")]
848fn normalize_v2_initialize_params_for_v1(
849    params: &serde_json::Map<String, serde_json::Value>,
850    require_lossless: bool,
851) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
852    if !require_lossless {
853        return crate::jsonrpc::normalize_v1_initialize_request(&serde_json::Value::Object(
854            params.clone(),
855        ));
856    }
857    // The connection-reuse probe additionally requires lossless v2
858    // canonicalization. Normal v1 routing above uses the same bootstrap as a
859    // standalone v1 agent, even when the v2 schema is not compiled in.
860    let initialize = parse_initialize_params::<v2::InitializeRequest>(params)?;
861    let mut target = serialize_initialize_params(initialize)?;
862    if require_lossless && target != *params {
863        return Err(invalid_initialize_params(
864            "v2 initialize parameters are not losslessly representable in v1",
865        ));
866    }
867
868    target.insert(
869        "protocolVersion".into(),
870        serde_json::to_value(ProtocolVersion::V1).map_err(crate::Error::into_internal_error)?,
871    );
872    let info = target
873        .remove("info")
874        .ok_or_else(|| invalid_initialize_params("v2 InitializeRequest.info is required"))?;
875    target.insert("clientInfo".into(), info);
876    let capabilities = target
877        .remove("capabilities")
878        .and_then(|capabilities| capabilities.as_object().cloned())
879        .ok_or_else(|| {
880            crate::util::internal_error("v2 initialize capabilities did not serialize as an object")
881        })?;
882    let mut capabilities = capabilities;
883    if let Some(auth) = capabilities
884        .get_mut("auth")
885        .and_then(serde_json::Value::as_object_mut)
886    {
887        let terminal = auth.remove("terminal");
888        if require_lossless
889            && terminal
890                .as_ref()
891                .and_then(serde_json::Value::as_object)
892                .is_some_and(|terminal| terminal.contains_key("_meta"))
893        {
894            return Err(invalid_initialize_params(
895                "v2 terminal authentication metadata is not representable in v1",
896            ));
897        }
898        auth.insert("terminal".into(), terminal.is_some().into());
899    }
900    capabilities.insert(
901        "session".into(),
902        serde_json::json!({ "configOptions": { "boolean": {} } }),
903    );
904    target.insert("clientCapabilities".into(), capabilities.into());
905
906    let initialize = parse_initialize_params::<InitializeRequest>(&target)?;
907    let normalized = serialize_initialize_params(initialize)?;
908    if require_lossless && !json_object_contains(&normalized, &target) {
909        return Err(invalid_initialize_params(
910            "v2 initialize parameters are not losslessly representable in v1",
911        ));
912    }
913    Ok(normalized)
914}
915
916#[cfg(all(test, feature = "unstable_protocol_v2"))]
917mod initialize_normalization_tests {
918    use super::*;
919
920    fn v2_initialize_params() -> serde_json::Map<String, serde_json::Value> {
921        let value = serde_json::to_value(v2::InitializeRequest::new(
922            ProtocolVersion::V2,
923            v2::Implementation::new("test-client", "1.0.0"),
924        ))
925        .expect("serialize v2 initialize request");
926        value
927            .as_object()
928            .expect("initialize params serialize as an object")
929            .clone()
930    }
931
932    #[test]
933    fn v2_tolerant_fields_are_canonicalized_before_v1_normalization() {
934        let mut params = v2_initialize_params();
935        params.insert(
936            "capabilities".into(),
937            serde_json::Value::String("malformed".into()),
938        );
939        params.insert(
940            "_meta".into(),
941            serde_json::Value::String("malformed".into()),
942        );
943
944        let normalized = normalize_v2_initialize_params_for_v1(&params, false)
945            .expect("tolerant v2 fields should normalize through their defaults");
946        let normalized = serde_json::Value::Object(normalized);
947
948        assert!(normalized.get("_meta").is_none());
949        assert_eq!(
950            normalized.pointer("/clientCapabilities/session/configOptions/boolean"),
951            Some(&serde_json::json!({}))
952        );
953    }
954
955    #[test]
956    fn noncanonical_v2_fields_disable_reuse_but_not_v1_routing() {
957        let mut params = v2_initialize_params();
958        params
959            .get_mut("info")
960            .and_then(serde_json::Value::as_object_mut)
961            .expect("v2 initialize info is an object")
962            .insert("buildCommit".into(), serde_json::json!("abc123"));
963
964        normalize_v2_initialize_params_for_v1(&params, false)
965            .expect("v1 routing may ignore parameters unavailable in v1");
966        normalize_v2_initialize_params_for_v1(&params, true)
967            .expect_err("connection reuse requires lossless normalization");
968    }
969
970    #[test]
971    fn v1_reuse_probe_preserves_raw_initialize_params() {
972        let mut params = v2_initialize_params();
973        params.insert(
974            "protocolVersion".into(),
975            serde_json::json!(ProtocolVersion::V1),
976        );
977        let message = RawJsonRpcMessage::request(
978            "initialize".into(),
979            serde_json::Value::Object(params.clone()),
980            RawJsonRpcId::Number(1),
981        )
982        .expect("build initialize request");
983
984        let normalized = normalize_v2_initialize_params_for_reuse(&message)
985            .expect("v1-shaped initialize request should be valid");
986
987        assert_eq!(normalized, params);
988    }
989
990    #[test]
991    fn null_v2_terminal_marker_meta_is_omitted_before_v1_normalization() {
992        let mut params = v2_initialize_params();
993        params.insert(
994            "capabilities".into(),
995            serde_json::json!({
996                "auth": {
997                    "terminal": { "_meta": null }
998                }
999            }),
1000        );
1001
1002        let normalized = normalize_v2_initialize_params_for_v1(&params, false)
1003            .expect("null marker metadata is equivalent to omission");
1004        let normalized = serde_json::Value::Object(normalized);
1005
1006        assert_eq!(
1007            normalized.pointer("/clientCapabilities/auth/terminal"),
1008            Some(&serde_json::Value::Bool(true))
1009        );
1010    }
1011
1012    #[test]
1013    fn terminal_marker_metadata_disables_reuse_but_not_v1_routing() {
1014        let mut params = v2_initialize_params();
1015        params.insert(
1016            "capabilities".into(),
1017            serde_json::json!({
1018                "auth": {
1019                    "terminal": {
1020                        "_meta": { "source": "test" }
1021                    }
1022                }
1023            }),
1024        );
1025
1026        normalize_v2_initialize_params_for_v1(&params, false)
1027            .expect("v1 routing may discard terminal marker metadata");
1028        normalize_v2_initialize_params_for_v1(&params, true)
1029            .expect_err("connection reuse must preserve terminal marker metadata");
1030    }
1031}
1032
1033#[cfg(feature = "unstable_protocol_v2")]
1034fn parse_initialize_params<T: DeserializeOwned>(
1035    params: &serde_json::Map<String, serde_json::Value>,
1036) -> Result<T, crate::Error> {
1037    serde_json::from_value(serde_json::Value::Object(params.clone()))
1038        .map_err(invalid_initialize_params)
1039}
1040
1041#[cfg(feature = "unstable_protocol_v2")]
1042fn serialize_initialize_params(
1043    initialize: impl Serialize,
1044) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
1045    let value = serde_json::to_value(initialize).map_err(crate::Error::into_internal_error)?;
1046    let serde_json::Value::Object(object) = value else {
1047        return Err(crate::util::internal_error(
1048            "initialize params did not serialize to an object",
1049        ));
1050    };
1051    Ok(object)
1052}
1053
1054#[cfg(feature = "unstable_protocol_v2")]
1055fn json_object_contains(
1056    actual: &serde_json::Map<String, serde_json::Value>,
1057    expected: &serde_json::Map<String, serde_json::Value>,
1058) -> bool {
1059    fn contains(actual: &serde_json::Value, expected: &serde_json::Value) -> bool {
1060        match (actual, expected) {
1061            (serde_json::Value::Object(actual), serde_json::Value::Object(expected)) => expected
1062                .iter()
1063                .all(|(key, value)| actual.get(key).is_some_and(|item| contains(item, value))),
1064            _ => actual == expected,
1065        }
1066    }
1067
1068    expected
1069        .iter()
1070        .all(|(key, value)| actual.get(key).is_some_and(|item| contains(item, value)))
1071}
1072
1073#[cfg(feature = "unstable_protocol_v2")]
1074fn select_configured_protocol(
1075    requested: ProtocolVersion,
1076    supported: SupportedProtocols,
1077) -> Result<SelectedProtocol, crate::Error> {
1078    supported.select(requested).ok_or_else(|| {
1079        crate::v2::schema::Error::unsupported_protocol_version(requested, &supported.versions())
1080            .into()
1081    })
1082}
1083
1084#[cfg(feature = "unstable_protocol_v2")]
1085fn invalid_initialize_protocol_version() -> crate::Error {
1086    crate::Error::invalid_params()
1087        .data("initialize.protocolVersion must be a valid ACP protocol version")
1088}
1089
1090#[cfg(feature = "unstable_protocol_v2")]
1091fn invalid_initialize_params(error: impl ToString) -> crate::Error {
1092    crate::Error::invalid_params().data(format!("invalid initialize params: {}", error.to_string()))
1093}
1094
1095#[cfg(feature = "unstable_protocol_v2")]
1096fn send_initialize_error(
1097    tx: &futures::channel::mpsc::UnboundedSender<TransportFrame>,
1098    frame: &TransportFrame,
1099    error: crate::Error,
1100) -> Result<(), crate::Error> {
1101    fn response_for_message(
1102        entry: &RawJsonRpcMessage,
1103        initialize_error: &crate::Error,
1104    ) -> Option<RawJsonRpcMessage> {
1105        match entry {
1106            RawJsonRpcMessage::Request(request) => Some(RawJsonRpcMessage::response(
1107                request.id.clone(),
1108                Err(initialize_error.clone()),
1109            )),
1110            RawJsonRpcMessage::Notification(_) | RawJsonRpcMessage::Response(_) => None,
1111        }
1112    }
1113
1114    let response = match frame {
1115        TransportFrame::Single(entry) => {
1116            let Some(response) = response_for_message(entry, &error) else {
1117                return Ok(());
1118            };
1119            TransportFrame::Single(response)
1120        }
1121        TransportFrame::Malformed { raw, error } if !raw_is_response_only_shape(raw) => {
1122            TransportFrame::Single(RawJsonRpcMessage::response(
1123                RawJsonRpcId::Null,
1124                Err(error.clone()),
1125            ))
1126        }
1127        TransportFrame::Malformed { .. } => return Ok(()),
1128    };
1129
1130    tx.unbounded_send(response)
1131        .map_err(crate::util::internal_error)
1132}
1133
1134#[cfg(feature = "unstable_protocol_v2")]
1135struct RunningProtocolPeer {
1136    rx: futures::channel::mpsc::UnboundedReceiver<TransportFrame>,
1137    tx: futures::channel::mpsc::UnboundedSender<TransportFrame>,
1138    driver: ProtocolPeerDriver,
1139}
1140
1141#[cfg(feature = "unstable_protocol_v2")]
1142enum ProtocolPeerDriver {
1143    Passive,
1144    Active(crate::ConnectionDriver),
1145    Completed {
1146        finish: Option<crate::component::FinishControl>,
1147    },
1148}
1149
1150#[cfg(feature = "unstable_protocol_v2")]
1151impl ProtocolPeerDriver {
1152    fn into_driver(self) -> Option<crate::ConnectionDriver> {
1153        match self {
1154            Self::Passive => None,
1155            Self::Active(driver) => Some(driver),
1156            // Conversion happens only when handing the peer to its final
1157            // bridge, never while reading its remaining queued frames. This
1158            // records actual owned completion, not a passive ready sentinel.
1159            Self::Completed { finish } => Some(match finish {
1160                Some(mut finish) => {
1161                    crate::ConnectionDriver::with_finish(future::ready(Ok(())), move || {
1162                        finish.request();
1163                    })
1164                }
1165                None => crate::ConnectionDriver::new(future::ready(Ok(()))),
1166            }),
1167        }
1168    }
1169}
1170
1171#[cfg(feature = "unstable_protocol_v2")]
1172impl RunningProtocolPeer {
1173    fn new<R: Role>(component: impl ConnectTo<R>) -> Self {
1174        let (Channel { rx, tx }, future) = component.into_channel_and_future();
1175        let driver = match future {
1176            None => ProtocolPeerDriver::Passive,
1177            Some(future) => ProtocolPeerDriver::Active(future),
1178        };
1179        Self { rx, tx, driver }
1180    }
1181
1182    async fn next_frame(self) -> Result<Option<(TransportFrame, Self)>, crate::Error> {
1183        let Self { mut rx, tx, driver } = self;
1184        let ProtocolPeerDriver::Active(mut future) = driver else {
1185            return Ok(rx
1186                .next()
1187                .await
1188                .map(|frame| (frame, Self { rx, tx, driver })));
1189        };
1190
1191        // Poll the owned driver first: a ready error must not be hidden by
1192        // an equally ready frame or clean channel EOF.
1193        match future::select(&mut future, Box::pin(rx.next())).await {
1194            future::Either::Right((Some(frame), _)) => Ok(Some((
1195                frame,
1196                Self {
1197                    rx,
1198                    tx,
1199                    driver: ProtocolPeerDriver::Active(future),
1200                },
1201            ))),
1202            future::Either::Right((None, _)) => {
1203                drop(tx);
1204                future.await?;
1205                Ok(None)
1206            }
1207            future::Either::Left((result, next_message)) => {
1208                result?;
1209                drop(next_message);
1210                // No more output may be accepted from an owned endpoint once
1211                // its driver completes, even if a sender escaped the component.
1212                rx.close();
1213                let Some(frame) = rx.next().await else {
1214                    return Ok(None);
1215                };
1216                Ok(Some((
1217                    frame,
1218                    Self {
1219                        rx,
1220                        tx,
1221                        driver: ProtocolPeerDriver::Completed {
1222                            finish: future.take_finish(),
1223                        },
1224                    },
1225                )))
1226            }
1227        }
1228    }
1229
1230    async fn next_message(self) -> Result<Option<(RawJsonRpcMessage, Self)>, crate::Error> {
1231        let Some((frame, peer)) = self.next_frame().await? else {
1232            return Ok(None);
1233        };
1234        Ok(Some((initialize_message(frame)?, peer)))
1235    }
1236
1237    fn send(&self, message: RawJsonRpcMessage) -> Result<(), crate::Error> {
1238        self.send_frame(TransportFrame::Single(message))
1239    }
1240
1241    fn send_frame(&self, frame: TransportFrame) -> Result<(), crate::Error> {
1242        self.tx
1243            .unbounded_send(frame)
1244            .map_err(crate::util::internal_error)
1245    }
1246}
1247
1248#[cfg(feature = "unstable_protocol_v2")]
1249fn initialize_message(frame: TransportFrame) -> Result<RawJsonRpcMessage, crate::Error> {
1250    match frame {
1251        TransportFrame::Single(message) => Ok(message),
1252        TransportFrame::Malformed { error, .. } => Err(error),
1253    }
1254}
1255
1256#[cfg(feature = "unstable_protocol_v2")]
1257fn initialize_message_mut(
1258    frame: &mut TransportFrame,
1259) -> Result<Option<&mut RawJsonRpcMessage>, crate::Error> {
1260    match frame {
1261        TransportFrame::Single(RawJsonRpcMessage::Response(_)) => Ok(None),
1262        TransportFrame::Single(entry) => Ok(Some(entry)),
1263        TransportFrame::Malformed { raw, .. } if raw_is_response_only_shape(raw) => Ok(None),
1264        TransportFrame::Malformed { error, .. } => Err(error.clone()),
1265    }
1266}
1267
1268// Every protocol router uses the same ownership rule. Passive halves keep
1269// independent lifetimes; owned completion drains output and then either joins
1270// an opposed cooperative driver or cancels opaque work after polling errors.
1271#[cfg(feature = "unstable_protocol_v2")]
1272async fn pipe_protocol_peers_until_done(
1273    left: RunningProtocolPeer,
1274    right: RunningProtocolPeer,
1275) -> Result<(), crate::Error> {
1276    let mut left_driver = left.driver.into_driver();
1277    let mut right_driver = right.driver.into_driver();
1278    let left_passive = left_driver.is_none();
1279    let right_passive = right_driver.is_none();
1280    let left_finish = left_driver
1281        .as_mut()
1282        .and_then(crate::ConnectionDriver::take_finish);
1283    let right_finish = right_driver
1284        .as_mut()
1285        .and_then(crate::ConnectionDriver::take_finish);
1286    let (stop_left_tx, stop_left_rx) = futures::channel::oneshot::channel();
1287    let (stop_right_tx, stop_right_rx) = futures::channel::oneshot::channel();
1288    let stop = async |rx: futures::channel::oneshot::Receiver<()>| {
1289        if rx.await.is_err() {
1290            future::pending::<()>().await;
1291        }
1292    };
1293    let left_to_right = Box::pin(
1294        Channel {
1295            rx: left.rx,
1296            tx: right.tx,
1297        }
1298        .copy_with_driver_until(left_driver, stop(stop_left_rx)),
1299    );
1300    let right_to_left = Box::pin(
1301        Channel {
1302            rx: right.rx,
1303            tx: left.tx,
1304        }
1305        .copy_with_driver_until(right_driver, stop(stop_right_rx)),
1306    );
1307
1308    match future::select(left_to_right, right_to_left).await {
1309        future::Either::Left((result, right_to_left)) => {
1310            result?;
1311            if !left_passive {
1312                let _ = stop_right_tx.send(());
1313            }
1314            if left_passive || right_finish.is_some() {
1315                if !left_passive && let Some(mut finish) = right_finish {
1316                    finish.request();
1317                }
1318                right_to_left.await
1319            } else {
1320                // Without a cooperative finish hook, opposed work may remain
1321                // open indefinitely. Poll ready errors before cancelling it.
1322                crate::util::run_until(right_to_left, future::ready(Ok(()))).await
1323            }
1324        }
1325        future::Either::Right((result, left_to_right)) => {
1326            result?;
1327            if !right_passive {
1328                let _ = stop_left_tx.send(());
1329            }
1330            if right_passive || left_finish.is_some() {
1331                if !right_passive && let Some(mut finish) = left_finish {
1332                    finish.request();
1333                }
1334                left_to_right.await
1335            } else {
1336                crate::util::run_until(left_to_right, future::ready(Ok(()))).await
1337            }
1338        }
1339    }
1340}
1341
1342#[cfg(feature = "unstable_protocol_v2")]
1343#[derive(Debug)]
1344struct InitializeResponse {
1345    id: RawJsonRpcId,
1346    result: Result<serde_json::Value, Box<RawJsonRpcError>>,
1347}
1348
1349#[cfg(feature = "unstable_protocol_v2")]
1350impl InitializeResponse {
1351    fn from_message(message: RawJsonRpcMessage) -> Result<Self, crate::Error> {
1352        match message {
1353            RawJsonRpcMessage::Response(RpcResponse::Result { id, result }) => Ok(Self {
1354                id,
1355                result: Ok(result),
1356            }),
1357            RawJsonRpcMessage::Response(RpcResponse::Error { id, error }) => Ok(Self {
1358                id,
1359                result: Err(error),
1360            }),
1361            message => Err(crate::Error::invalid_request().data(format!(
1362                "first ACP response must be an initialize response, got {message:?}",
1363            ))),
1364        }
1365    }
1366
1367    fn into_message(self) -> RawJsonRpcMessage {
1368        RawJsonRpcMessage::Response(RpcResponse::new(self.id, self.result))
1369    }
1370
1371    fn with_id(self, id: RawJsonRpcId) -> RawJsonRpcMessage {
1372        RawJsonRpcMessage::Response(RpcResponse::new(id, self.result))
1373    }
1374
1375    fn protocol_version(&self) -> Option<ProtocolVersion> {
1376        serde_json::from_value(self.result.as_ref().ok()?.get("protocolVersion")?.clone()).ok()
1377    }
1378}
1379
1380#[cfg(all(test, feature = "unstable_protocol_v2"))]
1381mod raw_initialize_tests {
1382    use super::*;
1383    use serde_json::json;
1384
1385    #[test]
1386    fn initialize_error_forwarding_preserves_raw_fields() {
1387        for data in [
1388            None,
1389            Some(serde_json::Value::Null),
1390            Some(json!({"detail":"kept"})),
1391        ] {
1392            let mut error = json!({
1393                "code":-32000, "message":"peer", "extension":{"retry":true}
1394            });
1395            if let Some(data) = data {
1396                error["data"] = data;
1397            }
1398            let wire = json!({"jsonrpc":"2.0", "id":"original", "error":error});
1399            let response =
1400                InitializeResponse::from_message(serde_json::from_value(wire.clone()).unwrap())
1401                    .unwrap();
1402            assert_eq!(serde_json::to_value(response.into_message()).unwrap(), wire);
1403            let response =
1404                InitializeResponse::from_message(serde_json::from_value(wire).unwrap()).unwrap();
1405            let forwarded = response.with_id(RawJsonRpcId::Str("replacement".into()));
1406            assert_eq!(
1407                serde_json::to_value(forwarded).unwrap(),
1408                json!({"jsonrpc":"2.0", "id":"replacement", "error":error})
1409            );
1410        }
1411    }
1412}
1413
1414#[cfg(feature = "unstable_protocol_v2")]
1415#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1416enum ClientProtocol {
1417    V1,
1418    V2,
1419}
1420
1421#[cfg(feature = "unstable_protocol_v2")]
1422impl ClientProtocol {
1423    fn name(self) -> &'static str {
1424        match self {
1425            Self::V1 => "1",
1426            Self::V2 => "2",
1427        }
1428    }
1429}
1430
1431#[cfg(feature = "unstable_protocol_v2")]
1432#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1433struct SupportedClientProtocols {
1434    v1: bool,
1435    v2: bool,
1436}
1437
1438#[cfg(feature = "unstable_protocol_v2")]
1439impl SupportedClientProtocols {
1440    fn highest_configured(self) -> Option<ClientProtocol> {
1441        if self.v2 {
1442            return Some(ClientProtocol::V2);
1443        }
1444
1445        if self.v1 {
1446            return Some(ClientProtocol::V1);
1447        }
1448
1449        None
1450    }
1451}
1452
1453#[cfg(feature = "unstable_protocol_v2")]
1454async fn start_client_protocol(
1455    protocol: ClientProtocol,
1456    client: DynConnectTo<Agent>,
1457) -> Result<(RunningProtocolPeer, RawJsonRpcMessage), crate::Error> {
1458    let client = RunningProtocolPeer::new(client);
1459    let Some((initialize, client)) = client.next_message().await? else {
1460        return Err(crate::Error::invalid_request().data(format!(
1461            "ACP protocol version {} client implementation ended before initialize",
1462            protocol.name()
1463        )));
1464    };
1465    ensure_client_initialize_request(protocol, &initialize)?;
1466    Ok((client, initialize))
1467}
1468
1469#[cfg(feature = "unstable_protocol_v2")]
1470async fn send_initialize_and_receive(
1471    client: RunningProtocolPeer,
1472    agent: RunningProtocolPeer,
1473    initialize: RawJsonRpcMessage,
1474) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
1475    agent.send(initialize)?;
1476    let Some((response, agent)) = agent.next_message().await? else {
1477        return Err(crate::Error::internal_error().data("agent closed before initialize response"));
1478    };
1479    let response = InitializeResponse::from_message(response)?;
1480    Ok((client, agent, response))
1481}
1482
1483#[cfg(feature = "unstable_protocol_v2")]
1484async fn initialize_client_protocol(
1485    protocol: ClientProtocol,
1486    client: DynConnectTo<Agent>,
1487    agent: impl ConnectTo<Client>,
1488) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
1489    let agent = RunningProtocolPeer::new(agent);
1490    let (client, initialize) = start_client_protocol(protocol, client).await?;
1491    send_initialize_and_receive(client, agent, initialize).await
1492}
1493
1494#[cfg(feature = "unstable_protocol_v2")]
1495async fn connect_client_protocol(
1496    protocol: ClientProtocol,
1497    client: DynConnectTo<Agent>,
1498    agent: impl ConnectTo<Client>,
1499) -> Result<(), crate::Error> {
1500    let (client, agent, initialize_response) =
1501        initialize_client_protocol(protocol, client, agent).await?;
1502    client.send(initialize_response.into_message())?;
1503    pipe_protocol_peers_until_done(client, agent).await
1504}
1505
1506#[cfg(feature = "unstable_protocol_v2")]
1507fn ensure_client_initialize_request(
1508    protocol: ClientProtocol,
1509    message: &RawJsonRpcMessage,
1510) -> Result<(), crate::Error> {
1511    let RawJsonRpcMessage::Request(request) = message else {
1512        return Err(crate::Error::invalid_request().data(format!(
1513            "ACP protocol version {} client implementation must send initialize first",
1514            protocol.name()
1515        )));
1516    };
1517
1518    if request.method.as_ref() != "initialize" {
1519        return Err(crate::Error::invalid_request().data(format!(
1520            "ACP protocol version {} client implementation must send initialize first",
1521            protocol.name()
1522        )));
1523    }
1524
1525    Ok(())
1526}
1527
1528#[cfg(feature = "unstable_protocol_v2")]
1529fn initialize_request_id(message: &RawJsonRpcMessage) -> Option<RawJsonRpcId> {
1530    let RawJsonRpcMessage::Request(request) = message else {
1531        return None;
1532    };
1533    Some(request.id.clone())
1534}
1535
1536#[cfg(feature = "unstable_protocol_v2")]
1537fn initialize_response_negotiated_v1(response: &InitializeResponse) -> bool {
1538    response.protocol_version() == Some(ProtocolVersion::V1)
1539}
1540
1541impl HasPeer<Agent> for Agent {
1542    fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1543        RemoteStyle::Counterpart
1544    }
1545}
1546
1547/// The proxy role - an intermediary that can intercept and modify messages.
1548///
1549/// Proxies sit between a client and an agent (or another proxy), and can:
1550/// - Add tools via MCP servers
1551/// - Filter or transform messages
1552/// - Inject additional context
1553///
1554/// Proxies connect to a [`Conductor`] which orchestrates the proxy chain.
1555#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1556pub struct Proxy;
1557
1558impl Role for Proxy {
1559    type Counterpart = Conductor;
1560
1561    fn default_handle_dispatch_from(
1562        &self,
1563        message: crate::Dispatch,
1564        _connection: crate::ConnectionTo<Self>,
1565    ) -> impl Future<Output = Result<crate::Handled<crate::Dispatch>, crate::Error>> + Send {
1566        std::future::ready(Ok(Handled::No {
1567            message,
1568            retry: false,
1569        }))
1570    }
1571
1572    fn role_id(&self) -> RoleId {
1573        RoleId::from_singleton(self)
1574    }
1575
1576    fn counterpart(&self) -> Self::Counterpart {
1577        Conductor
1578    }
1579}
1580
1581impl Proxy {
1582    /// Create a stable protocol v1 connection builder for a proxy.
1583    ///
1584    /// The proxy negotiates `proxy/initialize` the way a v1 agent negotiates
1585    /// `initialize`: it selects v1 for every requested version. A request for
1586    /// a newer version keeps what parses under v1, and one below v1 keeps
1587    /// v1's fallback-to-latest rule.
1588    ///
1589    /// Use `Proxy::v2` for a protocol-v2-only proxy with typed callbacks and
1590    /// wire validation. Protocol-routing infrastructure that deliberately
1591    /// selects a version itself can use the unguarded
1592    /// `Builder::new(Proxy)`.
1593    ///
1594    /// Typed handlers on this builder receive [`V1`](crate::V1) request
1595    /// handles.
1596    pub fn v1(self) -> crate::v1::Builder<Proxy> {
1597        Builder::new(self).v1_proxy()
1598    }
1599
1600    /// Create a proxy builder that uses the ACP protocol v2 API.
1601    ///
1602    /// The proxy negotiates `proxy/initialize` the way a v2 agent negotiates
1603    /// `initialize`: it selects v2 for a request for v2 or newer, keeping what
1604    /// parses under v2 for a newer request, and rejects a request below v2
1605    /// with `-32022`.
1606    /// Fluent callbacks receive [`v2::ConnectionTo<Conductor>`](crate::v2::ConnectionTo), while
1607    /// low-level custom handlers and runners retain the protocol-neutral
1608    /// [`ConnectionTo`] interface.
1609    ///
1610    /// Requires the `unstable_protocol_v2` crate feature.
1611    #[cfg(feature = "unstable_protocol_v2")]
1612    pub fn v2(self) -> crate::v2::Builder<Proxy> {
1613        Builder::new(self).v2_proxy()
1614    }
1615
1616    /// Create a router that chooses between configured proxy implementations.
1617    ///
1618    /// Add implementations with [`ProxyProtocolRouter::with_v1`] and
1619    /// [`ProxyProtocolRouter::with_v2`]. The router reads the initial
1620    /// `proxy/initialize` request and selects an implementation exactly as
1621    /// [`Agent::protocol_router`] does for `initialize`: a request for v2 or
1622    /// newer selects the v2 implementation, keeping what parses under v2 for
1623    /// a newer request. With a v1 implementation configured, everything else
1624    /// is handed to the v1 implementation, which negotiates it as it would
1625    /// without the router. A v2-only router rejects a request below v2 with
1626    /// `-32022`. Like the agent router, it answers each rejected request or
1627    /// malformed frame with an error and keeps reading, so the conductor can
1628    /// send `proxy/initialize` again. A request for exactly the selected
1629    /// version is handed over in the complete, unchanged initial transport
1630    /// frame. The router does not convert later messages.
1631    ///
1632    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
1633    /// stabilizes.
1634    #[cfg(feature = "unstable_protocol_v2")]
1635    #[must_use]
1636    pub fn protocol_router(self) -> ProxyProtocolRouter {
1637        ProxyProtocolRouter::new()
1638    }
1639}
1640
1641/// Proxy component that routes each connection to a configured protocol implementation.
1642///
1643/// Use [`Proxy::protocol_router`] to start the builder, then add stable-v1 and
1644/// draft-v2 proxy implementations independently. Like [`AgentProtocolRouter`],
1645/// it selects the highest configured version at or below the one
1646/// `proxy/initialize` requests.
1647///
1648/// The `with_v1` and `with_v2` slots take any `ConnectTo<Conductor>`; nothing checks that an
1649/// implementation speaks the slot's version. Put a `.v1()` implementation in
1650/// `with_v1` and a `.v2()` implementation in `with_v2`.
1651#[cfg(feature = "unstable_protocol_v2")]
1652#[derive(Debug, Default)]
1653pub struct ProxyProtocolRouter {
1654    v1: Option<DynConnectTo<Conductor>>,
1655    v2: Option<DynConnectTo<Conductor>>,
1656}
1657
1658#[cfg(feature = "unstable_protocol_v2")]
1659impl ProxyProtocolRouter {
1660    /// Create an empty proxy protocol router.
1661    #[must_use]
1662    pub fn new() -> Self {
1663        Self::default()
1664    }
1665
1666    /// Return this router with a stable ACP v1 proxy implementation.
1667    #[must_use]
1668    pub fn with_v1(mut self, proxy: impl ConnectTo<Conductor>) -> Self {
1669        self.v1 = Some(DynConnectTo::new(proxy));
1670        self
1671    }
1672
1673    /// Return this router with a draft ACP v2 proxy implementation.
1674    #[must_use]
1675    pub fn with_v2(mut self, proxy: impl ConnectTo<Conductor>) -> Self {
1676        self.v2 = Some(DynConnectTo::new(proxy));
1677        self
1678    }
1679}
1680
1681#[cfg(feature = "unstable_protocol_v2")]
1682impl ConnectTo<Conductor> for ProxyProtocolRouter {
1683    async fn connect_to(self, conductor: impl ConnectTo<Proxy>) -> Result<(), crate::Error> {
1684        let supported = SupportedProtocols {
1685            v1: self.v1.is_some(),
1686            v2: self.v2.is_some(),
1687        };
1688        let conductor = RunningProtocolPeer::new(conductor);
1689        let Some((first_frame, conductor, selected)) =
1690            select_protocol_peer(conductor, PROXY_METHOD_NAMES.proxy_initialize, supported).await?
1691        else {
1692            return Ok(());
1693        };
1694        let proxy = selected
1695            .take_proxy(self)
1696            .expect("the router only selects a configured implementation");
1697
1698        let proxy = RunningProtocolPeer::new(proxy);
1699        proxy.send_frame(first_frame)?;
1700        pipe_protocol_peers_until_done(conductor, proxy).await
1701    }
1702}
1703
1704#[cfg(feature = "unstable_protocol_v2")]
1705impl SelectedProtocol {
1706    fn take_proxy(self, proxy: ProxyProtocolRouter) -> Option<DynConnectTo<Conductor>> {
1707        match self {
1708            Self::V1 => proxy.v1,
1709            Self::V2 => proxy.v2,
1710        }
1711    }
1712}
1713
1714impl HasPeer<Proxy> for Proxy {
1715    fn remote_style(&self, _peer: Proxy) -> RemoteStyle {
1716        RemoteStyle::Counterpart
1717    }
1718}
1719
1720/// The conductor role - orchestrates proxy chains.
1721///
1722/// Conductors manage connections between clients, proxies, and agents,
1723/// routing messages through the appropriate proxy chain.
1724#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1725pub struct Conductor;
1726
1727impl Role for Conductor {
1728    type Counterpart = Proxy;
1729
1730    fn role_id(&self) -> RoleId {
1731        RoleId::from_singleton(self)
1732    }
1733
1734    fn counterpart(&self) -> Self::Counterpart {
1735        Proxy
1736    }
1737
1738    async fn default_handle_dispatch_from(
1739        &self,
1740        message: Dispatch,
1741        cx: ConnectionTo<Conductor>,
1742    ) -> Result<Handled<Dispatch>, crate::Error> {
1743        forward_proxy_dispatch(message, cx).await
1744    }
1745}
1746
1747/// Default forwarding for a proxy.
1748///
1749/// `proxy/initialize` has the same wire shape in every protocol version, so
1750/// one untyped path serves v1 and v2 proxies alike. Messages are forwarded
1751/// without being parsed and re-serialized, so fields this SDK does not know
1752/// survive. The connection's protocol guard, if any, has already validated
1753/// them. A `session/new` response installs the session's update routing from
1754/// its raw `sessionId`.
1755async fn forward_proxy_dispatch(
1756    message: Dispatch,
1757    cx: ConnectionTo<Conductor>,
1758) -> Result<Handled<Dispatch>, crate::Error> {
1759    let message = match message {
1760        Dispatch::Request(request, responder) if request.method() == "initialize" => {
1761            responder.respond_with_error(crate::Error::invalid_request().data(format!(
1762                "proxies must be initialized with `{}`",
1763                PROXY_METHOD_NAMES.proxy_initialize
1764            )))?;
1765            return Ok(Handled::Yes);
1766        }
1767        Dispatch::Request(mut request, responder)
1768            if request.method() == PROXY_METHOD_NAMES.proxy_initialize =>
1769        {
1770            request.method = "initialize".to_string();
1771            cx.send_ordered_request_to::<Raw, _, _>(Agent, request)
1772                .forward_response_to(responder)?;
1773            return Ok(Handled::Yes);
1774        }
1775        Dispatch::Request(request, responder) if request.method() == "session/new" => {
1776            let sent = cx.send_ordered_request_to::<Raw, _, _>(Agent, request);
1777            // The dynamic-handler hook below means we cannot use
1778            // `forward_response_to`, so wire up cancellation forwarding
1779            // explicitly to keep `session/new` cancellable like every
1780            // other proxied request.
1781            let sent = sent.forward_cancellation_from(responder.cancellation());
1782            sent.on_receiving_result({
1783                let cx = cx.clone();
1784                async move |result| {
1785                    // The result goes back unchanged, as the proxy chains RFD
1786                    // requires, even when no session routing can be installed
1787                    // from it.
1788                    if let Ok(response) = &result
1789                        && let Err(error) = install_proxy_session_routing(&cx, response)
1790                    {
1791                        tracing::warn!(
1792                            error = ?crate::util::redacted_error(error.code),
1793                            "forwarding a `session/new` result without session routing"
1794                        );
1795                    }
1796                    responder.respond_with_result(result)
1797                }
1798            })?;
1799            return Ok(Handled::Yes);
1800        }
1801        message => message,
1802    };
1803
1804    MatchDispatchFrom::new(message, &cx)
1805        .if_dispatch_from(Client, async |message: Dispatch| {
1806            cx.send_proxied_message_to(Agent, message)
1807        })
1808        .await
1809        .if_dispatch_from(Agent, async |message: Dispatch| {
1810            cx.send_proxied_message_to(Client, message)
1811        })
1812        .await
1813        .done()
1814}
1815
1816/// Route the updates of the session a forwarded `session/new` result created.
1817fn install_proxy_session_routing(
1818    cx: &ConnectionTo<Conductor>,
1819    response: &serde_json::Value,
1820) -> Result<(), crate::Error> {
1821    let envelope: NewSessionResponseEnvelope = crate::util::json_cast(response)?;
1822    cx.add_dynamic_handler(ProxySessionMessages::new(envelope.session_id))?
1823        .detach();
1824    Ok(())
1825}
1826
1827impl Conductor {
1828    /// Create a connection builder for a conductor.
1829    pub fn builder(self) -> Builder<Conductor, NullHandler, NullRun> {
1830        Builder::new(self)
1831    }
1832}
1833
1834impl HasPeer<Client> for Conductor {
1835    fn remote_style(&self, _peer: Client) -> RemoteStyle {
1836        RemoteStyle::Predecessor
1837    }
1838}
1839
1840impl HasPeer<Agent> for Conductor {
1841    fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1842        RemoteStyle::Successor
1843    }
1844}
1845
1846/// Dynamic handler that proxies session messages from Agent to Client.
1847///
1848/// This is used internally to handle session message routing after a
1849/// `session.new` request has been forwarded.
1850pub(crate) struct ProxySessionMessages {
1851    session_id: SessionId,
1852}
1853
1854impl ProxySessionMessages {
1855    /// Create a new proxy handler for the given session.
1856    pub fn new(session_id: SessionId) -> Self {
1857        Self { session_id }
1858    }
1859}
1860
1861impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for ProxySessionMessages
1862where
1863    Counterpart: HasPeer<Agent> + HasPeer<Client>,
1864{
1865    async fn handle_dispatch_from(
1866        &mut self,
1867        message: Dispatch,
1868        connection: ConnectionTo<Counterpart>,
1869    ) -> Result<Handled<Dispatch>, crate::Error> {
1870        MatchDispatchFrom::new(message, &connection)
1871            .if_dispatch_from(Agent, async |message| {
1872                // If this is for our session-id, proxy it to the client.
1873                if let Some(session_id) = message.get_session_id()?
1874                    && session_id == self.session_id
1875                {
1876                    connection.send_proxied_message_to(Client, message)?;
1877                    return Ok(Handled::Yes);
1878                }
1879
1880                // Otherwise, leave it alone.
1881                Ok(Handled::No {
1882                    message,
1883                    retry: false,
1884                })
1885            })
1886            .await
1887            .done()
1888    }
1889
1890    fn describe_chain(&self) -> impl std::fmt::Debug {
1891        format!("ProxySessionMessages({})", self.session_id)
1892    }
1893}
1894
1895#[cfg(all(test, feature = "unstable_protocol_v2"))]
1896mod lifetime_tests {
1897    use super::*;
1898    use crate::{ConnectionDriver, UntypedRole};
1899    use futures::FutureExt as _;
1900
1901    fn frame() -> TransportFrame {
1902        TransportFrame::parse_json(r#"{"jsonrpc":"2.0","method":"test/queued","params":{}}"#)
1903    }
1904
1905    #[tokio::test]
1906    async fn passive_initialization_waits_for_a_frame_not_driver_readiness() {
1907        let (channel, remote) = Channel::duplex();
1908        let peer = RunningProtocolPeer::new::<UntypedRole>(channel);
1909        let mut next = Box::pin(peer.next_frame());
1910        assert!(next.as_mut().now_or_never().is_none());
1911
1912        remote.tx.unbounded_send(frame()).unwrap();
1913        let (_, peer) = next.await.unwrap().expect("passive peer remains connected");
1914        assert!(matches!(peer.driver, ProtocolPeerDriver::Passive));
1915    }
1916
1917    #[tokio::test]
1918    async fn owned_peer_preserves_finish_metadata_through_active_and_completed_states() {
1919        let (Channel { rx, tx }, remote) = Channel::duplex();
1920        let (done_tx, done_rx) = futures::channel::oneshot::channel();
1921        let (finish_tx, finish_rx) = futures::channel::oneshot::channel();
1922        let peer = RunningProtocolPeer {
1923            rx,
1924            tx,
1925            driver: ProtocolPeerDriver::Active(ConnectionDriver::with_finish(
1926                async move {
1927                    done_rx.await.unwrap();
1928                    Ok(())
1929                },
1930                move || {
1931                    let _ = finish_tx.send(());
1932                },
1933            )),
1934        };
1935
1936        remote.tx.unbounded_send(frame()).unwrap();
1937        let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1938        assert!(matches!(&peer.driver, ProtocolPeerDriver::Active(_)));
1939
1940        remote.tx.unbounded_send(frame()).unwrap();
1941        done_tx.send(()).unwrap();
1942        let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1943        assert!(matches!(
1944            &peer.driver,
1945            ProtocolPeerDriver::Completed { finish: Some(_) }
1946        ));
1947        let mut driver = peer
1948            .driver
1949            .into_driver()
1950            .expect("completed owned work must not become passive");
1951        assert!(driver.request_finish());
1952        finish_rx.await.unwrap();
1953        driver.await.unwrap();
1954    }
1955
1956    #[tokio::test]
1957    async fn active_initialization_drains_accepted_frames_without_escaped_sender_eof() {
1958        let (Channel { rx, tx }, remote) = Channel::duplex();
1959        remote.tx.unbounded_send(frame()).unwrap();
1960        remote.tx.unbounded_send(frame()).unwrap();
1961        let mut polls = 0;
1962        let driver = ConnectionDriver::new(future::poll_fn(move |_| {
1963            polls += 1;
1964            assert_eq!(polls, 1, "the completed driver must never be re-polled");
1965            std::task::Poll::Ready(Ok(()))
1966        }));
1967        let peer = RunningProtocolPeer {
1968            rx,
1969            tx,
1970            driver: ProtocolPeerDriver::Active(driver),
1971        };
1972
1973        let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1974        assert!(
1975            remote.tx.unbounded_send(frame()).is_err(),
1976            "active completion must reject new output from escaped handles"
1977        );
1978        let (_, peer) = peer.next_frame().await.unwrap().unwrap();
1979        assert!(peer.next_frame().await.unwrap().is_none());
1980    }
1981
1982    #[tokio::test]
1983    async fn ready_initialization_driver_error_beats_queued_frames() {
1984        let (Channel { rx, tx }, remote) = Channel::duplex();
1985        remote.tx.unbounded_send(frame()).unwrap();
1986        let error = crate::Error::internal_error().data("owned initialization failed");
1987        let peer = RunningProtocolPeer {
1988            rx,
1989            tx,
1990            driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Err(
1991                error.clone()
1992            )))),
1993        };
1994
1995        match peer.next_frame().await {
1996            Err(actual) => assert_eq!(actual, error),
1997            Ok(_) => panic!("ready driver error must not be hidden by a queued frame"),
1998        }
1999    }
2000
2001    struct QueuedFinalClient;
2002
2003    impl ConnectTo<Agent> for QueuedFinalClient {
2004        async fn connect_to(self, agent: impl ConnectTo<Client>) -> Result<(), crate::Error> {
2005            let (mut channel, driver) = agent.into_channel_and_future();
2006            let foreground = async move {
2007                channel
2008                    .tx
2009                    .unbounded_send(TransportFrame::Single(RawJsonRpcMessage::request(
2010                        "initialize".into(),
2011                        serde_json::json!({ "protocolVersion": 1, "clientCapabilities": {} }),
2012                        RawJsonRpcId::Number(1),
2013                    )?))
2014                    .unwrap();
2015                assert!(channel.rx.next().await.is_some(), "initialize response");
2016                // Exceed the physical writer capacity so only concurrent polling
2017                // of the sink can make this bridge finish.
2018                for index in 0..3 {
2019                    channel
2020                        .tx
2021                        .unbounded_send(TransportFrame::Single(RawJsonRpcMessage::notification(
2022                            "test/final".into(),
2023                            serde_json::json!({ "index": index, "payload": "x".repeat(1024) }),
2024                        )?))
2025                        .unwrap();
2026                }
2027                Ok(())
2028            };
2029            match driver {
2030                Some(driver) => crate::util::run_until(driver, foreground).await,
2031                None => foreground.await,
2032            }
2033        }
2034    }
2035
2036    #[tokio::test]
2037    async fn connector_completion_flushes_byte_streams_without_remote_read_eof() {
2038        use tokio::io::{AsyncBufReadExt as _, AsyncWriteExt as _, BufReader};
2039        use tokio_util::compat::{TokioAsyncReadCompatExt as _, TokioAsyncWriteCompatExt as _};
2040
2041        let (writer, remote_reader) = tokio::io::duplex(64);
2042        let (mut remote_input, reader) = tokio::io::duplex(64);
2043        let physical = crate::ByteStreams::new(writer.compat_write(), reader.compat());
2044        let mut physical = Some(crate::DynConnectTo::<Client>::new(physical));
2045        let connector = tokio::spawn(
2046            ClientProtocolConnector::new()
2047                .with_v1(|| QueuedFinalClient)
2048                .connect_to(move || physical.take().expect("one physical connection")),
2049        );
2050        let mut lines = BufReader::new(remote_reader).lines();
2051
2052        tokio::time::timeout(std::time::Duration::from_secs(5), async {
2053            let initialize = lines
2054                .next_line()
2055                .await
2056                .unwrap()
2057                .expect("initialize request");
2058            let value: serde_json::Value = serde_json::from_str(&initialize).unwrap();
2059            assert_eq!(value["method"], "initialize");
2060            remote_input
2061                .write_all(b"{\"jsonrpc\":\"2.0\",\"id\":1,\"result\":{\"protocolVersion\":1}}\n")
2062                .await
2063                .unwrap();
2064            for index in 0..3 {
2065                let line = lines
2066                    .next_line()
2067                    .await
2068                    .unwrap()
2069                    .expect("accepted final output");
2070                let value: serde_json::Value = serde_json::from_str(&line).unwrap();
2071                assert_eq!(value["params"]["index"], index);
2072                assert_eq!(value["params"]["payload"].as_str().unwrap().len(), 1024);
2073            }
2074            connector.await.unwrap().unwrap();
2075            assert!(lines.next_line().await.unwrap().is_none());
2076        })
2077        .await
2078        .expect("physical flush must not wait for independent remote input EOF");
2079        drop(remote_input);
2080    }
2081
2082    #[derive(Default, Debug)]
2083    struct GatedLineSinkState {
2084        pending: Vec<String>,
2085        flushed: Vec<String>,
2086        closed: bool,
2087        dropped: bool,
2088    }
2089
2090    struct GatedLineSink {
2091        state: std::sync::Arc<std::sync::Mutex<GatedLineSinkState>>,
2092        release: futures::channel::oneshot::Receiver<()>,
2093        released: bool,
2094    }
2095
2096    impl futures::Sink<String> for GatedLineSink {
2097        type Error = std::io::Error;
2098
2099        fn poll_ready(
2100            self: std::pin::Pin<&mut Self>,
2101            _: &mut std::task::Context<'_>,
2102        ) -> std::task::Poll<Result<(), Self::Error>> {
2103            std::task::Poll::Ready(Ok(()))
2104        }
2105
2106        fn start_send(self: std::pin::Pin<&mut Self>, line: String) -> Result<(), Self::Error> {
2107            self.state.lock().unwrap().pending.push(line);
2108            Ok(())
2109        }
2110
2111        fn poll_flush(
2112            mut self: std::pin::Pin<&mut Self>,
2113            cx: &mut std::task::Context<'_>,
2114        ) -> std::task::Poll<Result<(), Self::Error>> {
2115            if !self.released {
2116                if std::pin::Pin::new(&mut self.release).poll(cx).is_pending() {
2117                    return std::task::Poll::Pending;
2118                }
2119                self.released = true;
2120            }
2121            let mut state = self.state.lock().unwrap();
2122            let pending = std::mem::take(&mut state.pending);
2123            state.flushed.extend(pending);
2124            std::task::Poll::Ready(Ok(()))
2125        }
2126
2127        fn poll_close(
2128            mut self: std::pin::Pin<&mut Self>,
2129            cx: &mut std::task::Context<'_>,
2130        ) -> std::task::Poll<Result<(), Self::Error>> {
2131            match self.as_mut().poll_flush(cx) {
2132                std::task::Poll::Ready(Ok(())) => {
2133                    self.state.lock().unwrap().closed = true;
2134                    std::task::Poll::Ready(Ok(()))
2135                }
2136                result => result,
2137            }
2138        }
2139    }
2140
2141    impl Drop for GatedLineSink {
2142        fn drop(&mut self) {
2143            self.state.lock().unwrap().dropped = true;
2144        }
2145    }
2146
2147    #[tokio::test]
2148    async fn foreground_completion_flushes_lines_with_already_normalized_remote_input() {
2149        let state = std::sync::Arc::new(std::sync::Mutex::new(GatedLineSinkState::default()));
2150        let (release_tx, release_rx) = futures::channel::oneshot::channel();
2151        let sink = GatedLineSink {
2152            state: state.clone(),
2153            release: release_rx,
2154            released: false,
2155        };
2156        let (remote_input, incoming) = futures::channel::mpsc::unbounded();
2157        remote_input
2158            .unbounded_send(Ok(
2159                r#"{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1}}"#.to_string(),
2160            ))
2161            .unwrap();
2162        remote_input
2163            .unbounded_send(Ok(
2164                r#"{"jsonrpc":"2.0","method":"test/queued","params":{}}"#.to_string(),
2165            ))
2166            .unwrap();
2167        let physical = RunningProtocolPeer::new::<Client>(crate::Lines::new(sink, incoming));
2168        // The real Lines driver reads both ready lines before this returns.
2169        let (initialize, physical) = physical.next_frame().await.unwrap().unwrap();
2170        assert_eq!(
2171            futures::Stream::size_hint(&physical.rx).0,
2172            1,
2173            "trailing input must already be in the original normalized queue"
2174        );
2175
2176        let (Channel { rx, tx }, mut local) = Channel::duplex();
2177        tx.unbounded_send(initialize).unwrap();
2178        let foreground = RunningProtocolPeer {
2179            rx,
2180            tx,
2181            driver: ProtocolPeerDriver::Active(ConnectionDriver::new(async move {
2182                assert!(local.rx.next().await.is_some(), "initialize response");
2183                for index in 0..3 {
2184                    local
2185                        .tx
2186                        .unbounded_send(TransportFrame::Single(RawJsonRpcMessage::notification(
2187                            "test/final".into(),
2188                            serde_json::json!({ "index": index, "payload": "x".repeat(1024) }),
2189                        )?))
2190                        .unwrap();
2191                }
2192                // This owns the foreground input receiver: completion closes it.
2193                drop(local);
2194                Ok(())
2195            })),
2196        };
2197        let mut bridge = Box::pin(pipe_protocol_peers_until_done(foreground, physical));
2198        let early_result = bridge.as_mut().now_or_never();
2199        assert!(
2200            !state.lock().unwrap().pending.is_empty(),
2201            "the real physical writer must accept output before the gated flush"
2202        );
2203        // Never use time to establish the race: only the sink gate controls drain.
2204        let _released = release_tx.send(());
2205        let result = match early_result {
2206            Some(result) => result,
2207            None => tokio::time::timeout(std::time::Duration::from_secs(1), bridge)
2208                .await
2209                .expect("physical drain must not require remote input EOF"),
2210        };
2211        let state = state.lock().unwrap();
2212        assert!(
2213            result.is_ok() && state.flushed.len() == 3 && state.closed,
2214            "accepted output must drain cleanly despite queued remote input: result={result:?}, pending={}, flushed={}, closed={}, dropped={}",
2215            state.pending.len(),
2216            state.flushed.len(),
2217            state.closed,
2218            state.dropped,
2219        );
2220        for (index, line) in state.flushed.iter().enumerate() {
2221            let value: serde_json::Value = serde_json::from_str(line).unwrap();
2222            assert_eq!(value["method"], "test/final");
2223            assert_eq!(value["params"]["index"], index);
2224            assert_eq!(value["params"]["payload"].as_str().unwrap().len(), 1024);
2225        }
2226        drop(remote_input);
2227    }
2228
2229    #[tokio::test]
2230    async fn foreground_completion_keeps_read_errors_during_lines_drain() {
2231        let state = std::sync::Arc::new(std::sync::Mutex::new(GatedLineSinkState::default()));
2232        let (_release_tx, release_rx) = futures::channel::oneshot::channel();
2233        let sink = GatedLineSink {
2234            state: state.clone(),
2235            release: release_rx,
2236            released: false,
2237        };
2238        let (remote_input, incoming) = futures::channel::mpsc::unbounded();
2239        let physical = RunningProtocolPeer::new::<Client>(crate::Lines::new(sink, incoming));
2240        let (Channel { rx, tx }, local) = Channel::duplex();
2241        local.tx.unbounded_send(frame()).unwrap();
2242        drop(local);
2243        let foreground = RunningProtocolPeer {
2244            rx,
2245            tx,
2246            driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Ok(())))),
2247        };
2248        let mut bridge = Box::pin(pipe_protocol_peers_until_done(foreground, physical));
2249        assert!(bridge.as_mut().now_or_never().is_none());
2250        assert_eq!(state.lock().unwrap().pending.len(), 1);
2251
2252        // Newly read successful input is irrelevant to the completed foreground,
2253        // but a genuine read failure must still cancel the blocked sink drain.
2254        remote_input
2255            .unbounded_send(Ok(r#"{"jsonrpc":"2.0","method":"late"}"#.to_string()))
2256            .unwrap();
2257        remote_input
2258            .unbounded_send(Err(std::io::Error::other("read failed after foreground")))
2259            .unwrap();
2260        let error = tokio::time::timeout(std::time::Duration::from_secs(1), bridge)
2261            .await
2262            .expect("read failure must not wait for the sink gate")
2263            .expect_err("real read error must win over foreground success");
2264        assert!(
2265            error
2266                .data
2267                .unwrap()
2268                .to_string()
2269                .contains("read failed after foreground"),
2270            "the read error must not become a receiver-gone forwarding error"
2271        );
2272        assert_eq!(state.lock().unwrap().flushed, Vec::<String>::new());
2273    }
2274
2275    #[tokio::test]
2276    async fn foreground_completion_cancels_opposed_opaque_work_in_both_directions() {
2277        for foreground_on_left in [true, false] {
2278            let (Channel { rx, tx }, foreground_remote) = Channel::duplex();
2279            foreground_remote.tx.unbounded_send(frame()).unwrap();
2280            let foreground = RunningProtocolPeer {
2281                rx,
2282                tx,
2283                driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Ok(())))),
2284            };
2285            let (Channel { rx, tx }, mut opposed_remote) = Channel::duplex();
2286            let (work_tx, work_rx) = futures::channel::oneshot::channel::<()>();
2287            let opposed = RunningProtocolPeer {
2288                rx,
2289                tx,
2290                driver: ProtocolPeerDriver::Active(ConnectionDriver::new(async move {
2291                    work_rx.await.map_err(crate::util::internal_error)?;
2292                    Ok(())
2293                })),
2294            };
2295            let mut bridge = Box::pin(if foreground_on_left {
2296                pipe_protocol_peers_until_done(foreground, opposed)
2297            } else {
2298                pipe_protocol_peers_until_done(opposed, foreground)
2299            });
2300
2301            assert_eq!(
2302                bridge.as_mut().now_or_never(),
2303                Some(Ok(())),
2304                "finite foreground must not join an opaque pending peer"
2305            );
2306            assert!(work_tx.is_canceled(), "opaque work must be dropped");
2307            assert!(opposed_remote.rx.next().await.is_some());
2308            assert!(opposed_remote.rx.next().await.is_none());
2309        }
2310    }
2311
2312    #[tokio::test]
2313    async fn foreground_completion_does_not_hide_opposed_ready_driver_error() {
2314        let (Channel { rx, tx }, _foreground_remote) = Channel::duplex();
2315        let foreground = RunningProtocolPeer {
2316            rx,
2317            tx,
2318            driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Ok(())))),
2319        };
2320        let (Channel { rx, tx }, _opposed_remote) = Channel::duplex();
2321        let error = crate::Error::internal_error().data("opposed driver failed");
2322        let opposed = RunningProtocolPeer {
2323            rx,
2324            tx,
2325            driver: ProtocolPeerDriver::Active(ConnectionDriver::new(future::ready(Err(
2326                error.clone()
2327            )))),
2328        };
2329
2330        assert_eq!(
2331            pipe_protocol_peers_until_done(foreground, opposed).await,
2332            Err(error)
2333        );
2334    }
2335
2336    #[tokio::test]
2337    async fn passive_protocol_bridge_preserves_the_reverse_half_after_eof() {
2338        let (left, mut remote_left) = Channel::duplex();
2339        let (right, mut remote_right) = Channel::duplex();
2340        let mut bridge = Box::pin(pipe_protocol_peers_until_done(
2341            RunningProtocolPeer::new::<UntypedRole>(left),
2342            RunningProtocolPeer::new::<UntypedRole>(right),
2343        ));
2344        remote_left.tx.close_channel();
2345        assert!(bridge.as_mut().now_or_never().is_none());
2346        assert!(remote_right.rx.next().await.is_none());
2347
2348        remote_right.tx.unbounded_send(frame()).unwrap();
2349        remote_right.tx.close_channel();
2350        bridge.await.unwrap();
2351        assert!(remote_left.rx.next().await.is_some());
2352        assert!(remote_left.rx.next().await.is_none());
2353    }
2354}