Skip to main content

agent_client_protocol/role/
acp.rs

1use std::{fmt::Debug, 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};
11use crate::role::{HasPeer, RemoteStyle};
12use crate::schema::v1::{InitializeRequest, NewSessionRequest, NewSessionResponse, SessionId};
13#[cfg(feature = "unstable_protocol_v2")]
14use crate::schema::v1::{RequestId, Response as RpcResponse};
15use crate::schema::{InitializeProxyRequest, METHOD_INITIALIZE_PROXY};
16#[cfg(feature = "unstable_protocol_v2")]
17use crate::schema::{ProtocolVersion, v2};
18use crate::util::MatchDispatchFrom;
19#[cfg(feature = "unstable_protocol_v2")]
20use crate::{Channel, RawJsonRpcMessage, RawJsonRpcParams};
21use crate::{ConnectTo, ConnectionTo, Dispatch, HandleDispatchFrom, Handled, Role, RoleId};
22
23/// The client role - typically an IDE or CLI that controls an agent.
24///
25/// Clients send prompts and receive responses from agents.
26#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
27pub struct Client;
28
29impl Role for Client {
30    type Counterpart = Agent;
31
32    fn builder(self) -> Builder<Self> {
33        Builder::new(self).v1_client()
34    }
35
36    async fn default_handle_dispatch_from(
37        &self,
38        message: Dispatch,
39        _connection: ConnectionTo<Client>,
40    ) -> Result<Handled<Dispatch>, crate::Error> {
41        Ok(Handled::No {
42            message,
43            retry: false,
44        })
45    }
46
47    fn role_id(&self) -> RoleId {
48        RoleId::from_singleton(self)
49    }
50
51    fn counterpart(&self) -> Self::Counterpart {
52        Agent
53    }
54}
55
56impl Client {
57    /// Create a connection builder for a client.
58    pub fn builder(self) -> Builder<Client, NullHandler, NullRun> {
59        <Self as Role>::builder(self)
60    }
61
62    /// Create a client builder that requires an ACP protocol v2 agent.
63    ///
64    /// If the agent negotiates v1 during initialization, the initialize
65    /// request resolves with an error so callers can choose an explicit v1
66    /// fallback path.
67    ///
68    /// Requires the `unstable_protocol_v2` crate feature.
69    #[cfg(feature = "unstable_protocol_v2")]
70    pub fn v2(self) -> Builder<Client, NullHandler, NullRun> {
71        self.builder().v2_client()
72    }
73
74    /// Create a connector that chooses between configured protocol implementations.
75    ///
76    /// Add implementation factories with [`ClientProtocolConnector::with_v1`]
77    /// and [`ClientProtocolConnector::with_v2`]. The resulting connector starts
78    /// the highest configured protocol implementation. If a v2 implementation
79    /// successfully negotiates v1 and a v1 implementation is configured, the
80    /// connector reuses the connection only when the v1 implementation's
81    /// `initialize` metadata and capabilities match what the agent already saw;
82    /// otherwise it opens a fresh agent connection and restarts with v1.
83    ///
84    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
85    /// stabilizes.
86    #[cfg(feature = "unstable_protocol_v2")]
87    #[must_use]
88    pub fn protocol_connector(self) -> ClientProtocolConnector {
89        ClientProtocolConnector::new()
90    }
91
92    /// Connect to `agent` and run `main_fn` with the [`ConnectionTo`].
93    /// Returns the result of `main_fn` (or an error if something goes wrong).
94    ///
95    /// Equivalent to `self.builder().connect_with(agent, main_fn)`.
96    pub async fn connect_with<R>(
97        self,
98        agent: impl ConnectTo<Client>,
99        main_fn: impl AsyncFnOnce(ConnectionTo<Agent>) -> Result<R, crate::Error>,
100    ) -> Result<R, crate::Error> {
101        self.builder().connect_with(agent, main_fn).await
102    }
103}
104
105/// Client connector that opens an agent connection with a configured protocol implementation.
106///
107/// Use [`Client::protocol_connector`] to start the builder, then add each
108/// supported protocol version independently. Implementations and the agent
109/// connection are provided as factories because fallback from v2 to v1 may
110/// require a fresh connection initialized by the v1 implementation.
111#[cfg(feature = "unstable_protocol_v2")]
112#[derive(Debug, Default)]
113pub struct ClientProtocolConnector {
114    v1: Option<DynConnectToFactory<Agent>>,
115    v2: Option<DynConnectToFactory<Agent>>,
116}
117
118#[cfg(feature = "unstable_protocol_v2")]
119impl ClientProtocolConnector {
120    /// Create an empty client protocol connector.
121    #[must_use]
122    pub fn new() -> Self {
123        Self::default()
124    }
125
126    /// Return this connector with an ACP v1 implementation factory configured.
127    #[must_use]
128    pub fn with_v1<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
129    where
130        C: ConnectTo<Agent>,
131    {
132        self.v1 = Some(DynConnectToFactory::new(client));
133        self
134    }
135
136    /// Return this connector with an ACP v2 implementation factory configured.
137    #[must_use]
138    pub fn with_v2<C>(mut self, client: impl FnMut() -> C + Send + 'static) -> Self
139    where
140        C: ConnectTo<Agent>,
141    {
142        self.v2 = Some(DynConnectToFactory::new(client));
143        self
144    }
145
146    /// Connect to an agent produced by `agent` using the highest configured
147    /// compatible protocol implementation.
148    pub async fn connect_to<C>(
149        mut self,
150        mut agent: impl FnMut() -> C + Send + 'static,
151    ) -> Result<(), crate::Error>
152    where
153        C: ConnectTo<Client>,
154    {
155        let supported = SupportedClientProtocols {
156            v1: self.v1.is_some(),
157            v2: self.v2.is_some(),
158        };
159        let Some(selected) = supported.highest_configured() else {
160            return Err(crate::Error::invalid_request()
161                .data("client protocol connector has no configured ACP protocol implementations"));
162        };
163
164        match selected {
165            ClientProtocol::V1 => {
166                let client = self
167                    .v1
168                    .as_mut()
169                    .expect("selected protocol is configured")
170                    .create();
171                connect_client_protocol(ClientProtocol::V1, client, agent()).await
172            }
173            ClientProtocol::V2 => {
174                let client = self
175                    .v2
176                    .as_mut()
177                    .expect("selected protocol is configured")
178                    .create();
179                let agent_connection = RunningProtocolPeer::new(agent());
180                let (client, initialize) =
181                    start_client_protocol(ClientProtocol::V2, client).await?;
182                let v2_initialize_as_v1 =
183                    normalize_initialize_params_for_agent_protocol(&initialize, AgentProtocol::V1)?;
184                let (client, agent_connection, initialize_response) =
185                    send_initialize_and_receive(client, agent_connection, initialize).await?;
186
187                if initialize_response_negotiated_v1(&initialize_response)
188                    && let Some(v1) = self.v1.as_mut()
189                {
190                    let fallback_client = v1.create();
191                    let (fallback_client, fallback_initialize) =
192                        start_client_protocol(ClientProtocol::V1, fallback_client).await?;
193                    let v1_initialize = normalize_initialize_params_for_agent_protocol(
194                        &fallback_initialize,
195                        AgentProtocol::V1,
196                    )?;
197
198                    if v1_initialize == v2_initialize_as_v1 {
199                        let fallback_response = initialize_response.with_id(
200                            initialize_request_id(&fallback_initialize)
201                                .expect("validated initialize request has an id"),
202                        );
203                        fallback_client.send(fallback_response)?;
204                        return pipe_protocol_peers_until_done(fallback_client, agent_connection)
205                            .await;
206                    }
207
208                    drop((client, agent_connection, initialize_response));
209                    return connect_client_protocol(ClientProtocol::V1, v1.create(), agent()).await;
210                }
211
212                client.send(initialize_response.into_message())?;
213                pipe_protocol_peers_until_done(client, agent_connection).await
214            }
215        }
216    }
217}
218
219#[cfg(feature = "unstable_protocol_v2")]
220struct DynConnectToFactory<R: Role> {
221    inner: Box<dyn FnMut() -> DynConnectTo<R> + Send>,
222}
223
224#[cfg(feature = "unstable_protocol_v2")]
225impl<R: Role> DynConnectToFactory<R> {
226    fn new<C>(mut factory: impl FnMut() -> C + Send + 'static) -> Self
227    where
228        C: ConnectTo<R>,
229    {
230        Self {
231            inner: Box::new(move || DynConnectTo::new(factory())),
232        }
233    }
234
235    fn create(&mut self) -> DynConnectTo<R> {
236        (self.inner)()
237    }
238}
239
240#[cfg(feature = "unstable_protocol_v2")]
241impl<R: Role> Debug for DynConnectToFactory<R> {
242    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243        f.debug_struct("DynConnectToFactory")
244            .finish_non_exhaustive()
245    }
246}
247
248impl HasPeer<Client> for Client {
249    fn remote_style(&self, _peer: Client) -> RemoteStyle {
250        RemoteStyle::Counterpart
251    }
252}
253
254/// The agent role - typically an LLM that responds to prompts.
255///
256/// Agents receive prompts from clients and respond with answers,
257/// potentially invoking tools along the way.
258#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
259pub struct Agent;
260
261impl Role for Agent {
262    type Counterpart = Client;
263
264    fn builder(self) -> Builder<Self> {
265        Builder::new(self).v1_agent()
266    }
267
268    fn role_id(&self) -> RoleId {
269        RoleId::from_singleton(self)
270    }
271
272    fn counterpart(&self) -> Self::Counterpart {
273        Client
274    }
275
276    async fn default_handle_dispatch_from(
277        &self,
278        message: Dispatch,
279        connection: ConnectionTo<Agent>,
280    ) -> Result<Handled<Dispatch>, crate::Error> {
281        MatchDispatchFrom::new(message, &connection)
282            .if_message_from(Agent, async |message: Dispatch| {
283                // Subtle: messages that have a session-id field
284                // should be captured by a dynamic message handler
285                // for that session -- but there is a race condition
286                // between the dynamic handler being added and
287                // possible updates. Therefore, we "retry" all such
288                // messages, so that they will be resent as new handlers
289                // are added.
290                let retry = message.has_session_id();
291                Ok(Handled::No { message, retry })
292            })
293            .await
294            .done()
295    }
296}
297
298impl Agent {
299    /// Create a connection builder for an agent.
300    pub fn builder(self) -> Builder<Agent, NullHandler, NullRun> {
301        <Self as Role>::builder(self)
302    }
303
304    /// Create an agent builder that uses the ACP protocol v2 API.
305    ///
306    /// This builder requires clients to negotiate protocol v2 during
307    /// initialization. Use a v1 builder for v1 clients.
308    ///
309    /// Requires the `unstable_protocol_v2` crate feature.
310    #[cfg(feature = "unstable_protocol_v2")]
311    pub fn v2(self) -> Builder<Agent, NullHandler, NullRun> {
312        self.builder().v2_agent()
313    }
314
315    /// Create a router that chooses between configured protocol implementations.
316    ///
317    /// Add implementations with [`AgentProtocolRouter::with_v1`] and
318    /// [`AgentProtocolRouter::with_v2`].
319    /// The resulting router reads the initial
320    /// `initialize` request, selects the highest configured implementation
321    /// compatible with the client's requested protocol version, then forwards
322    /// the connection to that implementation. It does not convert traffic
323    /// between protocol versions after routing.
324    ///
325    /// Requires the `unstable_protocol_v2` crate feature while protocol v2
326    /// stabilizes.
327    #[cfg(feature = "unstable_protocol_v2")]
328    #[must_use]
329    pub fn protocol_router(self) -> AgentProtocolRouter {
330        AgentProtocolRouter::new()
331    }
332}
333
334/// Agent component that routes each connection to a configured protocol implementation.
335///
336/// Use [`Agent::protocol_router`] to start the builder, then add each supported
337/// protocol version independently. The selected implementation owns the
338/// connection after the initial `initialize` negotiation.
339#[cfg(feature = "unstable_protocol_v2")]
340#[derive(Debug, Default)]
341pub struct AgentProtocolRouter {
342    v1: Option<DynConnectTo<Client>>,
343    v2: Option<DynConnectTo<Client>>,
344}
345
346#[cfg(feature = "unstable_protocol_v2")]
347impl AgentProtocolRouter {
348    /// Create an empty agent protocol router.
349    #[must_use]
350    pub fn new() -> Self {
351        Self::default()
352    }
353
354    /// Return this router with an ACP v1 implementation configured.
355    #[must_use]
356    pub fn with_v1(mut self, agent: impl ConnectTo<Client>) -> Self {
357        self.v1 = Some(DynConnectTo::new(agent));
358        self
359    }
360
361    /// Return this router with an ACP v2 implementation configured.
362    #[must_use]
363    pub fn with_v2(mut self, agent: impl ConnectTo<Client>) -> Self {
364        self.v2 = Some(DynConnectTo::new(agent));
365        self
366    }
367}
368
369#[cfg(feature = "unstable_protocol_v2")]
370impl ConnectTo<Client> for AgentProtocolRouter {
371    async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<(), crate::Error> {
372        let client = RunningProtocolPeer::new(client);
373        let Some((mut first_message, client)) = client.next_message().await? else {
374            return Ok(());
375        };
376        let supported = SupportedAgentProtocols {
377            v1: self.v1.is_some(),
378            v2: self.v2.is_some(),
379        };
380        let selected = match select_agent_protocol(&mut first_message, supported) {
381            Ok(selected) => selected,
382            Err(error) => {
383                return reject_initialize(client, &first_message, error).await;
384            }
385        };
386        let Some(agent) = selected.take_agent(self) else {
387            let error = selected.unsupported_error(supported);
388            return reject_initialize(client, &first_message, error).await;
389        };
390
391        let agent = RunningProtocolPeer::new(agent);
392        agent.send(first_message)?;
393        pipe_protocol_peers_until_closed(client, agent).await
394    }
395}
396
397#[cfg(feature = "unstable_protocol_v2")]
398#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399enum AgentProtocol {
400    V1,
401    V2,
402}
403
404#[cfg(feature = "unstable_protocol_v2")]
405impl AgentProtocol {
406    fn take_agent(self, agent: AgentProtocolRouter) -> Option<DynConnectTo<Client>> {
407        match self {
408            Self::V1 => agent.v1,
409            Self::V2 => agent.v2,
410        }
411    }
412
413    fn name(self) -> &'static str {
414        match self {
415            Self::V1 => "1",
416            Self::V2 => "2",
417        }
418    }
419
420    fn unsupported_error(self, supported: SupportedAgentProtocols) -> crate::Error {
421        crate::Error::invalid_request().data(format!(
422            "ACP protocol version {} is not configured; this endpoint supports {}",
423            self.name(),
424            supported.description()
425        ))
426    }
427}
428
429#[cfg(feature = "unstable_protocol_v2")]
430#[derive(Debug, Clone, Copy, PartialEq, Eq)]
431struct SupportedAgentProtocols {
432    v1: bool,
433    v2: bool,
434}
435
436#[cfg(feature = "unstable_protocol_v2")]
437impl SupportedAgentProtocols {
438    fn highest_compatible(self, requested: ProtocolVersion) -> Option<AgentProtocol> {
439        if self.v2 && requested >= ProtocolVersion::V2 {
440            return Some(AgentProtocol::V2);
441        }
442
443        if self.v1 && requested >= ProtocolVersion::V1 {
444            return Some(AgentProtocol::V1);
445        }
446
447        None
448    }
449
450    fn description(self) -> String {
451        match (self.v1, self.v2) {
452            (true, true) => "ACP protocol versions 1 and 2".into(),
453            (true, false) => "ACP protocol version 1".into(),
454            (false, true) => "ACP protocol version 2".into(),
455            (false, false) => "no ACP protocol versions".into(),
456        }
457    }
458}
459
460#[cfg(feature = "unstable_protocol_v2")]
461fn select_agent_protocol(
462    message: &mut RawJsonRpcMessage,
463    supported: SupportedAgentProtocols,
464) -> Result<AgentProtocol, crate::Error> {
465    let RawJsonRpcMessage::Request(request) = message else {
466        return Err(
467            crate::Error::invalid_request().data("first ACP message must be an initialize request")
468        );
469    };
470
471    if request.method.as_ref() != "initialize" {
472        return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
473    }
474
475    let Some(RawJsonRpcParams::Object(params)) = &mut request.params else {
476        return Err(invalid_initialize_protocol_version());
477    };
478    let Some(protocol_version) = params.get("protocolVersion") else {
479        return Err(invalid_initialize_protocol_version());
480    };
481
482    let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
483        .map_err(|_| invalid_initialize_protocol_version())?;
484    let selected = highest_compatible_agent_protocol(requested, supported)?;
485    rewrite_initialize_params(params, requested, selected)?;
486
487    Ok(selected)
488}
489
490#[cfg(feature = "unstable_protocol_v2")]
491fn normalize_initialize_params_for_agent_protocol(
492    message: &RawJsonRpcMessage,
493    selected: AgentProtocol,
494) -> Result<serde_json::Map<String, serde_json::Value>, crate::Error> {
495    let RawJsonRpcMessage::Request(request) = message else {
496        return Err(
497            crate::Error::invalid_request().data("first ACP message must be an initialize request")
498        );
499    };
500
501    if request.method.as_ref() != "initialize" {
502        return Err(crate::Error::invalid_request().data("first ACP request must be initialize"));
503    }
504
505    let Some(RawJsonRpcParams::Object(params)) = &request.params else {
506        return Err(invalid_initialize_protocol_version());
507    };
508    let Some(protocol_version) = params.get("protocolVersion") else {
509        return Err(invalid_initialize_protocol_version());
510    };
511
512    let requested = serde_json::from_value::<ProtocolVersion>(protocol_version.clone())
513        .map_err(|_| invalid_initialize_protocol_version())?;
514    let mut params = params.clone();
515    rewrite_initialize_params(&mut params, requested, selected)?;
516    Ok(params)
517}
518
519#[cfg(feature = "unstable_protocol_v2")]
520fn rewrite_initialize_params(
521    params: &mut serde_json::Map<String, serde_json::Value>,
522    requested: ProtocolVersion,
523    selected: AgentProtocol,
524) -> Result<(), crate::Error> {
525    match selected {
526        AgentProtocol::V1 => {
527            let mut initialize = if requested >= ProtocolVersion::V2 {
528                v2::conversion::v2_to_v1(parse_initialize_params::<v2::InitializeRequest>(params)?)
529                    .map_err(invalid_initialize_params)?
530            } else {
531                parse_initialize_params::<InitializeRequest>(params)?
532            };
533            initialize.protocol_version = ProtocolVersion::V1;
534            replace_initialize_params(params, initialize)
535        }
536        AgentProtocol::V2 => {
537            let mut initialize = if requested >= ProtocolVersion::V2 {
538                parse_initialize_params::<v2::InitializeRequest>(params)?
539            } else {
540                v2::conversion::v1_to_v2(parse_initialize_params::<InitializeRequest>(params)?)
541                    .map_err(invalid_initialize_params)?
542            };
543            initialize.protocol_version = ProtocolVersion::V2;
544            replace_initialize_params(params, initialize)
545        }
546    }
547}
548
549#[cfg(feature = "unstable_protocol_v2")]
550fn parse_initialize_params<T: DeserializeOwned>(
551    params: &serde_json::Map<String, serde_json::Value>,
552) -> Result<T, crate::Error> {
553    serde_json::from_value(serde_json::Value::Object(params.clone()))
554        .map_err(invalid_initialize_params)
555}
556
557#[cfg(feature = "unstable_protocol_v2")]
558fn replace_initialize_params(
559    params: &mut serde_json::Map<String, serde_json::Value>,
560    initialize: impl Serialize,
561) -> Result<(), crate::Error> {
562    let value = serde_json::to_value(initialize).map_err(crate::Error::into_internal_error)?;
563    let serde_json::Value::Object(object) = value else {
564        return Err(crate::util::internal_error(
565            "initialize params did not serialize to an object",
566        ));
567    };
568    *params = object;
569    Ok(())
570}
571
572#[cfg(feature = "unstable_protocol_v2")]
573fn highest_compatible_agent_protocol(
574    requested: ProtocolVersion,
575    supported: SupportedAgentProtocols,
576) -> Result<AgentProtocol, crate::Error> {
577    supported.highest_compatible(requested).ok_or_else(|| {
578        crate::Error::invalid_request().data(format!(
579            "unsupported ACP protocol version {requested}; this endpoint supports {}",
580            supported.description()
581        ))
582    })
583}
584
585#[cfg(feature = "unstable_protocol_v2")]
586fn invalid_initialize_protocol_version() -> crate::Error {
587    crate::Error::invalid_params()
588        .data("initialize.protocolVersion must be a valid ACP protocol version")
589}
590
591#[cfg(feature = "unstable_protocol_v2")]
592fn invalid_initialize_params(error: impl ToString) -> crate::Error {
593    crate::Error::invalid_params().data(format!("invalid initialize params: {}", error.to_string()))
594}
595
596#[cfg(feature = "unstable_protocol_v2")]
597fn send_initialize_error(
598    tx: &futures::channel::mpsc::UnboundedSender<Result<RawJsonRpcMessage, crate::Error>>,
599    message: &RawJsonRpcMessage,
600    error: crate::Error,
601) -> Result<(), crate::Error> {
602    let id = match message {
603        RawJsonRpcMessage::Request(request) => request.id.clone(),
604        RawJsonRpcMessage::Notification(_) | RawJsonRpcMessage::Response(_) => RequestId::Null,
605    };
606    tx.unbounded_send(Ok(RawJsonRpcMessage::response(id, Err(error))))
607        .map_err(crate::util::internal_error)
608}
609
610#[cfg(feature = "unstable_protocol_v2")]
611async fn reject_initialize(
612    client: RunningProtocolPeer,
613    message: &RawJsonRpcMessage,
614    error: crate::Error,
615) -> Result<(), crate::Error> {
616    let RunningProtocolPeer { mut rx, tx, future } = client;
617    send_initialize_error(&tx, message, error)?;
618    drop(tx);
619
620    let drain_incoming = async move {
621        while let Some(message) = rx.next().await {
622            message?;
623        }
624        Ok(())
625    };
626
627    let ((), ()) = futures::try_join!(future, drain_incoming)?;
628    Ok(())
629}
630
631#[cfg(feature = "unstable_protocol_v2")]
632struct RunningProtocolPeer {
633    rx: futures::channel::mpsc::UnboundedReceiver<Result<RawJsonRpcMessage, crate::Error>>,
634    tx: futures::channel::mpsc::UnboundedSender<Result<RawJsonRpcMessage, crate::Error>>,
635    future: crate::BoxFuture<'static, Result<(), crate::Error>>,
636}
637
638#[cfg(feature = "unstable_protocol_v2")]
639impl RunningProtocolPeer {
640    fn new<R: Role>(component: impl ConnectTo<R>) -> Self {
641        let (Channel { rx, tx }, future) = component.into_channel_and_future();
642        Self { rx, tx, future }
643    }
644
645    async fn next_message(self) -> Result<Option<(RawJsonRpcMessage, Self)>, crate::Error> {
646        let Self { mut rx, tx, future } = self;
647        match future::select(Box::pin(rx.next()), future).await {
648            future::Either::Left((Some(message), future)) => {
649                Ok(Some((message?, Self { rx, tx, future })))
650            }
651            future::Either::Left((None, future)) => {
652                future.await?;
653                Ok(None)
654            }
655            future::Either::Right((result, next_message)) => {
656                result?;
657                drop(next_message);
658                let Some(message) = rx.next().await else {
659                    return Ok(None);
660                };
661                Ok(Some((
662                    message?,
663                    Self {
664                        rx,
665                        tx,
666                        future: Box::pin(future::ready(Ok(()))),
667                    },
668                )))
669            }
670        }
671    }
672
673    fn send(&self, message: RawJsonRpcMessage) -> Result<(), crate::Error> {
674        self.tx
675            .unbounded_send(Ok(message))
676            .map_err(crate::util::internal_error)
677    }
678}
679
680#[cfg(feature = "unstable_protocol_v2")]
681async fn pipe_protocol_peers_until_closed(
682    left: RunningProtocolPeer,
683    right: RunningProtocolPeer,
684) -> Result<(), crate::Error> {
685    let ((), (), (), ()) = futures::try_join!(
686        left.future,
687        right.future,
688        Channel {
689            rx: left.rx,
690            tx: right.tx,
691        }
692        .copy(),
693        Channel {
694            rx: right.rx,
695            tx: left.tx,
696        }
697        .copy(),
698    )?;
699
700    Ok(())
701}
702
703#[cfg(feature = "unstable_protocol_v2")]
704async fn pipe_protocol_peers_until_done(
705    left: RunningProtocolPeer,
706    right: RunningProtocolPeer,
707) -> Result<(), crate::Error> {
708    let bridge = Box::pin(async move {
709        let ((), ()) = futures::try_join!(
710            Channel {
711                rx: left.rx,
712                tx: right.tx,
713            }
714            .copy(),
715            Channel {
716                rx: right.rx,
717                tx: left.tx,
718            }
719            .copy(),
720        )?;
721        Ok(())
722    });
723
724    match future::select(left.future, future::select(right.future, bridge)).await {
725        future::Either::Left((result, _))
726        | future::Either::Right((
727            future::Either::Left((result, _)) | future::Either::Right((result, _)),
728            _,
729        )) => result,
730    }
731}
732
733#[cfg(feature = "unstable_protocol_v2")]
734#[derive(Debug)]
735struct InitializeResponse {
736    id: RequestId,
737    result: Result<serde_json::Value, crate::Error>,
738}
739
740#[cfg(feature = "unstable_protocol_v2")]
741impl InitializeResponse {
742    fn from_message(message: RawJsonRpcMessage) -> Result<Self, crate::Error> {
743        match message {
744            RawJsonRpcMessage::Response(RpcResponse::Result { id, result }) => Ok(Self {
745                id,
746                result: Ok(result),
747            }),
748            RawJsonRpcMessage::Response(RpcResponse::Error { id, error }) => Ok(Self {
749                id,
750                result: Err(error),
751            }),
752            message => Err(crate::Error::invalid_request().data(format!(
753                "first ACP response must be an initialize response, got {message:?}",
754            ))),
755        }
756    }
757
758    fn into_message(self) -> RawJsonRpcMessage {
759        RawJsonRpcMessage::response(self.id, self.result)
760    }
761
762    fn with_id(self, id: RequestId) -> RawJsonRpcMessage {
763        RawJsonRpcMessage::response(id, self.result)
764    }
765
766    fn protocol_version(&self) -> Option<ProtocolVersion> {
767        serde_json::from_value(self.result.as_ref().ok()?.get("protocolVersion")?.clone()).ok()
768    }
769}
770
771#[cfg(feature = "unstable_protocol_v2")]
772#[derive(Debug, Clone, Copy, PartialEq, Eq)]
773enum ClientProtocol {
774    V1,
775    V2,
776}
777
778#[cfg(feature = "unstable_protocol_v2")]
779impl ClientProtocol {
780    fn name(self) -> &'static str {
781        match self {
782            Self::V1 => "1",
783            Self::V2 => "2",
784        }
785    }
786}
787
788#[cfg(feature = "unstable_protocol_v2")]
789#[derive(Debug, Clone, Copy, PartialEq, Eq)]
790struct SupportedClientProtocols {
791    v1: bool,
792    v2: bool,
793}
794
795#[cfg(feature = "unstable_protocol_v2")]
796impl SupportedClientProtocols {
797    fn highest_configured(self) -> Option<ClientProtocol> {
798        if self.v2 {
799            return Some(ClientProtocol::V2);
800        }
801
802        if self.v1 {
803            return Some(ClientProtocol::V1);
804        }
805
806        None
807    }
808}
809
810#[cfg(feature = "unstable_protocol_v2")]
811async fn start_client_protocol(
812    protocol: ClientProtocol,
813    client: DynConnectTo<Agent>,
814) -> Result<(RunningProtocolPeer, RawJsonRpcMessage), crate::Error> {
815    let client = RunningProtocolPeer::new(client);
816    let Some((initialize, client)) = client.next_message().await? else {
817        return Err(crate::Error::invalid_request().data(format!(
818            "ACP protocol version {} client implementation ended before initialize",
819            protocol.name()
820        )));
821    };
822    ensure_client_initialize_request(protocol, &initialize)?;
823    Ok((client, initialize))
824}
825
826#[cfg(feature = "unstable_protocol_v2")]
827async fn send_initialize_and_receive(
828    client: RunningProtocolPeer,
829    agent: RunningProtocolPeer,
830    initialize: RawJsonRpcMessage,
831) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
832    agent.send(initialize)?;
833    let Some((response, agent)) = agent.next_message().await? else {
834        return Err(crate::Error::internal_error().data("agent closed before initialize response"));
835    };
836    let response = InitializeResponse::from_message(response)?;
837    Ok((client, agent, response))
838}
839
840#[cfg(feature = "unstable_protocol_v2")]
841async fn initialize_client_protocol(
842    protocol: ClientProtocol,
843    client: DynConnectTo<Agent>,
844    agent: impl ConnectTo<Client>,
845) -> Result<(RunningProtocolPeer, RunningProtocolPeer, InitializeResponse), crate::Error> {
846    let agent = RunningProtocolPeer::new(agent);
847    let (client, initialize) = start_client_protocol(protocol, client).await?;
848    send_initialize_and_receive(client, agent, initialize).await
849}
850
851#[cfg(feature = "unstable_protocol_v2")]
852async fn connect_client_protocol(
853    protocol: ClientProtocol,
854    client: DynConnectTo<Agent>,
855    agent: impl ConnectTo<Client>,
856) -> Result<(), crate::Error> {
857    let (client, agent, initialize_response) =
858        initialize_client_protocol(protocol, client, agent).await?;
859    client.send(initialize_response.into_message())?;
860    pipe_protocol_peers_until_done(client, agent).await
861}
862
863#[cfg(feature = "unstable_protocol_v2")]
864fn ensure_client_initialize_request(
865    protocol: ClientProtocol,
866    message: &RawJsonRpcMessage,
867) -> Result<(), crate::Error> {
868    let RawJsonRpcMessage::Request(request) = message else {
869        return Err(crate::Error::invalid_request().data(format!(
870            "ACP protocol version {} client implementation must send initialize first",
871            protocol.name()
872        )));
873    };
874
875    if request.method.as_ref() != "initialize" {
876        return Err(crate::Error::invalid_request().data(format!(
877            "ACP protocol version {} client implementation must send initialize first",
878            protocol.name()
879        )));
880    }
881
882    Ok(())
883}
884
885#[cfg(feature = "unstable_protocol_v2")]
886fn initialize_request_id(message: &RawJsonRpcMessage) -> Option<RequestId> {
887    let RawJsonRpcMessage::Request(request) = message else {
888        return None;
889    };
890    Some(request.id.clone())
891}
892
893#[cfg(feature = "unstable_protocol_v2")]
894fn initialize_response_negotiated_v1(response: &InitializeResponse) -> bool {
895    response.protocol_version() == Some(ProtocolVersion::V1)
896}
897
898impl HasPeer<Agent> for Agent {
899    fn remote_style(&self, _peer: Agent) -> RemoteStyle {
900        RemoteStyle::Counterpart
901    }
902}
903
904/// The proxy role - an intermediary that can intercept and modify messages.
905///
906/// Proxies sit between a client and an agent (or another proxy), and can:
907/// - Add tools via MCP servers
908/// - Filter or transform messages
909/// - Inject additional context
910///
911/// Proxies connect to a [`Conductor`] which orchestrates the proxy chain.
912#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
913pub struct Proxy;
914
915impl Role for Proxy {
916    type Counterpart = Conductor;
917
918    async fn default_handle_dispatch_from(
919        &self,
920        message: crate::Dispatch,
921        _connection: crate::ConnectionTo<Self>,
922    ) -> Result<crate::Handled<crate::Dispatch>, crate::Error> {
923        Ok(Handled::No {
924            message,
925            retry: false,
926        })
927    }
928
929    fn role_id(&self) -> RoleId {
930        RoleId::from_singleton(self)
931    }
932
933    fn counterpart(&self) -> Self::Counterpart {
934        Conductor
935    }
936}
937
938impl Proxy {
939    /// Create a connection builder for a proxy.
940    pub fn builder(self) -> Builder<Proxy, NullHandler, NullRun> {
941        Builder::new(self)
942    }
943}
944
945impl HasPeer<Proxy> for Proxy {
946    fn remote_style(&self, _peer: Proxy) -> RemoteStyle {
947        RemoteStyle::Counterpart
948    }
949}
950
951/// The conductor role - orchestrates proxy chains.
952///
953/// Conductors manage connections between clients, proxies, and agents,
954/// routing messages through the appropriate proxy chain.
955#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
956pub struct Conductor;
957
958impl Role for Conductor {
959    type Counterpart = Proxy;
960
961    fn role_id(&self) -> RoleId {
962        RoleId::from_singleton(self)
963    }
964
965    fn counterpart(&self) -> Self::Counterpart {
966        Proxy
967    }
968
969    async fn default_handle_dispatch_from(
970        &self,
971        message: Dispatch,
972        cx: ConnectionTo<Conductor>,
973    ) -> Result<Handled<Dispatch>, crate::Error> {
974        // Handle various special messages:
975        MatchDispatchFrom::new(message, &cx)
976            .if_request_from(Client, async |_req: InitializeRequest, responder| {
977                responder.respond_with_error(crate::Error::invalid_request().data(format!(
978                    "proxies must be initialized with `{METHOD_INITIALIZE_PROXY}`"
979                )))
980            })
981            .await
982            // Initialize Proxy coming from the client -- forward to the agent but
983            // convert into a regular initialize.
984            .if_request_from(
985                Client,
986                async |request: InitializeProxyRequest, responder| {
987                    let InitializeProxyRequest { initialize } = request;
988                    cx.send_request_to(Agent, initialize)
989                        .forward_response_to(responder)
990                },
991            )
992            .await
993            // New session coming from the client -- proxy to the agent
994            // and add a dynamic handler for that session-id.
995            .if_request_from(Client, async |request: NewSessionRequest, responder| {
996                let sent = cx.send_request_to(Agent, request);
997                // The dynamic-handler hook below means we cannot use
998                // `forward_response_to`, so wire up cancellation forwarding
999                // explicitly to keep `session/new` cancellable like every
1000                // other proxied request.
1001                let sent = sent.forward_cancellation_from(responder.cancellation());
1002                sent.on_receiving_result({
1003                    let cx = cx.clone();
1004                    async move |result| {
1005                        if let Ok(NewSessionResponse { session_id, .. }) = &result {
1006                            cx.add_dynamic_handler(ProxySessionMessages::new(session_id.clone()))?
1007                                .run_indefinitely();
1008                        }
1009                        responder.respond_with_result(result)
1010                    }
1011                })
1012            })
1013            .await
1014            // Incoming message from the client -- forward to the agent
1015            .if_message_from(Client, async |message: Dispatch| {
1016                cx.send_proxied_message_to(Agent, message)
1017            })
1018            .await
1019            // Incoming message from the agent -- forward to the client
1020            .if_message_from(Agent, async |message: Dispatch| {
1021                cx.send_proxied_message_to(Client, message)
1022            })
1023            .await
1024            .done()
1025    }
1026}
1027
1028impl Conductor {
1029    /// Create a connection builder for a conductor.
1030    pub fn builder(self) -> Builder<Conductor, NullHandler, NullRun> {
1031        Builder::new(self)
1032    }
1033}
1034
1035impl HasPeer<Client> for Conductor {
1036    fn remote_style(&self, _peer: Client) -> RemoteStyle {
1037        RemoteStyle::Predecessor
1038    }
1039}
1040
1041impl HasPeer<Agent> for Conductor {
1042    fn remote_style(&self, _peer: Agent) -> RemoteStyle {
1043        RemoteStyle::Successor
1044    }
1045}
1046
1047/// Dynamic handler that proxies session messages from Agent to Client.
1048///
1049/// This is used internally to handle session message routing after a
1050/// `session.new` request has been forwarded.
1051pub(crate) struct ProxySessionMessages {
1052    session_id: SessionId,
1053}
1054
1055impl ProxySessionMessages {
1056    /// Create a new proxy handler for the given session.
1057    pub fn new(session_id: SessionId) -> Self {
1058        Self { session_id }
1059    }
1060}
1061
1062impl<Counterpart: Role> HandleDispatchFrom<Counterpart> for ProxySessionMessages
1063where
1064    Counterpart: HasPeer<Agent> + HasPeer<Client>,
1065{
1066    async fn handle_dispatch_from(
1067        &mut self,
1068        message: Dispatch,
1069        connection: ConnectionTo<Counterpart>,
1070    ) -> Result<Handled<Dispatch>, crate::Error> {
1071        MatchDispatchFrom::new(message, &connection)
1072            .if_message_from(Agent, async |message| {
1073                // If this is for our session-id, proxy it to the client.
1074                if let Some(session_id) = message.get_session_id()?
1075                    && session_id == self.session_id
1076                {
1077                    connection.send_proxied_message_to(Client, message)?;
1078                    return Ok(Handled::Yes);
1079                }
1080
1081                // Otherwise, leave it alone.
1082                Ok(Handled::No {
1083                    message,
1084                    retry: false,
1085                })
1086            })
1087            .await
1088            .done()
1089    }
1090
1091    fn describe_chain(&self) -> impl std::fmt::Debug {
1092        format!("ProxySessionMessages({})", self.session_id)
1093    }
1094}