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