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