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;
20use crate::schema::v1::{InitializeRequest, SessionId};
21#[cfg(not(feature = "unstable_protocol_v2"))]
22use crate::schema::v1::{NewSessionRequest, NewSessionResponse};
23#[cfg(feature = "unstable_protocol_v2")]
24use crate::schema::v1::{RequestId, Response as RpcResponse};
25#[cfg(feature = "unstable_protocol_v2")]
26use crate::schema::{ProtocolVersion, v2};
27use crate::util::MatchDispatchFrom;
28#[cfg(feature = "unstable_protocol_v2")]
29use crate::{Channel, RawJsonRpcMessage, RawJsonRpcParams};
30use crate::{ConnectTo, ConnectionTo, Dispatch, HandleDispatchFrom, Handled, Role, RoleId};
31
32#[cfg(feature = "unstable_protocol_v2")]
33#[derive(serde::Deserialize)]
34struct NewSessionResponseEnvelope {
35    #[serde(rename = "sessionId")]
36    session_id: SessionId,
37}
38
39/// The client role - typically an IDE or CLI that controls an agent.
40///
41/// Clients send prompts and receive responses from agents.
42#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
43pub struct Client;
44
45impl Role for Client {
46    type Counterpart = Agent;
47
48    fn builder(self) -> Builder<Self> {
49        Builder::new(self).v1_client()
50    }
51
52    fn default_handle_dispatch_from(
53        &self,
54        message: Dispatch,
55        _connection: ConnectionTo<Client>,
56    ) -> impl Future<Output = Result<Handled<Dispatch>, crate::Error>> + Send {
57        std::future::ready(Ok(Handled::No {
58            message,
59            retry: false,
60        }))
61    }
62
63    fn role_id(&self) -> RoleId {
64        RoleId::from_singleton(self)
65    }
66
67    fn counterpart(&self) -> Self::Counterpart {
68        Agent
69    }
70}
71
72impl Client {
73    /// Create a connection builder for a client.
74    pub fn builder(self) -> Builder<Client, NullHandler, NullRun> {
75        <Self as Role>::builder(self)
76    }
77
78    /// Create a client builder that requires an ACP protocol v2 agent.
79    ///
80    /// If the agent negotiates v1 during initialization, the initialize
81    /// request resolves with an error so callers can choose an explicit v1
82    /// fallback path.
83    ///
84    /// Requires the `unstable_protocol_v2` crate feature.
85    #[cfg(feature = "unstable_protocol_v2")]
86    pub fn v2(self) -> V2Builder<Client, NullHandler, NullRun> {
87        self.builder().v2_client()
88    }
89
90    /// Create a connector that chooses between configured protocol implementations.
91    ///
92    /// Add implementation factories with [`ClientProtocolConnector::with_v1`]
93    /// and [`ClientProtocolConnector::with_v2`]. The resulting connector starts
94    /// the highest configured protocol implementation. If a v2 implementation
95    /// successfully negotiates v1 and a v1 implementation is configured, the
96    /// connector reuses the connection only when the v1 implementation's
97    /// complete `initialize` parameters match what the agent already saw;
98    /// otherwise it opens a fresh agent connection and restarts with v1.
99    ///
100    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
101    /// stabilizes.
102    #[cfg(feature = "unstable_protocol_v2")]
103    #[must_use]
104    pub fn protocol_connector(self) -> ClientProtocolConnector {
105        ClientProtocolConnector::new()
106    }
107
108    /// Connect to `agent` and run `main_fn` with the [`ConnectionTo`].
109    /// Returns the result of `main_fn` (or an error if something goes wrong).
110    ///
111    /// Equivalent to `self.builder().connect_with(agent, main_fn)`.
112    pub async fn connect_with<R>(
113        self,
114        agent: impl ConnectTo<Client>,
115        main_fn: impl AsyncFnOnce(ConnectionTo<Agent>) -> Result<R, crate::Error>,
116    ) -> Result<R, crate::Error> {
117        self.builder().connect_with(agent, main_fn).await
118    }
119}
120
121/// Client connector that opens an agent connection with a configured protocol implementation.
122///
123/// Use [`Client::protocol_connector`] to start the builder, then add each
124/// supported protocol version independently. Implementations and the agent
125/// connection are provided as factories because fallback from v2 to v1 may
126/// require a fresh connection initialized by the v1 implementation.
127#[cfg(feature = "unstable_protocol_v2")]
128#[derive(Debug, Default)]
129pub struct ClientProtocolConnector {
130    v1: Option<DynConnectToFactory<Agent>>,
131    v2: Option<DynConnectToFactory<Agent>>,
132}
133
134#[cfg(feature = "unstable_protocol_v2")]
135impl ClientProtocolConnector {
136    /// Create an empty client protocol connector.
137    #[must_use]
138    pub fn new() -> Self {
139        Self::default()
140    }
141
142    /// Return this connector with an ACP v1 implementation factory configured.
143    #[must_use]
144    pub fn with_v1<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
145    where
146        C: ConnectTo<Agent>,
147    {
148        self.v1 = Some(DynConnectToFactory::new(client));
149        self
150    }
151
152    /// Return this connector with an ACP v2 implementation factory configured.
153    #[must_use]
154    pub fn with_v2<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
155    where
156        C: ConnectTo<Agent>,
157    {
158        self.v2 = Some(DynConnectToFactory::new(client));
159        self
160    }
161
162    /// Connect to an agent produced by `agent` using the highest configured
163    /// compatible protocol implementation.
164    pub async fn connect_to<C>(
165        mut self,
166        mut agent: impl FnMut() -> C + Send + 'static,
167    ) -> Result<(), crate::Error>
168    where
169        C: ConnectTo<Client>,
170    {
171        let supported = SupportedClientProtocols {
172            v1: self.v1.is_some(),
173            v2: self.v2.is_some(),
174        };
175        let Some(selected) = supported.highest_configured() else {
176            return Err(crate::Error::invalid_request()
177                .data("client protocol connector has no configured ACP protocol implementations"));
178        };
179
180        match selected {
181            ClientProtocol::V1 => {
182                let client = self
183                    .v1
184                    .as_mut()
185                    .expect("selected protocol is configured")
186                    .create();
187                connect_client_protocol(ClientProtocol::V1, client, agent()).await
188            }
189            ClientProtocol::V2 => {
190                let client = self
191                    .v2
192                    .as_mut()
193                    .expect("selected protocol is configured")
194                    .create();
195                let agent_connection = RunningProtocolPeer::new(agent());
196                let (client, initialize) =
197                    start_client_protocol(ClientProtocol::V2, client).await?;
198                // This normalization is only a probe for the connection-reuse
199                // optimization. A request that cannot be represented in v1
200                // can still be valid v2 traffic and must reach the agent.
201                let v2_initialize_as_v1 = normalize_v2_initialize_params_for_reuse(&initialize);
202                let (client, agent_connection, initialize_response) =
203                    send_initialize_and_receive(client, agent_connection, initialize).await?;
204
205                if initialize_response_negotiated_v1(&initialize_response)
206                    && let Some(v1) = self.v1.as_mut()
207                {
208                    let fallback_client = v1.create();
209                    let (fallback_client, fallback_initialize) =
210                        start_client_protocol(ClientProtocol::V1, fallback_client).await?;
211                    let v1_initialize =
212                        validated_initialize_params::<InitializeRequest>(&fallback_initialize)?;
213
214                    if v2_initialize_as_v1
215                        .as_ref()
216                        .is_ok_and(|v2_initialize| v2_initialize == &v1_initialize)
217                    {
218                        let fallback_response = initialize_response.with_id(
219                            initialize_request_id(&fallback_initialize)
220                                .expect("validated initialize request has an id"),
221                        );
222                        // The v2 implementation will never receive its initialize response once
223                        // the matching v1 implementation takes over this connection. Drop its
224                        // future and channels before running the v1 session so any resources it
225                        // owns are released promptly.
226                        drop(client);
227                        fallback_client.send(fallback_response)?;
228                        return pipe_protocol_peers_until_done(fallback_client, agent_connection)
229                            .await;
230                    }
231
232                    // Neither probe can continue on the replacement connection. Release both
233                    // client implementations and the original agent connection before starting
234                    // the real v1 session so they cannot retain resources for its lifetime.
235                    drop((
236                        client,
237                        fallback_client,
238                        agent_connection,
239                        initialize_response,
240                    ));
241                    return connect_client_protocol(ClientProtocol::V1, v1.create(), agent()).await;
242                }
243
244                client.send(initialize_response.into_message())?;
245                pipe_protocol_peers_until_done(client, agent_connection).await
246            }
247        }
248    }
249}
250
251#[cfg(feature = "unstable_protocol_v2")]
252struct DynConnectToFactory<R: Role> {
253    inner: Box<dyn FnMut() -> DynConnectTo<R> + Send>,
254}
255
256#[cfg(feature = "unstable_protocol_v2")]
257impl<R: Role> DynConnectToFactory<R> {
258    fn new<C>(mut factory: impl FnMut() -> C + Send + 'static) -> Self
259    where
260        C: ConnectTo<R>,
261    {
262        Self {
263            inner: Box::new(move || DynConnectTo::new(factory())),
264        }
265    }
266
267    fn create(&mut self) -> DynConnectTo<R> {
268        (self.inner)()
269    }
270}
271
272#[cfg(feature = "unstable_protocol_v2")]
273impl<R: Role> Debug for DynConnectToFactory<R> {
274    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
275        f.debug_struct("DynConnectToFactory")
276            .finish_non_exhaustive()
277    }
278}
279
280impl HasPeer<Client> for Client {
281    fn remote_style(&self, _peer: Client) -> RemoteStyle {
282        RemoteStyle::Counterpart
283    }
284}
285
286/// The agent role - typically an LLM that responds to prompts.
287///
288/// Agents receive prompts from clients and respond with answers,
289/// potentially invoking tools along the way.
290#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
291pub struct Agent;
292
293impl Role for Agent {
294    type Counterpart = Client;
295
296    fn builder(self) -> Builder<Self> {
297        Builder::new(self).v1_agent()
298    }
299
300    fn role_id(&self) -> RoleId {
301        RoleId::from_singleton(self)
302    }
303
304    fn counterpart(&self) -> Self::Counterpart {
305        Client
306    }
307
308    async fn default_handle_dispatch_from(
309        &self,
310        message: Dispatch,
311        connection: ConnectionTo<Agent>,
312    ) -> Result<Handled<Dispatch>, crate::Error> {
313        MatchDispatchFrom::new(message, &connection)
314            .if_dispatch_from(Agent, async |message: Dispatch| {
315                // Stable v1 session helpers install a dynamic handler after
316                // `session/new`. Retry session messages to close the race
317                // between the response and that registration.
318                //
319                // V2 uses typed handlers installed before the connection
320                // starts. Retrying an unhandled v2 message would retain it
321                // forever because no per-session dynamic handler is expected.
322                #[cfg(feature = "unstable_protocol_v2")]
323                let retry = message.has_session_id()
324                    && connection.acp_protocol_version()
325                        != Some(crate::schema::ProtocolVersion::V2);
326                #[cfg(not(feature = "unstable_protocol_v2"))]
327                let retry = message.has_session_id();
328                Ok(Handled::No { message, retry })
329            })
330            .await
331            .done()
332    }
333}
334
335impl Agent {
336    /// Create a connection builder for an agent.
337    pub fn builder(self) -> Builder<Agent, NullHandler, NullRun> {
338        <Self as Role>::builder(self)
339    }
340
341    /// Create an agent builder that uses the ACP protocol v2 API.
342    ///
343    /// This builder requires clients to negotiate protocol v2 during
344    /// initialization. Use a v1 builder for v1 clients.
345    ///
346    /// Requires the `unstable_protocol_v2` crate feature.
347    #[cfg(feature = "unstable_protocol_v2")]
348    pub fn v2(self) -> V2Builder<Agent, NullHandler, NullRun> {
349        self.builder().v2_agent()
350    }
351
352    /// Create a router that chooses between configured protocol implementations.
353    ///
354    /// Add implementations with [`AgentProtocolRouter::with_v1`] and
355    /// [`AgentProtocolRouter::with_v2`].
356    /// The resulting router reads the initial
357    /// `initialize` request, selects the highest configured implementation
358    /// compatible with the client's requested protocol version, then forwards
359    /// the connection to that implementation. It does not convert traffic
360    /// between protocol versions after routing.
361    ///
362    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
363    /// stabilizes.
364    #[cfg(feature = "unstable_protocol_v2")]
365    #[must_use]
366    pub fn protocol_router(self) -> AgentProtocolRouter {
367        AgentProtocolRouter::new()
368    }
369}
370
371/// Agent component that routes each connection to a configured protocol implementation.
372///
373/// Use [`Agent::protocol_router`] to start the builder, then add each supported
374/// protocol version independently. The selected implementation owns the
375/// connection after the initial `initialize` negotiation.
376#[cfg(feature = "unstable_protocol_v2")]
377#[derive(Debug, Default)]
378pub struct AgentProtocolRouter {
379    v1: Option<DynConnectTo<Client>>,
380    v2: Option<DynConnectTo<Client>>,
381}
382
383#[cfg(feature = "unstable_protocol_v2")]
384impl AgentProtocolRouter {
385    /// Create an empty agent protocol router.
386    #[must_use]
387    pub fn new() -> Self {
388        Self::default()
389    }
390
391    /// Return this router with an ACP v1 implementation configured.
392    #[must_use]
393    pub fn with_v1(mut self, agent: impl ConnectTo<Client>) -> Self {
394        self.v1 = Some(DynConnectTo::new(agent));
395        self
396    }
397
398    /// Return this router with an ACP v2 implementation configured.
399    #[must_use]
400    pub fn with_v2(mut self, agent: impl ConnectTo<Client>) -> Self {
401        self.v2 = Some(DynConnectTo::new(agent));
402        self
403    }
404}
405
406#[cfg(feature = "unstable_protocol_v2")]
407impl ConnectTo<Client> for AgentProtocolRouter {
408    async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), crate::Error> {
409        let supported = SupportedProtocols {
410            v1: self.v1.is_some(),
411            v2: self.v2.is_some(),
412        };
413        let mut client = RunningProtocolPeer::new(client);
414        let (first_frame, client, selected) = loop {
415            let Some((mut frame, next_client)) = client.next_frame().await? else {
416                return Ok(());
417            };
418            let message = match initialize_message_mut(&mut frame) {
419                Ok(Some(message)) => message,
420                Ok(None) => {
421                    client = next_client;
422                    continue;
423                }
424                Err(error) => return reject_initialize(next_client, &frame, error).await,
425            };
426            let selected = match select_agent_protocol(message, supported) {
427                Ok(selected) => selected,
428                Err(error) => return reject_initialize(next_client, &frame, error).await,
429            };
430            break (frame, next_client, selected);
431        };
432        let Some(agent) = selected.take_agent(self) else {
433            let error = selected.unsupported_error(supported);
434            return reject_initialize(client, &first_frame, error).await;
435        };
436
437        let agent = RunningProtocolPeer::new(agent);
438        agent.send_frame(first_frame)?;
439        pipe_protocol_peers_until_closed(client, agent).await
440    }
441}
442
443#[cfg(feature = "unstable_protocol_v2")]
444#[derive(Debug, Clone, Copy, PartialEq, Eq)]
445enum SelectedProtocol {
446    V1,
447    V2,
448}
449
450#[cfg(feature = "unstable_protocol_v2")]
451impl SelectedProtocol {
452    fn take_agent(self, agent: AgentProtocolRouter) -> Option<DynConnectTo<Client>> {
453        match self {
454            Self::V1 => agent.v1,
455            Self::V2 => agent.v2,
456        }
457    }
458
459    fn version(self) -> ProtocolVersion {
460        match self {
461            Self::V1 => ProtocolVersion::V1,
462            Self::V2 => ProtocolVersion::V2,
463        }
464    }
465
466    fn name(self) -> &'static str {
467        match self {
468            Self::V1 => "1",
469            Self::V2 => "2",
470        }
471    }
472
473    fn unsupported_error(self, supported: SupportedProtocols) -> crate::Error {
474        crate::Error::invalid_request().data(format!(
475            "ACP protocol version {} is not configured; this endpoint supports {}",
476            self.name(),
477            supported.description()
478        ))
479    }
480}
481
482#[cfg(feature = "unstable_protocol_v2")]
483#[derive(Debug, Clone, Copy, PartialEq, Eq)]
484struct SupportedProtocols {
485    v1: bool,
486    v2: bool,
487}
488
489#[cfg(feature = "unstable_protocol_v2")]
490impl SupportedProtocols {
491    fn highest_compatible(self, requested: ProtocolVersion) -> Option<SelectedProtocol> {
492        if self.v2 && requested >= ProtocolVersion::V2 {
493            return Some(SelectedProtocol::V2);
494        }
495
496        if self.v1 && requested >= ProtocolVersion::V1 {
497            return Some(SelectedProtocol::V1);
498        }
499
500        None
501    }
502
503    fn exact(self, requested: ProtocolVersion) -> Option<SelectedProtocol> {
504        if self.v1 && requested == ProtocolVersion::V1 {
505            Some(SelectedProtocol::V1)
506        } else if self.v2 && requested == ProtocolVersion::V2 {
507            Some(SelectedProtocol::V2)
508        } else {
509            None
510        }
511    }
512
513    fn description(self) -> String {
514        match (self.v1, self.v2) {
515            (true, true) => "ACP protocol versions 1 and 2".into(),
516            (true, false) => "ACP protocol version 1".into(),
517            (false, true) => "ACP protocol version 2".into(),
518            (false, false) => "no ACP protocol versions".into(),
519        }
520    }
521}
522
523#[cfg(feature = "unstable_protocol_v2")]
524fn select_agent_protocol(
525    message: &mut RawJsonRpcMessage,
526    supported: SupportedProtocols,
527) -> Result<SelectedProtocol, crate::Error> {
528    let RawJsonRpcMessage::Request(request) = message else {
529        return Err(
530            crate::Error::invalid_request().data("first ACP message must be an initialize request")
531        );
532    };
533
534    if request.method.as_ref() != "initialize" {
535        return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
536    }
537
538    let Some(RawJsonRpcParams::Object(params)) = &mut request.params else {
539        return Err(invalid_initialize_protocol_version());
540    };
541    let Some(protocol_version) = params.get("protocolVersion") else {
542        return Err(invalid_initialize_protocol_version());
543    };
544
545    let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
546        .map_err(|_| invalid_initialize_protocol_version())?;
547    let selected = highest_compatible_agent_protocol(requested, supported)?;
548    rewrite_initialize_params(params, requested, selected)?;
549
550    Ok(selected)
551}
552
553#[cfg(feature = "unstable_protocol_v2")]
554fn initialize_request_params(
555    message: &RawJsonRpcMessage,
556) -> Result<&serde_json::Map<String, serde_json::Value>, crate::Error> {
557    let RawJsonRpcMessage::Request(request) = message else {
558        return Err(
559            crate::Error::invalid_request().data("first ACP message must be an initialize request")
560        );
561    };
562
563    if request.method.as_ref() != "initialize" {
564        return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
565    }
566
567    let Some(RawJsonRpcParams::Object(params)) = &request.params else {
568        return Err(invalid_initialize_protocol_version());
569    };
570    if !params.contains_key("protocolVersion") {
571        return Err(invalid_initialize_protocol_version());
572    }
573    Ok(params)
574}
575
576#[cfg(feature = "unstable_protocol_v2")]
577fn validated_initialize_params<T: DeserializeOwned>(
578    message: &RawJsonRpcMessage,
579) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
580    let params = initialize_request_params(message)?;
581    parse_initialize_params::<T>(params)?;
582    Ok(params.clone())
583}
584
585#[cfg(feature = "unstable_protocol_v2")]
586fn normalize_v2_initialize_params_for_reuse(
587    message: &RawJsonRpcMessage,
588) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
589    let params = initialize_request_params(message)?;
590    let requested = params
591        .get("protocolVersion")
592        .cloned()
593        .ok_or_else(invalid_initialize_protocol_version)
594        .and_then(|version| {
595            serde_json::from_value::<ProtocolVersion>(version)
596                .map_err(|_| invalid_initialize_protocol_version())
597        })?;
598    if requested == ProtocolVersion::V1 {
599        parse_initialize_params::<InitializeRequest>(params)?;
600        return Ok(params.clone());
601    }
602    normalize_v2_initialize_params_for_v1(params, true)
603}
604
605#[cfg(feature = "unstable_protocol_v2")]
606fn rewrite_initialize_params(
607    params: &mut serde_json::Map<String, serde_json::Value>,
608    requested: ProtocolVersion,
609    selected: SelectedProtocol,
610) -> Result<(), crate::Error> {
611    // Validate exact-version initialization without replacing its raw
612    // parameters. Reserializing through the SDK's pinned schema would discard
613    // fields added by newer compatible peers.
614    if requested == selected.version() {
615        match selected {
616            SelectedProtocol::V1 => {
617                parse_initialize_params::<InitializeRequest>(params)?;
618            }
619            SelectedProtocol::V2 => {
620                parse_initialize_params::<v2::InitializeRequest>(params)?;
621            }
622        }
623        return Ok(());
624    }
625
626    match selected {
627        SelectedProtocol::V1 => {
628            debug_assert!(requested >= ProtocolVersion::V2);
629            *params = normalize_v2_initialize_params_for_v1(params, false)?;
630            Ok(())
631        }
632        SelectedProtocol::V2 => {
633            let mut initialize = parse_initialize_params::<v2::InitializeRequest>(params)?;
634            initialize.protocol_version = ProtocolVersion::V2;
635            *params = serialize_initialize_params(initialize)?;
636            Ok(())
637        }
638    }
639}
640
641#[cfg(feature = "unstable_protocol_v2")]
642fn normalize_v2_initialize_params_for_v1(
643    params: &serde_json::Map<String, serde_json::Value>,
644    require_lossless: bool,
645) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
646    // Canonicalize through v2 first so tolerant field semantics are applied.
647    // A lossless result is only required by the connection-reuse probe. Normal
648    // v1 routing may discard fields that have no meaning in the selected
649    // protocol version.
650    let initialize = parse_initialize_params::<v2::InitializeRequest>(params)?;
651    let mut target = serialize_initialize_params(initialize)?;
652    if require_lossless && target != *params {
653        return Err(invalid_initialize_params(
654            "v2 initialize parameters are not losslessly representable in v1",
655        ));
656    }
657
658    target.insert(
659        "protocolVersion".into(),
660        serde_json::to_value(ProtocolVersion::V1).map_err(crate::Error::into_internal_error)?,
661    );
662    let info = target
663        .remove("info")
664        .ok_or_else(|| invalid_initialize_params("v2 InitializeRequest.info is required"))?;
665    target.insert("clientInfo".into(), info);
666    let capabilities = target
667        .remove("capabilities")
668        .and_then(|capabilities| capabilities.as_object().cloned())
669        .ok_or_else(|| {
670            crate::util::internal_error("v2 initialize capabilities did not serialize as an object")
671        })?;
672    let mut capabilities = capabilities;
673    if let Some(auth) = capabilities
674        .get_mut("auth")
675        .and_then(serde_json::Value::as_object_mut)
676    {
677        let terminal = auth.remove("terminal");
678        if require_lossless
679            && terminal
680                .as_ref()
681                .and_then(serde_json::Value::as_object)
682                .is_some_and(|terminal| terminal.contains_key("_meta"))
683        {
684            return Err(invalid_initialize_params(
685                "v2 terminal authentication metadata is not representable in v1",
686            ));
687        }
688        auth.insert("terminal".into(), terminal.is_some().into());
689    }
690    capabilities.insert(
691        "session".into(),
692        serde_json::json!({ "configOptions": { "boolean": {} } }),
693    );
694    target.insert("clientCapabilities".into(), capabilities.into());
695
696    let initialize = parse_initialize_params::<InitializeRequest>(&target)?;
697    let normalized = serialize_initialize_params(initialize)?;
698    if require_lossless && !json_object_contains(&normalized, &target) {
699        return Err(invalid_initialize_params(
700            "v2 initialize parameters are not losslessly representable in v1",
701        ));
702    }
703    Ok(normalized)
704}
705
706#[cfg(all(test, feature = "unstable_protocol_v2"))]
707mod initialize_normalization_tests {
708    use super::*;
709
710    fn v2_initialize_params() -> serde_json::Map<String, serde_json::Value> {
711        let value = serde_json::to_value(v2::InitializeRequest::new(
712            ProtocolVersion::V2,
713            v2::Implementation::new("test-client", "1.0.0"),
714        ))
715        .expect("serialize v2 initialize request");
716        value
717            .as_object()
718            .expect("initialize params serialize as an object")
719            .clone()
720    }
721
722    #[test]
723    fn v2_tolerant_fields_are_canonicalized_before_v1_normalization() {
724        let mut params = v2_initialize_params();
725        params.insert(
726            "capabilities".into(),
727            serde_json::Value::String("malformed".into()),
728        );
729        params.insert(
730            "_meta".into(),
731            serde_json::Value::String("malformed".into()),
732        );
733
734        let normalized = normalize_v2_initialize_params_for_v1(&params, false)
735            .expect("tolerant v2 fields should normalize through their defaults");
736        let normalized = serde_json::Value::Object(normalized);
737
738        assert!(normalized.get("_meta").is_none());
739        assert_eq!(
740            normalized.pointer("/clientCapabilities/session/configOptions/boolean"),
741            Some(&serde_json::json!({}))
742        );
743    }
744
745    #[test]
746    fn noncanonical_v2_fields_disable_reuse_but_not_v1_routing() {
747        let mut params = v2_initialize_params();
748        params
749            .get_mut("info")
750            .and_then(serde_json::Value::as_object_mut)
751            .expect("v2 initialize info is an object")
752            .insert("buildCommit".into(), serde_json::json!("abc123"));
753
754        normalize_v2_initialize_params_for_v1(&params, false)
755            .expect("v1 routing may ignore parameters unavailable in v1");
756        normalize_v2_initialize_params_for_v1(&params, true)
757            .expect_err("connection reuse requires lossless normalization");
758    }
759
760    #[test]
761    fn v1_reuse_probe_preserves_raw_initialize_params() {
762        let mut params = v2_initialize_params();
763        params.insert(
764            "protocolVersion".into(),
765            serde_json::json!(ProtocolVersion::V1),
766        );
767        let message = RawJsonRpcMessage::request(
768            "initialize".into(),
769            serde_json::Value::Object(params.clone()),
770            RequestId::Number(1),
771        )
772        .expect("build initialize request");
773
774        let normalized = normalize_v2_initialize_params_for_reuse(&message)
775            .expect("v1-shaped initialize request should be valid");
776
777        assert_eq!(normalized, params);
778    }
779
780    #[test]
781    fn null_v2_terminal_marker_meta_is_omitted_before_v1_normalization() {
782        let mut params = v2_initialize_params();
783        params.insert(
784            "capabilities".into(),
785            serde_json::json!({
786                "auth": {
787                    "terminal": { "_meta": null }
788                }
789            }),
790        );
791
792        let normalized = normalize_v2_initialize_params_for_v1(&params, false)
793            .expect("null marker metadata is equivalent to omission");
794        let normalized = serde_json::Value::Object(normalized);
795
796        assert_eq!(
797            normalized.pointer("/clientCapabilities/auth/terminal"),
798            Some(&serde_json::Value::Bool(true))
799        );
800    }
801
802    #[test]
803    fn terminal_marker_metadata_disables_reuse_but_not_v1_routing() {
804        let mut params = v2_initialize_params();
805        params.insert(
806            "capabilities".into(),
807            serde_json::json!({
808                "auth": {
809                    "terminal": {
810                        "_meta": { "source": "test" }
811                    }
812                }
813            }),
814        );
815
816        normalize_v2_initialize_params_for_v1(&params, false)
817            .expect("v1 routing may discard terminal marker metadata");
818        normalize_v2_initialize_params_for_v1(&params, true)
819            .expect_err("connection reuse must preserve terminal marker metadata");
820    }
821}
822
823#[cfg(feature = "unstable_protocol_v2")]
824fn parse_initialize_params<T: DeserializeOwned>(
825    params: &serde_json::Map<String, serde_json::Value>,
826) -> Result<T, crate::Error> {
827    serde_json::from_value(serde_json::Value::Object(params.clone()))
828        .map_err(invalid_initialize_params)
829}
830
831#[cfg(feature = "unstable_protocol_v2")]
832fn serialize_initialize_params(
833    initialize: impl Serialize,
834) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
835    let value = serde_json::to_value(initialize).map_err(crate::Error::into_internal_error)?;
836    let serde_json::Value::Object(object) = value else {
837        return Err(crate::util::internal_error(
838            "initialize params did not serialize to an object",
839        ));
840    };
841    Ok(object)
842}
843
844#[cfg(feature = "unstable_protocol_v2")]
845fn json_object_contains(
846    actual: &serde_json::Map<String, serde_json::Value>,
847    expected: &serde_json::Map<String, serde_json::Value>,
848) -> bool {
849    fn contains(actual: &serde_json::Value, expected: &serde_json::Value) -> bool {
850        match (actual, expected) {
851            (serde_json::Value::Object(actual), serde_json::Value::Object(expected)) => expected
852                .iter()
853                .all(|(key, value)| actual.get(key).is_some_and(|item| contains(item, value))),
854            _ => actual == expected,
855        }
856    }
857
858    expected
859        .iter()
860        .all(|(key, value)| actual.get(key).is_some_and(|item| contains(item, value)))
861}
862
863#[cfg(feature = "unstable_protocol_v2")]
864fn highest_compatible_agent_protocol(
865    requested: ProtocolVersion,
866    supported: SupportedProtocols,
867) -> Result<SelectedProtocol, crate::Error> {
868    supported.highest_compatible(requested).ok_or_else(|| {
869        crate::Error::invalid_request().data(format!(
870            "unsupported ACP protocol version {requested}; this endpoint supports {}",
871            supported.description()
872        ))
873    })
874}
875
876#[cfg(feature = "unstable_protocol_v2")]
877fn invalid_initialize_protocol_version() -> crate::Error {
878    crate::Error::invalid_params()
879        .data("initialize.protocolVersion must be a valid ACP protocol version")
880}
881
882#[cfg(feature = "unstable_protocol_v2")]
883fn invalid_initialize_params(error: impl ToString) -> crate::Error {
884    crate::Error::invalid_params().data(format!("invalid initialize params: {}", error.to_string()))
885}
886
887#[cfg(feature = "unstable_protocol_v2")]
888fn send_initialize_error(
889    tx: &futures::channel::mpsc::UnboundedSender<TransportFrame>,
890    frame: &TransportFrame,
891    error: crate::Error,
892) -> Result<(), crate::Error> {
893    fn response_for_message(
894        entry: &RawJsonRpcMessage,
895        initialize_error: &crate::Error,
896    ) -> Option<RawJsonRpcMessage> {
897        match entry {
898            RawJsonRpcMessage::Request(request) => Some(RawJsonRpcMessage::response(
899                request.id.clone(),
900                Err(initialize_error.clone()),
901            )),
902            RawJsonRpcMessage::Notification(_) | RawJsonRpcMessage::Response(_) => None,
903        }
904    }
905
906    fn response_for_entry(
907        entry: &TransportBatchEntry,
908        initialize_error: &crate::Error,
909    ) -> Option<RawJsonRpcMessage> {
910        match entry {
911            TransportBatchEntry::Message(message) => {
912                response_for_message(message, initialize_error)
913            }
914            TransportBatchEntry::Malformed { raw, error } if !is_response_only_shape(raw) => Some(
915                RawJsonRpcMessage::response(RequestId::Null, Err(error.clone())),
916            ),
917            TransportBatchEntry::Malformed { .. } => None,
918        }
919    }
920
921    let response = match frame {
922        TransportFrame::Single(entry) => {
923            let Some(response) = response_for_message(entry, &error) else {
924                return Ok(());
925            };
926            TransportFrame::Single(response)
927        }
928        TransportFrame::Malformed { raw, error } if !raw_is_response_only_shape(raw) => {
929            TransportFrame::Single(RawJsonRpcMessage::response(
930                RequestId::Null,
931                Err(error.clone()),
932            ))
933        }
934        TransportFrame::Malformed { .. } => return Ok(()),
935        TransportFrame::Batch(batch) => {
936            let responses = batch
937                .entries()
938                .filter_map(|entry| response_for_entry(entry, &error))
939                .collect::<Vec<_>>();
940            let Some(responses) = TransportBatch::from_messages(responses) else {
941                return Ok(());
942            };
943            TransportFrame::Batch(responses)
944        }
945    };
946
947    tx.unbounded_send(response)
948        .map_err(crate::util::internal_error)
949}
950
951#[cfg(feature = "unstable_protocol_v2")]
952async fn reject_initialize(
953    client: RunningProtocolPeer,
954    frame: &TransportFrame,
955    error: crate::Error,
956) -> Result<(), crate::Error> {
957    let RunningProtocolPeer { mut rx, tx, future } = client;
958    send_initialize_error(&tx, frame, error)?;
959    drop(tx);
960
961    let drain_incoming = async move {
962        // Later input has no protocol meaning once initialization is rejected.
963        // Keep draining it only so the transport can flush the queued rejection;
964        // treating a malformed trailing frame as fatal would cancel that flush.
965        while rx.next().await.is_some() {}
966        Ok::<_, crate::Error>(())
967    };
968
969    let ((), ()) = futures::try_join!(future, drain_incoming)?;
970    Ok(())
971}
972
973#[cfg(feature = "unstable_protocol_v2")]
974struct RunningProtocolPeer {
975    rx: futures::channel::mpsc::UnboundedReceiver<TransportFrame>,
976    tx: futures::channel::mpsc::UnboundedSender<TransportFrame>,
977    future: crate::BoxFuture<'static, Result<(), crate::Error>>,
978}
979
980#[cfg(feature = "unstable_protocol_v2")]
981impl RunningProtocolPeer {
982    fn new<R: Role>(component: impl ConnectTo<R>) -> Self {
983        let (Channel { rx, tx }, future) = component.into_channel_and_future();
984        Self { rx, tx, future }
985    }
986
987    async fn next_frame(self) -> Result<Option<(TransportFrame, Self)>, crate::Error> {
988        let Self { mut rx, tx, future } = self;
989        match future::select(Box::pin(rx.next()), future).await {
990            future::Either::Left((Some(frame), future)) => {
991                Ok(Some((frame, Self { rx, tx, future })))
992            }
993            future::Either::Left((None, future)) => {
994                future.await?;
995                Ok(None)
996            }
997            future::Either::Right((result, next_message)) => {
998                result?;
999                drop(next_message);
1000                let Some(frame) = rx.next().await else {
1001                    return Ok(None);
1002                };
1003                Ok(Some((
1004                    frame,
1005                    Self {
1006                        rx,
1007                        tx,
1008                        future: Box::pin(future::ready(Ok(()))),
1009                    },
1010                )))
1011            }
1012        }
1013    }
1014
1015    async fn next_message(self) -> Result<Option<(RawJsonRpcMessage, Self)>, crate::Error> {
1016        let Some((frame, peer)) = self.next_frame().await? else {
1017            return Ok(None);
1018        };
1019        Ok(Some((initialize_message(frame)?, peer)))
1020    }
1021
1022    fn send(&self, message: RawJsonRpcMessage) -> Result<(), crate::Error> {
1023        self.send_frame(TransportFrame::Single(message))
1024    }
1025
1026    fn send_frame(&self, frame: TransportFrame) -> Result<(), crate::Error> {
1027        self.tx
1028            .unbounded_send(frame)
1029            .map_err(crate::util::internal_error)
1030    }
1031}
1032
1033#[cfg(feature = "unstable_protocol_v2")]
1034fn initialize_message(frame: TransportFrame) -> Result<RawJsonRpcMessage, crate::Error> {
1035    match frame {
1036        TransportFrame::Single(message) => Ok(message),
1037        TransportFrame::Malformed { error, .. } => Err(error),
1038        TransportFrame::Batch(_) => Err(crate::Error::invalid_request()
1039            .data("ACP initialize request and response messages must be sent individually")),
1040    }
1041}
1042
1043#[cfg(feature = "unstable_protocol_v2")]
1044fn initialize_message_mut(
1045    frame: &mut TransportFrame,
1046) -> Result<Option<&mut RawJsonRpcMessage>, crate::Error> {
1047    match frame {
1048        TransportFrame::Single(RawJsonRpcMessage::Response(_)) => Ok(None),
1049        TransportFrame::Single(entry) => Ok(Some(entry)),
1050        TransportFrame::Malformed { raw, .. } if raw_is_response_only_shape(raw) => Ok(None),
1051        TransportFrame::Malformed { error, .. } => Err(error.clone()),
1052        TransportFrame::Batch(batch) => {
1053            for entry in batch.entries_mut() {
1054                match entry {
1055                    TransportBatchEntry::Message(RawJsonRpcMessage::Response(_)) => {}
1056                    TransportBatchEntry::Message(message) => return Ok(Some(message)),
1057                    TransportBatchEntry::Malformed { raw, .. } if is_response_only_shape(raw) => {}
1058                    TransportBatchEntry::Malformed { error, .. } => return Err(error.clone()),
1059                }
1060            }
1061            Ok(None)
1062        }
1063    }
1064}
1065
1066#[cfg(feature = "unstable_protocol_v2")]
1067async fn pipe_protocol_peers_until_closed(
1068    left: RunningProtocolPeer,
1069    right: RunningProtocolPeer,
1070) -> Result<(), crate::Error> {
1071    let ((), (), (), ()) = futures::try_join!(
1072        left.future,
1073        right.future,
1074        Channel {
1075            rx: left.rx,
1076            tx: right.tx,
1077        }
1078        .copy(),
1079        Channel {
1080            rx: right.rx,
1081            tx: left.tx,
1082        }
1083        .copy(),
1084    )?;
1085
1086    Ok(())
1087}
1088
1089#[cfg(feature = "unstable_protocol_v2")]
1090async fn pipe_protocol_peers_until_done(
1091    left: RunningProtocolPeer,
1092    right: RunningProtocolPeer,
1093) -> Result<(), crate::Error> {
1094    let bridge = Box::pin(async move {
1095        let ((), ()) = futures::try_join!(
1096            Channel {
1097                rx: left.rx,
1098                tx: right.tx,
1099            }
1100            .copy(),
1101            Channel {
1102                rx: right.rx,
1103                tx: left.tx,
1104            }
1105            .copy(),
1106        )?;
1107        Ok(())
1108    });
1109
1110    match future::select(left.future, future::select(right.future, bridge)).await {
1111        future::Either::Left((result, _))
1112        | future::Either::Right((
1113            future::Either::Left((result, _)) | future::Either::Right((result, _)),
1114            _,
1115        )) => result,
1116    }
1117}
1118
1119#[cfg(feature = "unstable_protocol_v2")]
1120#[derive(Debug)]
1121struct InitializeResponse {
1122    id: RequestId,
1123    result: Result<serde_json::Value, crate::Error>,
1124}
1125
1126#[cfg(feature = "unstable_protocol_v2")]
1127impl InitializeResponse {
1128    fn from_message(message: RawJsonRpcMessage) -> Result<Self, crate::Error> {
1129        match message {
1130            RawJsonRpcMessage::Response(RpcResponse::Result { id, result }) => Ok(Self {
1131                id,
1132                result: Ok(result),
1133            }),
1134            RawJsonRpcMessage::Response(RpcResponse::Error { id, error }) => Ok(Self {
1135                id,
1136                result: Err(error),
1137            }),
1138            message => Err(crate::Error::invalid_request().data(format!(
1139                "first ACP response must be an initialize response, got {message:?}",
1140            ))),
1141        }
1142    }
1143
1144    fn into_message(self) -> RawJsonRpcMessage {
1145        RawJsonRpcMessage::response(self.id, self.result)
1146    }
1147
1148    fn with_id(self, id: RequestId) -> RawJsonRpcMessage {
1149        RawJsonRpcMessage::response(id, self.result)
1150    }
1151
1152    fn protocol_version(&self) -> Option<ProtocolVersion> {
1153        serde_json::from_value(self.result.as_ref().ok()?.get("protocolVersion")?.clone()).ok()
1154    }
1155}
1156
1157#[cfg(feature = "unstable_protocol_v2")]
1158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1159enum ClientProtocol {
1160    V1,
1161    V2,
1162}
1163
1164#[cfg(feature = "unstable_protocol_v2")]
1165impl ClientProtocol {
1166    fn name(self) -> &'static str {
1167        match self {
1168            Self::V1 => "1",
1169            Self::V2 => "2",
1170        }
1171    }
1172}
1173
1174#[cfg(feature = "unstable_protocol_v2")]
1175#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1176struct SupportedClientProtocols {
1177    v1: bool,
1178    v2: bool,
1179}
1180
1181#[cfg(feature = "unstable_protocol_v2")]
1182impl SupportedClientProtocols {
1183    fn highest_configured(self) -> Option<ClientProtocol> {
1184        if self.v2 {
1185            return Some(ClientProtocol::V2);
1186        }
1187
1188        if self.v1 {
1189            return Some(ClientProtocol::V1);
1190        }
1191
1192        None
1193    }
1194}
1195
1196#[cfg(feature = "unstable_protocol_v2")]
1197async fn start_client_protocol(
1198    protocol: ClientProtocol,
1199    client: DynConnectTo<Agent>,
1200) -> Result<(RunningProtocolPeer, RawJsonRpcMessage), crate::Error> {
1201    let client = RunningProtocolPeer::new(client);
1202    let Some((initialize, client)) = client.next_message().await? else {
1203        return Err(crate::Error::invalid_request().data(format!(
1204            "ACP protocol version {} client implementation ended before initialize",
1205            protocol.name()
1206        )));
1207    };
1208    ensure_client_initialize_request(protocol, &initialize)?;
1209    Ok((client, initialize))
1210}
1211
1212#[cfg(feature = "unstable_protocol_v2")]
1213async fn send_initialize_and_receive(
1214    client: RunningProtocolPeer,
1215    agent: RunningProtocolPeer,
1216    initialize: RawJsonRpcMessage,
1217) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
1218    agent.send(initialize)?;
1219    let Some((response, agent)) = agent.next_message().await? else {
1220        return Err(crate::Error::internal_error().data("agent closed before initialize response"));
1221    };
1222    let response = InitializeResponse::from_message(response)?;
1223    Ok((client, agent, response))
1224}
1225
1226#[cfg(feature = "unstable_protocol_v2")]
1227async fn initialize_client_protocol(
1228    protocol: ClientProtocol,
1229    client: DynConnectTo<Agent>,
1230    agent: impl ConnectTo<Client>,
1231) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
1232    let agent = RunningProtocolPeer::new(agent);
1233    let (client, initialize) = start_client_protocol(protocol, client).await?;
1234    send_initialize_and_receive(client, agent, initialize).await
1235}
1236
1237#[cfg(feature = "unstable_protocol_v2")]
1238async fn connect_client_protocol(
1239    protocol: ClientProtocol,
1240    client: DynConnectTo<Agent>,
1241    agent: impl ConnectTo<Client>,
1242) -> Result<(), crate::Error> {
1243    let (client, agent, initialize_response) =
1244        initialize_client_protocol(protocol, client, agent).await?;
1245    client.send(initialize_response.into_message())?;
1246    pipe_protocol_peers_until_done(client, agent).await
1247}
1248
1249#[cfg(feature = "unstable_protocol_v2")]
1250fn ensure_client_initialize_request(
1251    protocol: ClientProtocol,
1252    message: &RawJsonRpcMessage,
1253) -> Result<(), crate::Error> {
1254    let RawJsonRpcMessage::Request(request) = message else {
1255        return Err(crate::Error::invalid_request().data(format!(
1256            "ACP protocol version {} client implementation must send initialize first",
1257            protocol.name()
1258        )));
1259    };
1260
1261    if request.method.as_ref() != "initialize" {
1262        return Err(crate::Error::invalid_request().data(format!(
1263            "ACP protocol version {} client implementation must send initialize first",
1264            protocol.name()
1265        )));
1266    }
1267
1268    Ok(())
1269}
1270
1271#[cfg(feature = "unstable_protocol_v2")]
1272fn initialize_request_id(message: &RawJsonRpcMessage) -> Option<RequestId> {
1273    let RawJsonRpcMessage::Request(request) = message else {
1274        return None;
1275    };
1276    Some(request.id.clone())
1277}
1278
1279#[cfg(feature = "unstable_protocol_v2")]
1280fn initialize_response_negotiated_v1(response: &InitializeResponse) -> bool {
1281    response.protocol_version() == Some(ProtocolVersion::V1)
1282}
1283
1284impl HasPeer<Agent> for Agent {
1285    fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1286        RemoteStyle::Counterpart
1287    }
1288}
1289
1290/// The proxy role - an intermediary that can intercept and modify messages.
1291///
1292/// Proxies sit between a client and an agent (or another proxy), and can:
1293/// - Add tools via MCP servers
1294/// - Filter or transform messages
1295/// - Inject additional context
1296///
1297/// Proxies connect to a [`Conductor`] which orchestrates the proxy chain.
1298#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1299pub struct Proxy;
1300
1301impl Role for Proxy {
1302    type Counterpart = Conductor;
1303
1304    fn default_handle_dispatch_from(
1305        &self,
1306        message: crate::Dispatch,
1307        _connection: crate::ConnectionTo<Self>,
1308    ) -> impl Future<Output = Result<crate::Handled<crate::Dispatch>, crate::Error>> + Send {
1309        std::future::ready(Ok(Handled::No {
1310            message,
1311            retry: false,
1312        }))
1313    }
1314
1315    fn role_id(&self) -> RoleId {
1316        RoleId::from_singleton(self)
1317    }
1318
1319    fn counterpart(&self) -> Self::Counterpart {
1320        Conductor
1321    }
1322}
1323
1324impl Proxy {
1325    /// Create a stable protocol v1 connection builder for a proxy.
1326    ///
1327    /// Use `Proxy::v2` for a protocol-v2-only proxy with typed callbacks and
1328    /// wire validation. Protocol-routing infrastructure that deliberately
1329    /// selects a version itself can disable the guard with
1330    /// `Builder::without_acp_version_guard`.
1331    pub fn builder(self) -> Builder<Proxy, NullHandler, NullRun> {
1332        Builder::new(self)
1333    }
1334
1335    /// Create a proxy builder that uses the ACP protocol v2 API.
1336    ///
1337    /// This builder requires `_proxy/initialize` to select protocol v2.
1338    /// Fluent callbacks receive [`crate::V2ConnectionTo<Conductor>`], while
1339    /// low-level custom handlers and runners retain the protocol-neutral
1340    /// [`ConnectionTo`] interface.
1341    ///
1342    /// Requires the `unstable_protocol_v2` crate feature.
1343    #[cfg(feature = "unstable_protocol_v2")]
1344    pub fn v2(self) -> V2Builder<Proxy, NullHandler, NullRun> {
1345        self.builder().v2_proxy()
1346    }
1347
1348    /// Create a router that chooses between configured proxy implementations.
1349    ///
1350    /// Add implementations with [`ProxyProtocolRouter::with_v1`] and
1351    /// [`ProxyProtocolRouter::with_v2`]. The router reads the initial
1352    /// `_proxy/initialize` request, selects the implementation for that exact
1353    /// protocol version, and hands over the complete initial transport frame.
1354    /// It does not downgrade proxy traffic or convert later messages.
1355    ///
1356    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
1357    /// stabilizes.
1358    #[cfg(feature = "unstable_protocol_v2")]
1359    #[must_use]
1360    pub fn protocol_router(self) -> ProxyProtocolRouter {
1361        ProxyProtocolRouter::new()
1362    }
1363}
1364
1365/// Proxy component that routes each connection to a configured protocol implementation.
1366///
1367/// Use [`Proxy::protocol_router`] to start the builder, then add stable-v1 and
1368/// draft-v2 proxy implementations independently. Unlike
1369/// [`AgentProtocolRouter`], this router requires an exact version match: the
1370/// conductor has already selected and canonicalized the wire protocol before
1371/// sending `_proxy/initialize` to a proxy.
1372#[cfg(feature = "unstable_protocol_v2")]
1373#[derive(Debug, Default)]
1374pub struct ProxyProtocolRouter {
1375    v1: Option<DynConnectTo<Conductor>>,
1376    v2: Option<DynConnectTo<Conductor>>,
1377}
1378
1379#[cfg(feature = "unstable_protocol_v2")]
1380impl ProxyProtocolRouter {
1381    /// Create an empty proxy protocol router.
1382    #[must_use]
1383    pub fn new() -> Self {
1384        Self::default()
1385    }
1386
1387    /// Return this router with a stable ACP v1 proxy implementation.
1388    #[must_use]
1389    pub fn with_v1(mut self, proxy: impl ConnectTo<Conductor>) -> Self {
1390        self.v1 = Some(DynConnectTo::new(proxy));
1391        self
1392    }
1393
1394    /// Return this router with a draft ACP v2 proxy implementation.
1395    #[must_use]
1396    pub fn with_v2(mut self, proxy: impl ConnectTo<Conductor>) -> Self {
1397        self.v2 = Some(DynConnectTo::new(proxy));
1398        self
1399    }
1400}
1401
1402#[cfg(feature = "unstable_protocol_v2")]
1403impl ConnectTo<Conductor> for ProxyProtocolRouter {
1404    async fn connect_to(self, conductor: impl ConnectTo<Proxy>) -> Result<(), crate::Error> {
1405        let supported = SupportedProtocols {
1406            v1: self.v1.is_some(),
1407            v2: self.v2.is_some(),
1408        };
1409        let mut conductor = RunningProtocolPeer::new(conductor);
1410        let (first_frame, conductor, selected) = loop {
1411            let Some((mut frame, next_conductor)) = conductor.next_frame().await? else {
1412                return Ok(());
1413            };
1414            let message = match initialize_message_mut(&mut frame) {
1415                Ok(Some(message)) => message,
1416                Ok(None) => {
1417                    conductor = next_conductor;
1418                    continue;
1419                }
1420                Err(error) => return reject_initialize(next_conductor, &frame, error).await,
1421            };
1422            let selected = match select_proxy_protocol(message, supported) {
1423                Ok(selected) => selected,
1424                Err(error) => return reject_initialize(next_conductor, &frame, error).await,
1425            };
1426            break (frame, next_conductor, selected);
1427        };
1428        let Some(proxy) = selected.take_proxy(self) else {
1429            let error = selected.unsupported_error(supported);
1430            return reject_initialize(conductor, &first_frame, error).await;
1431        };
1432
1433        let proxy = RunningProtocolPeer::new(proxy);
1434        proxy.send_frame(first_frame)?;
1435        pipe_protocol_peers_until_closed(conductor, proxy).await
1436    }
1437}
1438
1439#[cfg(feature = "unstable_protocol_v2")]
1440impl SelectedProtocol {
1441    fn take_proxy(self, proxy: ProxyProtocolRouter) -> Option<DynConnectTo<Conductor>> {
1442        match self {
1443            Self::V1 => proxy.v1,
1444            Self::V2 => proxy.v2,
1445        }
1446    }
1447}
1448
1449#[cfg(feature = "unstable_protocol_v2")]
1450fn select_proxy_protocol(
1451    message: &RawJsonRpcMessage,
1452    supported: SupportedProtocols,
1453) -> Result<SelectedProtocol, crate::Error> {
1454    let RawJsonRpcMessage::Request(request) = message else {
1455        return Err(crate::Error::invalid_request()
1456            .data("first ACP proxy message must be an `_proxy/initialize` request"));
1457    };
1458
1459    if request.method.as_ref() != METHOD_INITIALIZE_PROXY {
1460        return Err(crate::Error::invalid_request()
1461            .data("first ACP proxy request must be `_proxy/initialize`"));
1462    }
1463
1464    let Some(RawJsonRpcParams::Object(params)) = &request.params else {
1465        return Err(invalid_initialize_protocol_version());
1466    };
1467    let Some(protocol_version) = params.get("protocolVersion") else {
1468        return Err(invalid_initialize_protocol_version());
1469    };
1470    let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
1471        .map_err(|_| invalid_initialize_protocol_version())?;
1472    let selected = supported.exact(requested).ok_or_else(|| {
1473        crate::Error::invalid_request().data(format!(
1474            "unsupported ACP protocol version {requested}; this proxy supports {}",
1475            supported.description()
1476        ))
1477    })?;
1478
1479    match selected {
1480        SelectedProtocol::V1 => {
1481            parse_initialize_params::<crate::schema::InitializeProxyRequest>(params)?;
1482        }
1483        SelectedProtocol::V2 => {
1484            parse_initialize_params::<v2::InitializeProxyRequest>(params)?;
1485        }
1486    }
1487    Ok(selected)
1488}
1489
1490impl HasPeer<Proxy> for Proxy {
1491    fn remote_style(&self, _peer: Proxy) -> RemoteStyle {
1492        RemoteStyle::Counterpart
1493    }
1494}
1495
1496/// The conductor role - orchestrates proxy chains.
1497///
1498/// Conductors manage connections between clients, proxies, and agents,
1499/// routing messages through the appropriate proxy chain.
1500#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1501pub struct Conductor;
1502
1503impl Role for Conductor {
1504    type Counterpart = Proxy;
1505
1506    fn role_id(&self) -> RoleId {
1507        RoleId::from_singleton(self)
1508    }
1509
1510    fn counterpart(&self) -> Self::Counterpart {
1511        Proxy
1512    }
1513
1514    async fn default_handle_dispatch_from(
1515        &self,
1516        message: Dispatch,
1517        cx: ConnectionTo<Conductor>,
1518    ) -> Result<Handled<Dispatch>, crate::Error> {
1519        #[cfg(not(feature = "unstable_protocol_v2"))]
1520        {
1521            MatchDispatchFrom::new(message, &cx)
1522                .if_request_from(Client, async |_req: InitializeRequest, responder| {
1523                    responder.respond_with_error(crate::Error::invalid_request().data(format!(
1524                        "proxies must be initialized with `{METHOD_INITIALIZE_PROXY}`"
1525                    )))
1526                })
1527                .await
1528                .if_request_from(
1529                    Client,
1530                    async |request: InitializeProxyRequest, responder| {
1531                        let InitializeProxyRequest { initialize } = request;
1532                        cx.send_ordered_request_to(Agent, initialize)
1533                            .forward_response_to(responder)
1534                    },
1535                )
1536                .await
1537                .if_request_from(Client, async |request: NewSessionRequest, responder| {
1538                    let sent = cx.send_ordered_request_to(Agent, request);
1539                    let sent = sent.forward_cancellation_from(responder.cancellation());
1540                    sent.on_receiving_result({
1541                        let cx = cx.clone();
1542                        async move |result| {
1543                            if let Ok(NewSessionResponse { session_id, .. }) = &result {
1544                                cx.add_dynamic_handler(ProxySessionMessages::new(
1545                                    session_id.clone(),
1546                                ))?
1547                                .detach();
1548                            }
1549                            responder.respond_with_result(result)
1550                        }
1551                    })
1552                })
1553                .await
1554                .if_dispatch_from(Client, async |message: Dispatch| {
1555                    cx.send_proxied_message_to(Agent, message)
1556                })
1557                .await
1558                .if_dispatch_from(Agent, async |message: Dispatch| {
1559                    cx.send_proxied_message_to(Client, message)
1560                })
1561                .await
1562                .done()
1563        }
1564
1565        #[cfg(feature = "unstable_protocol_v2")]
1566        {
1567            let message = match message {
1568                Dispatch::Request(request, responder) if request.method() == "initialize" => {
1569                    responder.respond_with_error(crate::Error::invalid_request().data(format!(
1570                        "proxies must be initialized with `{METHOD_INITIALIZE_PROXY}`"
1571                    )))?;
1572                    return Ok(Handled::Yes);
1573                }
1574                Dispatch::Request(mut request, responder)
1575                    if request.method() == METHOD_INITIALIZE_PROXY =>
1576                {
1577                    request.method = "initialize".to_string();
1578                    cx.send_ordered_request_to(Agent, request)
1579                        .forward_response_to(responder)?;
1580                    return Ok(Handled::Yes);
1581                }
1582                Dispatch::Request(request, responder) if request.method() == "session/new" => {
1583                    let sent = cx.send_ordered_request_to(Agent, request);
1584                    // The dynamic-handler hook below means we cannot use
1585                    // `forward_response_to`, so wire up cancellation forwarding
1586                    // explicitly to keep `session/new` cancellable like every
1587                    // other proxied request.
1588                    let sent = sent.forward_cancellation_from(responder.cancellation());
1589                    sent.on_receiving_result({
1590                        let cx = cx.clone();
1591                        async move |result| {
1592                            let result = result.and_then(|response| {
1593                                let envelope: NewSessionResponseEnvelope =
1594                                    crate::util::json_cast(response.clone())?;
1595                                cx.add_dynamic_handler(ProxySessionMessages::new(
1596                                    envelope.session_id,
1597                                ))?
1598                                .detach();
1599                                Ok(response)
1600                            });
1601                            responder.respond_with_result(result)
1602                        }
1603                    })?;
1604                    return Ok(Handled::Yes);
1605                }
1606                message => message,
1607            };
1608
1609            MatchDispatchFrom::new(message, &cx)
1610                .if_dispatch_from(Client, async |message: Dispatch| {
1611                    cx.send_proxied_message_to(Agent, message)
1612                })
1613                .await
1614                .if_dispatch_from(Agent, async |message: Dispatch| {
1615                    cx.send_proxied_message_to(Client, message)
1616                })
1617                .await
1618                .done()
1619        }
1620    }
1621}
1622
1623impl Conductor {
1624    /// Create a connection builder for a conductor.
1625    pub fn builder(self) -> Builder<Conductor, NullHandler, NullRun> {
1626        Builder::new(self)
1627    }
1628}
1629
1630impl HasPeer<Client> for Conductor {
1631    fn remote_style(&self, _peer: Client) -> RemoteStyle {
1632        RemoteStyle::Predecessor
1633    }
1634}
1635
1636impl HasPeer<Agent> for Conductor {
1637    fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1638        RemoteStyle::Successor
1639    }
1640}
1641
1642/// Dynamic handler that proxies session messages from Agent to Client.
1643///
1644/// This is used internally to handle session message routing after a
1645/// `session.new` request has been forwarded.
1646pub(crate) struct ProxySessionMessages {
1647    session_id: SessionId,
1648}
1649
1650impl ProxySessionMessages {
1651    /// Create a new proxy handler for the given session.
1652    pub fn new(session_id: SessionId) -> Self {
1653        Self { session_id }
1654    }
1655}
1656
1657impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for ProxySessionMessages
1658where
1659    Counterpart: HasPeer<Agent> + HasPeer<Client>,
1660{
1661    async fn handle_dispatch_from(
1662        &mut self,
1663        message: Dispatch,
1664        connection: ConnectionTo<Counterpart>,
1665    ) -> Result<Handled<Dispatch>, crate::Error> {
1666        MatchDispatchFrom::new(message, &connection)
1667            .if_dispatch_from(Agent, async |message| {
1668                // If this is for our session-id, proxy it to the client.
1669                if let Some(session_id) = message.get_session_id()?
1670                    && session_id == self.session_id
1671                {
1672                    connection.send_proxied_message_to(Client, message)?;
1673                    return Ok(Handled::Yes);
1674                }
1675
1676                // Otherwise, leave it alone.
1677                Ok(Handled::No {
1678                    message,
1679                    retry: false,
1680                })
1681            })
1682            .await
1683            .done()
1684    }
1685
1686    fn describe_chain(&self) -> impl std::fmt::Debug {
1687        format!("ProxySessionMessages({})", self.session_id)
1688    }
1689}