agent_client_protocol_conductor/conductor.rs
1//! # Conductor: ACP Proxy Chain Orchestrator
2//!
3//! This module implements the Conductor conductor, which orchestrates a chain of
4//! proxy components that sit between an editor and an agent, transforming the
5//! Agent-Client Protocol (ACP) stream bidirectionally.
6//!
7//! ## Architecture Overview
8//!
9//! The conductor builds and manages a chain of components:
10//!
11//! ```text
12//! Editor <-ACP-> [Component 0] <-ACP-> [Component 1] <-ACP-> ... <-ACP-> Agent
13//! ```
14//!
15//! Each component receives ACP messages, can transform them, and forwards them
16//! to the next component in the chain. The conductor:
17//!
18//! 1. Spawns each component as a subprocess
19//! 2. Establishes bidirectional JSON-RPC connections with each component
20//! 3. Routes messages between editor, components, and agent
21//! 4. Distinguishes proxy vs agent components via distinct request types
22//!
23//! ## Recursive Chain Building
24//!
25//! The chain is built recursively through the `_proxy/successor/*` protocol:
26//!
27//! 1. Editor connects to Component 0 via the conductor
28//! 2. When Component 0 wants to communicate with its successor, it sends
29//! requests/notifications with method prefix `_proxy/successor/`
30//! 3. The conductor intercepts these messages, strips the prefix, and forwards
31//! to Component 1
32//! 4. Component 1 does the same for Component 2, and so on
33//! 5. The last component talks directly to the agent (no `_proxy/successor/` prefix)
34//!
35//! This allows each component to be written as if it's talking to a single successor,
36//! without knowing about the full chain.
37//!
38//! ## Proxy vs Agent Initialization
39//!
40//! Components discover whether they're a proxy or agent via the initialization request they receive:
41//!
42//! - **Proxy components**: Receive `InitializeProxyRequest` (`_proxy/initialize` method)
43//! - **Agent component**: Receives standard `InitializeRequest` (`initialize` method)
44//!
45//! The conductor sends `InitializeProxyRequest` to all proxy components in the chain,
46//! and `InitializeRequest` only to the final agent component. This allows proxies to
47//! know they should forward messages to a successor, while agents know they are the
48//! terminal component
49//!
50//! ## Message Routing
51//!
52//! The conductor runs an event loop processing messages from:
53//!
54//! - **Editor to first component**: Standard ACP messages
55//! - **Component to successor**: Via `_proxy/successor/*` prefix
56//! - **Component responses**: Via futures channels back to requesters
57//!
58//! The message flow ensures bidirectional communication while maintaining the
59//! abstraction that each component only knows about its immediate successor.
60//!
61//! ## Lazy Component Initialization
62//!
63//! Components are instantiated lazily when the first `initialize` request is received
64//! from the editor. This enables dynamic proxy chain construction based on client capabilities.
65//!
66//! ### Simple Usage
67//!
68//! Pass a Vec of components that implement `Component`:
69//!
70//! ```ignore
71//! let conductor = Conductor::new(
72//! "my-conductor",
73//! vec![proxy1, proxy2, agent],
74//! None,
75//! );
76//! ```
77//!
78//! All components are spawned in order when the editor sends the first `initialize` request.
79//!
80//! ### Dynamic Component Selection
81//!
82//! Pass a closure to examine the `InitializeRequest` and dynamically construct the chain:
83//!
84//! ```ignore
85//! let conductor = Conductor::new(
86//! "my-conductor",
87//! |cx, conductor_tx, init_req| async move {
88//! // Examine capabilities
89//! let needs_auth = has_auth_capability(&init_req);
90//!
91//! let mut components = Vec::new();
92//! if needs_auth {
93//! components.push(spawn_auth_proxy(&cx, &conductor_tx)?);
94//! }
95//! components.push(spawn_agent(&cx, &conductor_tx)?);
96//!
97//! // Return (potentially modified) request and component list
98//! Ok((init_req, components))
99//! },
100//! None,
101//! );
102//! ```
103//!
104//! The closure receives:
105//! - `cx: &ConnectionTo` - Connection context for spawning components
106//! - `conductor_tx: &mpsc::Sender<ConductorMessage>` - Channel for message routing
107//! - `init_req: InitializeRequest` - The Initialize request from the editor
108//!
109//! And returns:
110//! - Modified `InitializeRequest` to forward downstream
111//! - `Vec<ConnectionTo>` of spawned components
112
113use std::sync::Arc;
114
115use agent_client_protocol::{
116 Agent, BoxFuture, Client, Conductor, ConnectTo, Dispatch, DynConnectTo, Error, JsonRpcMessage,
117 Proxy, Role, RunWithConnectionTo, role::HasPeer, util::MatchDispatch,
118};
119use agent_client_protocol::{
120 Builder, ConnectionTo, JsonRpcNotification, JsonRpcRequest, SentRequest,
121};
122use agent_client_protocol::{
123 HandleDispatchFrom,
124 schema::{InitializeProxyRequest, v1::InitializeRequest},
125 util::MatchDispatchFrom,
126};
127use agent_client_protocol::{Handled, schema::SuccessorMessage};
128use futures::{
129 SinkExt, StreamExt,
130 channel::mpsc::{self},
131};
132use tracing::{debug, info};
133
134/// The conductor manages the proxy chain lifecycle and message routing.
135///
136/// It maintains connections to all components in the chain and routes messages
137/// bidirectionally between the editor, components, and agent.
138///
139#[derive(Debug)]
140pub struct ConductorImpl<Host: ConductorHostRole> {
141 host: Host,
142 name: String,
143 instantiator: Host::Instantiator,
144 trace_writer: Option<crate::trace::TraceWriter>,
145}
146
147impl<Host: ConductorHostRole> ConductorImpl<Host> {
148 pub fn new(host: Host, name: impl ToString, instantiator: Host::Instantiator) -> Self {
149 ConductorImpl {
150 name: name.to_string(),
151 host,
152 instantiator,
153 trace_writer: None,
154 }
155 }
156}
157
158impl ConductorImpl<Agent> {
159 /// Create a conductor in agent mode (the last component is an agent).
160 pub fn new_agent(
161 name: impl ToString,
162 instantiator: impl InstantiateProxiesAndAgent + 'static,
163 ) -> Self {
164 ConductorImpl::new(Agent, name, Box::new(instantiator))
165 }
166}
167
168impl ConductorImpl<Proxy> {
169 /// Create a conductor in proxy mode (forwards to another conductor).
170 pub fn new_proxy(name: impl ToString, instantiator: impl InstantiateProxies + 'static) -> Self {
171 ConductorImpl::new(Proxy, name, Box::new(instantiator))
172 }
173}
174
175impl<Host: ConductorHostRole> ConductorImpl<Host> {
176 /// Enable trace logging to a custom destination.
177 ///
178 /// Use `agent-client-protocol-trace-viewer` to view the trace as an interactive sequence diagram.
179 #[must_use]
180 pub fn trace_to(mut self, dest: impl crate::trace::WriteEvent) -> Self {
181 self.trace_writer = Some(crate::trace::TraceWriter::new(dest));
182 self
183 }
184
185 /// Enable trace logging to a file path.
186 ///
187 /// Events will be written as newline-delimited JSON (`.jsons` format).
188 /// Use `agent-client-protocol-trace-viewer` to view the trace as an interactive sequence diagram.
189 pub fn trace_to_path(mut self, path: impl AsRef<std::path::Path>) -> std::io::Result<Self> {
190 self.trace_writer = Some(crate::trace::TraceWriter::from_path(path)?);
191 Ok(self)
192 }
193
194 /// Enable trace logging with an existing TraceWriter.
195 #[must_use]
196 pub fn with_trace_writer(mut self, writer: crate::trace::TraceWriter) -> Self {
197 self.trace_writer = Some(writer);
198 self
199 }
200
201 /// Run the conductor with a transport.
202 pub async fn run(
203 self,
204 transport: impl ConnectTo<Host>,
205 ) -> Result<(), agent_client_protocol::Error> {
206 let (conductor_tx, conductor_rx) = mpsc::channel(128 /* chosen arbitrarily */);
207
208 // Set up tracing if enabled - spawn writer task and get handle
209 let trace_handle;
210 let trace_future: BoxFuture<'static, Result<(), agent_client_protocol::Error>>;
211 if let Some((h, f)) = self.trace_writer.map(super::trace::TraceWriter::spawn) {
212 trace_handle = Some(h);
213 trace_future = Box::pin(f);
214 } else {
215 trace_handle = None;
216 trace_future = Box::pin(std::future::ready(Ok(())));
217 }
218
219 let responder = ConductorResponder {
220 conductor_rx,
221 conductor_tx: conductor_tx.clone(),
222 instantiator: Some(self.instantiator),
223 proxies: Vec::default(),
224 successor: Arc::new(agent_client_protocol::util::internal_error(
225 "successor not initialized",
226 )),
227 trace_handle,
228 host: self.host.clone(),
229 };
230
231 Builder::new_with(
232 self.host.clone(),
233 ConductorMessageHandler {
234 conductor_tx,
235 host: self.host.clone(),
236 },
237 )
238 .name(self.name)
239 .with_responder(responder)
240 .with_spawned(|_cx| trace_future)
241 .connect_to(transport)
242 .await
243 }
244
245 async fn incoming_message_from_client(
246 conductor_tx: &mut mpsc::Sender<ConductorMessage>,
247 message: Dispatch,
248 ) -> Result<(), agent_client_protocol::Error> {
249 conductor_tx
250 .send(ConductorMessage::LeftToRight {
251 target_component_index: 0,
252 message,
253 })
254 .await
255 .map_err(agent_client_protocol::util::internal_error)
256 }
257
258 async fn incoming_message_from_agent(
259 conductor_tx: &mut mpsc::Sender<ConductorMessage>,
260 message: Dispatch,
261 ) -> Result<(), agent_client_protocol::Error> {
262 conductor_tx
263 .send(ConductorMessage::RightToLeft {
264 source_component_index: SourceComponentIndex::Successor,
265 message,
266 })
267 .await
268 .map_err(agent_client_protocol::util::internal_error)
269 }
270}
271
272impl<Host: ConductorHostRole> ConnectTo<Host::Counterpart> for ConductorImpl<Host> {
273 async fn connect_to(
274 self,
275 client: impl ConnectTo<Host>,
276 ) -> Result<(), agent_client_protocol::Error> {
277 self.run(client).await
278 }
279}
280
281struct ConductorMessageHandler<Host: ConductorHostRole> {
282 conductor_tx: mpsc::Sender<ConductorMessage>,
283 host: Host,
284}
285
286impl<Host: ConductorHostRole> HandleDispatchFrom<Host::Counterpart>
287 for ConductorMessageHandler<Host>
288{
289 async fn handle_dispatch_from(
290 &mut self,
291 message: Dispatch,
292 connection: agent_client_protocol::ConnectionTo<Host::Counterpart>,
293 ) -> Result<agent_client_protocol::Handled<Dispatch>, agent_client_protocol::Error> {
294 self.host
295 .handle_dispatch(message, connection, &mut self.conductor_tx)
296 .await
297 }
298
299 fn describe_chain(&self) -> impl std::fmt::Debug {
300 "ConductorMessageHandler"
301 }
302}
303
304/// The conductor manages the proxy chain lifecycle and message routing.
305///
306/// It maintains connections to all components in the chain and routes messages
307/// bidirectionally between the editor, components, and agent.
308///
309pub struct ConductorResponder<Host>
310where
311 Host: ConductorHostRole,
312{
313 conductor_rx: mpsc::Receiver<ConductorMessage>,
314
315 conductor_tx: mpsc::Sender<ConductorMessage>,
316
317 /// The instantiator for lazy initialization.
318 /// Set to None after components are instantiated.
319 instantiator: Option<Host::Instantiator>,
320
321 /// The chain of proxies before the agent (if any).
322 ///
323 /// Populated lazily when the first Initialize request is received.
324 proxies: Vec<ConnectionTo<Proxy>>,
325
326 /// If the conductor is operating in agent mode, this will direct messages to the agent.
327 /// If the conductor is operating in proxy mode, this will direct messages to the successor.
328 /// Populated lazily when the first Initialize request is received; the initial value just returns errors.
329 successor: Arc<dyn ConductorSuccessor<Host>>,
330
331 /// Optional trace handle for sequence diagram visualization.
332 trace_handle: Option<crate::trace::TraceHandle>,
333
334 /// Defines what sort of link we have
335 host: Host,
336}
337
338impl<Host> std::fmt::Debug for ConductorResponder<Host>
339where
340 Host: ConductorHostRole,
341{
342 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343 f.debug_struct("ConductorResponder")
344 .field("conductor_rx", &self.conductor_rx)
345 .field("conductor_tx", &self.conductor_tx)
346 .field("proxies", &self.proxies)
347 .field("trace_handle", &self.trace_handle)
348 .field("host", &self.host)
349 .finish_non_exhaustive()
350 }
351}
352
353impl<Host> RunWithConnectionTo<Host::Counterpart> for ConductorResponder<Host>
354where
355 Host: ConductorHostRole,
356{
357 async fn run_with_connection_to(
358 mut self,
359 connection: ConnectionTo<Host::Counterpart>,
360 ) -> Result<(), agent_client_protocol::Error> {
361 // Components are now spawned lazily in forward_initialize_request
362 // when the first Initialize request is received.
363
364 // This is the "central actor" of the conductor. Most other things forward messages
365 // via `conductor_tx` into this loop. This lets us serialize the conductor's activity.
366 while let Some(message) = self.conductor_rx.next().await {
367 self.handle_conductor_message(connection.clone(), message)
368 .await?;
369 }
370 Ok(())
371 }
372}
373
374impl<Host> ConductorResponder<Host>
375where
376 Host: ConductorHostRole,
377{
378 /// Recursively spawns components and builds the proxy chain.
379 ///
380 /// This function implements the recursive chain building pattern:
381 /// 1. Pop the next component from the `providers` list
382 /// 2. Create the component (either spawn subprocess or use mock)
383 /// 3. Set up JSON-RPC connection and message handlers
384 /// 4. Recursively call itself to spawn the next component
385 /// 5. When no components remain, start the message routing loop via `serve()`
386 ///
387 /// Central message handling logic for the conductor.
388 /// The conductor routes all [`ConductorMessage`] messages through to this function.
389 /// Each message corresponds to a request or notification from one component to another.
390 /// The conductor ferries messages from one place to another, sometimes making modifications along the way.
391 /// Note that *responses to requests* are sent *directly* without going through this loop.
392 ///
393 /// The names we use are
394 ///
395 /// * The *client* is the originator of all ACP traffic, typically an editor or GUI.
396 /// * Then there is a sequence of *components* consisting of:
397 /// * Zero or more *proxies*, which receive messages and forward them to the next component in the chain.
398 /// * And finally the *agent*, which is the final component in the chain and handles the actual work.
399 ///
400 /// For the most part, we just pass messages through the chain without modification, but there are a few exceptions:
401 ///
402 /// * We send `InitializeProxyRequest` to proxy components and `InitializeRequest` to the agent component.
403 /// * We modify "session/new" requests that use `acp:...` as the URL for an MCP server to redirect
404 /// through a stdio server that runs on localhost and bridges messages.
405 async fn handle_conductor_message(
406 &mut self,
407 client: ConnectionTo<Host::Counterpart>,
408 message: ConductorMessage,
409 ) -> Result<(), agent_client_protocol::Error> {
410 tracing::debug!(?message, "handle_conductor_message");
411
412 match message {
413 ConductorMessage::LeftToRight {
414 target_component_index,
415 message,
416 } => {
417 // Tracing happens inside forward_client_to_agent_message, after initialization,
418 // so that component_name() has access to the populated proxies list.
419 self.forward_client_to_agent_message(target_component_index, message, client)
420 .await
421 }
422
423 ConductorMessage::RightToLeft {
424 source_component_index,
425 message,
426 } => {
427 tracing::debug!(
428 ?source_component_index,
429 message_method = ?message.method(),
430 "Conductor: AgentToClient received"
431 );
432 self.send_message_to_predecessor_of(client, source_component_index, message)
433 }
434 }
435 }
436
437 /// Send a message (request or notification) to the predecessor of the given component.
438 ///
439 /// This is a bit subtle because the relationship of the conductor
440 /// is different depending on who will be receiving the message:
441 /// * If the message is going to the conductor's client, then no changes
442 /// are needed, as the conductor is sending an agent-to-client message and
443 /// the conductor is acting as the agent.
444 /// * If the message is going to a proxy component, then we have to wrap
445 /// it in a "from successor" wrapper, because the conductor is the
446 /// proxy's client.
447 fn send_message_to_predecessor_of<Req: JsonRpcRequest, N: JsonRpcNotification>(
448 &mut self,
449 client: ConnectionTo<Host::Counterpart>,
450 source_component_index: SourceComponentIndex,
451 message: Dispatch<Req, N>,
452 ) -> Result<(), agent_client_protocol::Error>
453 where
454 Req::Response: Send,
455 {
456 let source_component_index = match source_component_index {
457 SourceComponentIndex::Successor => self.proxies.len(),
458 SourceComponentIndex::Proxy(index) => index,
459 };
460
461 match message {
462 Dispatch::Request(request, responder) => self
463 .send_request_to_predecessor_of(client, source_component_index, request)
464 .forward_response_to(responder),
465 Dispatch::Notification(notification) => {
466 // `$/cancel_request` is connection-scoped: its `requestId` was
467 // allocated on the connection the notification arrived over
468 // and means nothing on the predecessor's connection. The SDK
469 // already propagates the cancellation hop by hop through the
470 // `forward_response_to` calls above, so drop the raw
471 // notification instead of tunneling a meaningless ID.
472 if agent_client_protocol::is_cancel_request_notification(¬ification) {
473 tracing::debug!(
474 "not forwarding hop-scoped `$/cancel_request` notification to predecessor"
475 );
476 return Ok(());
477 }
478 self.send_notification_to_predecessor_of(
479 client,
480 source_component_index,
481 notification,
482 )
483 }
484 Dispatch::Response(result, router) => router.respond_with_result(result),
485 }
486 }
487
488 fn send_request_to_predecessor_of<Req: JsonRpcRequest>(
489 &mut self,
490 client_connection: ConnectionTo<Host::Counterpart>,
491 source_component_index: usize,
492 request: Req,
493 ) -> SentRequest<Req::Response> {
494 if source_component_index == 0 {
495 client_connection.send_request_to(Client, request)
496 } else {
497 self.proxies[source_component_index - 1].send_request(SuccessorMessage {
498 message: request,
499 meta: None,
500 })
501 }
502 }
503
504 /// Send a notification to the predecessor of the given component.
505 ///
506 /// This is a bit subtle because the relationship of the conductor
507 /// is different depending on who will be receiving the message:
508 /// * If the notification is going to the conductor's client, then no changes
509 /// are needed, as the conductor is sending an agent-to-client message and
510 /// the conductor is acting as the agent.
511 /// * If the notification is going to a proxy component, then we have to wrap
512 /// it in a "from successor" wrapper, because the conductor is the
513 /// proxy's client.
514 fn send_notification_to_predecessor_of<N: JsonRpcNotification>(
515 &mut self,
516 client: ConnectionTo<Host::Counterpart>,
517 source_component_index: usize,
518 notification: N,
519 ) -> Result<(), agent_client_protocol::Error> {
520 tracing::debug!(
521 source_component_index,
522 proxies_len = self.proxies.len(),
523 "send_notification_to_predecessor_of"
524 );
525 if source_component_index == 0 {
526 tracing::debug!("Sending notification directly to client");
527 client.send_notification_to(Client, notification)
528 } else {
529 tracing::debug!(
530 target_proxy = source_component_index - 1,
531 "Sending notification wrapped as SuccessorMessage to proxy"
532 );
533 self.proxies[source_component_index - 1].send_notification(SuccessorMessage {
534 message: notification,
535 meta: None,
536 })
537 }
538 }
539
540 /// Send a message (request or notification) from 'left to right'.
541 /// Left-to-right means from the client or an intermediate proxy to the component
542 /// at `target_component_index` (could be a proxy or the agent).
543 /// Makes changes to select messages along the way (e.g., `initialize` and `session/new`).
544 async fn forward_client_to_agent_message(
545 &mut self,
546 target_component_index: usize,
547 message: Dispatch,
548 client: ConnectionTo<Host::Counterpart>,
549 ) -> Result<(), agent_client_protocol::Error> {
550 tracing::trace!(
551 target_component_index,
552 ?message,
553 "forward_client_to_agent_message"
554 );
555
556 // Ensure components are initialized before processing any message.
557 let message = self.ensure_initialized(client.clone(), message).await?;
558
559 // In proxy mode, if the target is beyond our component chain,
560 // forward to the conductor's own successor (via client connection)
561 if target_component_index < self.proxies.len() {
562 self.forward_message_from_client_to_proxy(target_component_index, message)
563 .await
564 } else {
565 assert_eq!(target_component_index, self.proxies.len());
566
567 debug!(
568 target_component_index,
569 proxies_count = self.proxies.len(),
570 "Proxy mode: forwarding successor message to conductor's successor"
571 );
572 let successor = self.successor.clone();
573 successor.send_message(message, client, self).await
574 }
575 }
576
577 /// Ensures components are initialized before processing messages.
578 ///
579 /// If components haven't been initialized yet, this expects the first message
580 /// to be an `initialize` request and uses it to spawn the component chain.
581 ///
582 /// Returns:
583 /// - `Ok(Some(message))` - Components are initialized, continue processing this message
584 /// - `Ok(None)` - An error response was sent, caller should return early
585 /// - `Err(_)` - A fatal error occurred
586 async fn ensure_initialized(
587 &mut self,
588 client: ConnectionTo<Host::Counterpart>,
589 message: Dispatch,
590 ) -> Result<Dispatch, Error> {
591 // Already initialized - pass through
592 let Some(instantiator) = self.instantiator.take() else {
593 return Ok(message);
594 };
595
596 let host = self.host.clone();
597 let message = host.initialize(message, client, instantiator, self).await?;
598 Ok(message)
599 }
600
601 /// Wrap a proxy component with tracing if tracing is enabled.
602 ///
603 /// Returns the component unchanged if tracing is disabled.
604 fn trace_proxy(
605 &self,
606 proxy_index: ComponentIndex,
607 successor_index: ComponentIndex,
608 component: impl ConnectTo<Conductor>,
609 ) -> DynConnectTo<Conductor> {
610 match &self.trace_handle {
611 Some(trace_handle) => {
612 trace_handle.bridge_component(proxy_index, successor_index, component)
613 }
614 None => DynConnectTo::new(component),
615 }
616 }
617
618 /// Spawn proxy components and add them to the proxies list.
619 fn spawn_proxies(
620 &mut self,
621 client: ConnectionTo<Host::Counterpart>,
622 proxy_components: Vec<DynConnectTo<Conductor>>,
623 ) -> Result<(), agent_client_protocol::Error> {
624 assert!(self.proxies.is_empty());
625
626 let num_proxies = proxy_components.len();
627 info!(proxy_count = num_proxies, "spawn_proxies");
628
629 // Special case: if there are no user-defined proxies
630 // but tracing is enabled, we make a dummy proxy that just
631 // passes through messages but which can trigger the
632 // tracing events.
633 if self.trace_handle.is_some() && num_proxies == 0 {
634 self.connect_to_proxy(
635 &client,
636 0,
637 ComponentIndex::Client,
638 ComponentIndex::Agent,
639 Proxy.builder(),
640 )?;
641 } else {
642 // Spawn each proxy component
643 for (component_index, dyn_component) in proxy_components.into_iter().enumerate() {
644 debug!(component_index, "spawning proxy");
645
646 self.connect_to_proxy(
647 &client,
648 component_index,
649 ComponentIndex::Proxy(component_index),
650 ComponentIndex::successor_of(component_index, num_proxies),
651 dyn_component,
652 )?;
653 }
654 }
655
656 info!(proxy_count = self.proxies.len(), "Proxies spawned");
657
658 Ok(())
659 }
660
661 /// Create a connection to the proxy with index `component_index` implemented in `component`.
662 ///
663 /// If tracing is enabled, the proxy's index is `trace_proxy_index` and its successor is `trace_successor_index`.
664 fn connect_to_proxy(
665 &mut self,
666 client: &ConnectionTo<Host::Counterpart>,
667 component_index: usize,
668 trace_proxy_index: ComponentIndex,
669 trace_successor_index: ComponentIndex,
670 component: impl ConnectTo<Conductor>,
671 ) -> Result<(), Error> {
672 let connection_builder = self.connection_to_proxy(component_index);
673 let connect_component =
674 self.trace_proxy(trace_proxy_index, trace_successor_index, component);
675 let proxy_connection = client.spawn_connection(connection_builder, connect_component)?;
676 self.proxies.push(proxy_connection);
677 Ok(())
678 }
679
680 /// Create the conductor's connection to the proxy with index `component_index`.
681 ///
682 /// Outgoing messages received from the proxy are sent to `self.conductor_tx` as either
683 /// left-to-right or right-to-left messages depending on whether they are wrapped
684 /// in `SuccessorMessage`.
685 fn connection_to_proxy(
686 &mut self,
687 component_index: usize,
688 ) -> Builder<Conductor, impl HandleDispatchFrom<Proxy> + 'static> {
689 type SuccessorDispatch = Dispatch<SuccessorMessage, SuccessorMessage>;
690 let mut conductor_tx = self.conductor_tx.clone();
691 Conductor
692 .builder()
693 .name(format!("conductor-to-component({component_index})"))
694 // Intercept messages sent by the proxy.
695 .on_receive_dispatch(
696 async move |dispatch: Dispatch, _connection| {
697 MatchDispatch::new(dispatch)
698 .if_message(async |dispatch: SuccessorDispatch| {
699 // ------------------
700 // SuccessorMessages sent by the proxy go to its successor.
701 //
702 // Subtle point:
703 //
704 // `ConductorToProxy` has only a single peer, `Agent`. This means that we see
705 // "successor messages" in their "desugared form". So when we intercept an *outgoing*
706 // message that matches `SuccessorMessage`, it could be one of three things
707 //
708 // - A request being sent by the proxy to its successor (hence going left->right)
709 // - A notification being sent by the proxy to its successor (hence going left->right)
710 // - A response to a request sent to the proxy *by* its successor. Here, the *request*
711 // was going right->left, but the *response* (the message we are processing now)
712 // is going left->right.
713 //
714 // So, in all cases, we forward as a left->right message.
715
716 conductor_tx
717 .send(ConductorMessage::LeftToRight {
718 target_component_index: component_index + 1,
719 message: dispatch.map(|r, cx| (r.message, cx), |n| n.message),
720 })
721 .await
722 .map_err(agent_client_protocol::util::internal_error)
723 })
724 .await
725 .otherwise(async |dispatch| {
726 // Other messagrs send by the proxy go its predecessor.
727 // As in the previous handler:
728 //
729 // Messages here are seen in their "desugared form", so we are seeing
730 // one of three things
731 //
732 // - A request being sent by the proxy to its predecessor (hence going right->left)
733 // - A notification being sent by the proxy to its predecessor (hence going right->left)
734 // - A response to a request sent to the proxy *by* its predecessor. Here, the *request*
735 // was going left->right, but the *response* (the message we are processing now)
736 // is going right->left.
737 //
738 // So, in all cases, we forward as a right->left message.
739
740 let message = ConductorMessage::RightToLeft {
741 source_component_index: SourceComponentIndex::Proxy(
742 component_index,
743 ),
744 message: dispatch,
745 };
746 conductor_tx
747 .send(message)
748 .await
749 .map_err(agent_client_protocol::util::internal_error)
750 })
751 .await
752 },
753 agent_client_protocol::on_receive_dispatch!(),
754 )
755 }
756
757 async fn forward_message_from_client_to_proxy(
758 &mut self,
759 target_component_index: usize,
760 message: Dispatch,
761 ) -> Result<(), agent_client_protocol::Error> {
762 tracing::debug!(?message, "forward_message_to_proxy");
763
764 MatchDispatch::new(message)
765 .if_request(async |_request: InitializeProxyRequest, responder| {
766 responder.respond_with_error(
767 agent_client_protocol::Error::invalid_request()
768 .data("initialize/proxy requests are only sent by the conductor"),
769 )
770 })
771 .await
772 .if_request(async |request: InitializeRequest, responder| {
773 // The pattern for `Initialize` messages is a bit subtle.
774 // Proxy receive incoming `Initialize` messages as if they
775 // were a client. The conductor (us) intercepts these and
776 // converts them to an `InitializeProxyRequest`.
777 //
778 // The proxy will then initialize itself and forward an `Initialize`
779 // request to its successor.
780 let sent = self.proxies[target_component_index]
781 .send_request(InitializeProxyRequest::from(request));
782 // The request is rewritten, so `forward_response_to` cannot be
783 // used here; wire up cancellation forwarding explicitly to
784 // keep `initialize` cancellable like every other forwarded
785 // request.
786 let sent = sent.forward_cancellation_from(responder.cancellation());
787 sent.on_receiving_result(async move |result| {
788 tracing::debug!(?result, "got initialize_proxy response from proxy");
789 responder.respond_with_result(result)
790 })
791 })
792 .await
793 .otherwise(async |message| {
794 // Otherwise, just send the message along "as is".
795 self.proxies[target_component_index].send_proxied_message(message)
796 })
797 .await
798 }
799
800 /// Invoked when sending a message from the conductor to the agent that it manages.
801 /// This is called by `self.successor`'s [`ConductorSuccessor::send_message`]
802 /// method when `Link = ConductorToClient` (i.e., the conductor is not itself
803 /// running as a proxy).
804 async fn forward_message_to_agent(
805 &mut self,
806 _client_connection: ConnectionTo<Host::Counterpart>,
807 message: Dispatch,
808 agent_connection: ConnectionTo<Agent>,
809 ) -> Result<(), Error> {
810 MatchDispatch::new(message)
811 .if_request(async |_request: InitializeProxyRequest, responder| {
812 responder.respond_with_error(
813 agent_client_protocol::Error::invalid_request()
814 .data("initialize/proxy requests are only sent by the conductor"),
815 )
816 })
817 .await
818 .otherwise(async |message| {
819 // Forward all other messages to the agent as-is.
820 agent_connection.send_proxied_message_to(Agent, message)
821 })
822 .await
823 }
824}
825
826/// Identifies a component in the conductor's chain for tracing purposes.
827///
828/// Used to track message sources and destinations through the proxy chain.
829#[derive(Debug, Clone, Copy)]
830pub enum ComponentIndex {
831 /// The client (editor) at the start of the chain.
832 Client,
833
834 /// A proxy component at the given index.
835 Proxy(usize),
836
837 /// The successor (agent in agent mode, outer conductor in proxy mode).
838 Agent,
839}
840
841impl ComponentIndex {
842 /// Return the index for the predecessor of `proxy_index`, which might be `Client`.
843 #[must_use]
844 pub fn predecessor_of(proxy_index: usize) -> Self {
845 match proxy_index.checked_sub(1) {
846 Some(p_i) => ComponentIndex::Proxy(p_i),
847 None => ComponentIndex::Client,
848 }
849 }
850
851 /// Return the index for the predecessor of `proxy_index`, which might be `Client`.
852 #[must_use]
853 pub fn successor_of(proxy_index: usize, num_proxies: usize) -> Self {
854 if proxy_index == num_proxies {
855 ComponentIndex::Agent
856 } else {
857 ComponentIndex::Proxy(proxy_index + 1)
858 }
859 }
860}
861
862/// Identifies the source of an agent-to-client message.
863///
864/// This enum handles the fact that the conductor may receive messages from two different sources:
865/// 1. From one of its managed components (identified by index)
866/// 2. From the conductor's own successor in a larger proxy chain (when in proxy mode)
867#[derive(Debug, Clone, Copy)]
868pub enum SourceComponentIndex {
869 /// Message from a specific component at the given index in the managed chain.
870 Proxy(usize),
871
872 /// Message from the conductor's agent or successor.
873 Successor,
874}
875
876/// Trait for lazy proxy instantiation (proxy mode).
877///
878/// Used by conductors in proxy mode (`ConductorToConductor`) where all components
879/// are proxies that forward to an outer conductor.
880pub trait InstantiateProxies: Send {
881 /// Instantiate proxy components based on the Initialize request.
882 ///
883 /// Returns proxy components typed as `DynConnectTo<Conductor>` since proxies
884 /// communicate with the conductor.
885 fn instantiate_proxies(
886 self: Box<Self>,
887 req: InitializeRequest,
888 ) -> futures::future::BoxFuture<
889 'static,
890 Result<(InitializeRequest, Vec<DynConnectTo<Conductor>>), agent_client_protocol::Error>,
891 >;
892}
893
894/// Simple implementation: provide all proxy components unconditionally.
895///
896/// Requires `T: ConnectTo<Conductor>`.
897impl<T> InstantiateProxies for Vec<T>
898where
899 T: ConnectTo<Conductor> + 'static,
900{
901 fn instantiate_proxies(
902 self: Box<Self>,
903 req: InitializeRequest,
904 ) -> futures::future::BoxFuture<
905 'static,
906 Result<(InitializeRequest, Vec<DynConnectTo<Conductor>>), agent_client_protocol::Error>,
907 > {
908 Box::pin(async move {
909 let components: Vec<DynConnectTo<Conductor>> =
910 (*self).into_iter().map(|c| DynConnectTo::new(c)).collect();
911 Ok((req, components))
912 })
913 }
914}
915
916/// Dynamic implementation: closure receives the Initialize request and returns proxies.
917impl<F, Fut> InstantiateProxies for F
918where
919 F: FnOnce(InitializeRequest) -> Fut + Send + 'static,
920 Fut: std::future::Future<
921 Output = Result<
922 (InitializeRequest, Vec<DynConnectTo<Conductor>>),
923 agent_client_protocol::Error,
924 >,
925 > + Send
926 + 'static,
927{
928 fn instantiate_proxies(
929 self: Box<Self>,
930 req: InitializeRequest,
931 ) -> futures::future::BoxFuture<
932 'static,
933 Result<(InitializeRequest, Vec<DynConnectTo<Conductor>>), agent_client_protocol::Error>,
934 > {
935 Box::pin(async move { (*self)(req).await })
936 }
937}
938
939/// Trait for lazy proxy and agent instantiation (agent mode).
940///
941/// Used by conductors in agent mode (`ConductorToClient`) where there are
942/// zero or more proxies followed by an agent component.
943pub trait InstantiateProxiesAndAgent: Send {
944 /// Instantiate proxy and agent components based on the Initialize request.
945 ///
946 /// Returns the (possibly modified) request, a vector of proxy components
947 /// (typed as `DynConnectTo<Conductor>`), and the agent component
948 /// (typed as `DynConnectTo<Client>`).
949 fn instantiate_proxies_and_agent(
950 self: Box<Self>,
951 req: InitializeRequest,
952 ) -> futures::future::BoxFuture<
953 'static,
954 Result<
955 (
956 InitializeRequest,
957 Vec<DynConnectTo<Conductor>>,
958 DynConnectTo<Client>,
959 ),
960 agent_client_protocol::Error,
961 >,
962 >;
963}
964
965/// Wrapper to convert a single agent component (no proxies) into InstantiateProxiesAndAgent.
966#[derive(Debug)]
967pub struct AgentOnly<A>(pub A);
968
969impl<A: ConnectTo<Client> + 'static> InstantiateProxiesAndAgent for AgentOnly<A> {
970 fn instantiate_proxies_and_agent(
971 self: Box<Self>,
972 req: InitializeRequest,
973 ) -> futures::future::BoxFuture<
974 'static,
975 Result<
976 (
977 InitializeRequest,
978 Vec<DynConnectTo<Conductor>>,
979 DynConnectTo<Client>,
980 ),
981 agent_client_protocol::Error,
982 >,
983 > {
984 Box::pin(async move { Ok((req, Vec::new(), DynConnectTo::new(self.0))) })
985 }
986}
987
988/// Builder for creating proxies and agent components.
989///
990/// # Example
991/// ```ignore
992/// ProxiesAndAgent::new(ElizaAgent::new())
993/// .proxy(LoggingProxy::new())
994/// .proxy(AuthProxy::new())
995/// ```
996#[derive(Debug)]
997pub struct ProxiesAndAgent {
998 proxies: Vec<DynConnectTo<Conductor>>,
999 agent: DynConnectTo<Client>,
1000}
1001
1002impl ProxiesAndAgent {
1003 /// Create a new builder with the given agent component.
1004 pub fn new(agent: impl ConnectTo<Client> + 'static) -> Self {
1005 Self {
1006 proxies: vec![],
1007 agent: DynConnectTo::new(agent),
1008 }
1009 }
1010
1011 /// Add a single proxy component.
1012 #[must_use]
1013 pub fn proxy(mut self, proxy: impl ConnectTo<Conductor> + 'static) -> Self {
1014 self.proxies.push(DynConnectTo::new(proxy));
1015 self
1016 }
1017
1018 /// Add multiple proxy components.
1019 #[must_use]
1020 pub fn proxies<P, I>(mut self, proxies: I) -> Self
1021 where
1022 P: ConnectTo<Conductor> + 'static,
1023 I: IntoIterator<Item = P>,
1024 {
1025 self.proxies
1026 .extend(proxies.into_iter().map(DynConnectTo::new));
1027 self
1028 }
1029}
1030
1031impl InstantiateProxiesAndAgent for ProxiesAndAgent {
1032 fn instantiate_proxies_and_agent(
1033 self: Box<Self>,
1034 req: InitializeRequest,
1035 ) -> futures::future::BoxFuture<
1036 'static,
1037 Result<
1038 (
1039 InitializeRequest,
1040 Vec<DynConnectTo<Conductor>>,
1041 DynConnectTo<Client>,
1042 ),
1043 agent_client_protocol::Error,
1044 >,
1045 > {
1046 Box::pin(async move { Ok((req, self.proxies, self.agent)) })
1047 }
1048}
1049
1050/// Dynamic implementation: closure receives the Initialize request and returns proxies + agent.
1051impl<F, Fut> InstantiateProxiesAndAgent for F
1052where
1053 F: FnOnce(InitializeRequest) -> Fut + Send + 'static,
1054 Fut: std::future::Future<
1055 Output = Result<
1056 (
1057 InitializeRequest,
1058 Vec<DynConnectTo<Conductor>>,
1059 DynConnectTo<Client>,
1060 ),
1061 agent_client_protocol::Error,
1062 >,
1063 > + Send
1064 + 'static,
1065{
1066 fn instantiate_proxies_and_agent(
1067 self: Box<Self>,
1068 req: InitializeRequest,
1069 ) -> futures::future::BoxFuture<
1070 'static,
1071 Result<
1072 (
1073 InitializeRequest,
1074 Vec<DynConnectTo<Conductor>>,
1075 DynConnectTo<Client>,
1076 ),
1077 agent_client_protocol::Error,
1078 >,
1079 > {
1080 Box::pin(async move { (*self)(req).await })
1081 }
1082}
1083
1084/// Messages sent to the conductor's main event loop for routing.
1085///
1086/// These messages enable the conductor to route communication between:
1087/// - The editor and the first component
1088/// - Components and their successors in the chain
1089/// - Components and their clients (editor or predecessor)
1090///
1091/// All spawned tasks send messages via this enum through a shared channel,
1092/// allowing centralized routing logic in the `serve()` loop.
1093#[derive(Debug)]
1094pub enum ConductorMessage {
1095 /// If this message is a request or notification, then it is going "left-to-right"
1096 /// (e.g., a component making a request of its successor).
1097 ///
1098 /// If this message is a response, then it is going right-to-left
1099 /// (i.e., the successor answering a request made by its predecessor).
1100 LeftToRight {
1101 target_component_index: usize,
1102 message: Dispatch,
1103 },
1104
1105 /// If this message is a request or notification, then it is going "right-to-left"
1106 /// (e.g., a component making a request of its predecessor).
1107 ///
1108 /// If this message is a response, then it is going "left-to-right"
1109 /// (i.e., the predecessor answering a request made by its successor).
1110 RightToLeft {
1111 source_component_index: SourceComponentIndex,
1112 message: Dispatch,
1113 },
1114}
1115
1116/// Trait implemented for the two links the conductor can use:
1117///
1118/// * ConductorToClient -- conductor is acting as an agent, so when its last proxy sends to its successor, the conductor sends that message to its agent component
1119/// * ConductorToConductor -- conductor is acting as a proxy, so when its last proxy sends to its successor, the (inner) conductor sends that message to its successor, via the outer conductor
1120pub trait ConductorHostRole: Role<Counterpart: HasPeer<Client>> {
1121 /// The type used to instantiate components for this link type.
1122 type Instantiator: Send;
1123
1124 /// Handle initialization: parse the init request, instantiate components, and spawn them.
1125 ///
1126 /// Takes ownership of the instantiator and returns the (possibly modified) init request
1127 /// wrapped in a Dispatch for forwarding.
1128 fn initialize(
1129 &self,
1130 message: Dispatch,
1131 connection: ConnectionTo<Self::Counterpart>,
1132 instantiator: Self::Instantiator,
1133 responder: &mut ConductorResponder<Self>,
1134 ) -> impl Future<Output = Result<Dispatch, agent_client_protocol::Error>> + Send;
1135
1136 /// Handle an incoming message from the client or conductor, depending on `Self`
1137 fn handle_dispatch(
1138 &self,
1139 message: Dispatch,
1140 connection: ConnectionTo<Self::Counterpart>,
1141 conductor_tx: &mut mpsc::Sender<ConductorMessage>,
1142 ) -> impl Future<Output = Result<Handled<Dispatch>, agent_client_protocol::Error>> + Send;
1143}
1144
1145/// Conductor acting as an agent
1146impl ConductorHostRole for Agent {
1147 type Instantiator = Box<dyn InstantiateProxiesAndAgent>;
1148
1149 async fn initialize(
1150 &self,
1151 message: Dispatch,
1152 client_connection: ConnectionTo<Client>,
1153 instantiator: Self::Instantiator,
1154 responder: &mut ConductorResponder<Self>,
1155 ) -> Result<Dispatch, agent_client_protocol::Error> {
1156 let invalid_request = || Error::invalid_request().data("expected `initialize` request");
1157
1158 // Not yet initialized - expect an initialize request.
1159 // Error if we get anything else.
1160 let Dispatch::Request(request, init_responder) = message else {
1161 message.respond_with_error(invalid_request(), client_connection.clone())?;
1162 return Err(invalid_request());
1163 };
1164 if !InitializeRequest::matches_method(request.method()) {
1165 init_responder.respond_with_error(invalid_request())?;
1166 return Err(invalid_request());
1167 }
1168
1169 let init_request =
1170 match InitializeRequest::parse_message(request.method(), request.params()) {
1171 Ok(r) => r,
1172 Err(error) => {
1173 init_responder.respond_with_error(error)?;
1174 return Err(invalid_request());
1175 }
1176 };
1177
1178 // Instantiate proxies and agent
1179 let (modified_req, proxy_components, agent_component) = instantiator
1180 .instantiate_proxies_and_agent(init_request)
1181 .await?;
1182
1183 // Spawn the agent component
1184 debug!(?agent_component, "spawning agent");
1185
1186 let connection_to_agent = client_connection.spawn_connection(
1187 Client
1188 .builder()
1189 .name("conductor-to-agent")
1190 // Intercept agent-to-client messages from the agent.
1191 .on_receive_dispatch(
1192 {
1193 let mut conductor_tx = responder.conductor_tx.clone();
1194 async move |dispatch: Dispatch, _cx| {
1195 conductor_tx
1196 .send(ConductorMessage::RightToLeft {
1197 source_component_index: SourceComponentIndex::Successor,
1198 message: dispatch,
1199 })
1200 .await
1201 .map_err(agent_client_protocol::util::internal_error)
1202 }
1203 },
1204 agent_client_protocol::on_receive_dispatch!(),
1205 ),
1206 agent_component,
1207 )?;
1208 responder.successor = Arc::new(connection_to_agent);
1209
1210 // Spawn the proxy components
1211 responder.spawn_proxies(client_connection.clone(), proxy_components)?;
1212
1213 Ok(Dispatch::Request(
1214 modified_req.to_untyped_message()?,
1215 init_responder,
1216 ))
1217 }
1218
1219 async fn handle_dispatch(
1220 &self,
1221 message: Dispatch,
1222 client_connection: ConnectionTo<Client>,
1223 conductor_tx: &mut mpsc::Sender<ConductorMessage>,
1224 ) -> Result<Handled<Dispatch>, agent_client_protocol::Error> {
1225 tracing::debug!(
1226 method = ?message.method(),
1227 "ConductorToClient::handle_dispatch"
1228 );
1229 MatchDispatchFrom::new(message, &client_connection)
1230 // Any incoming messages from the client are client-to-agent messages targeting the first component.
1231 .if_message_from(Client, async move |message: Dispatch| {
1232 tracing::debug!(
1233 method = ?message.method(),
1234 "ConductorToClient::handle_dispatch - matched Client"
1235 );
1236 ConductorImpl::<Self>::incoming_message_from_client(conductor_tx, message).await
1237 })
1238 .await
1239 .done()
1240 }
1241}
1242
1243/// Conductor acting as a proxy
1244impl ConductorHostRole for Proxy {
1245 type Instantiator = Box<dyn InstantiateProxies>;
1246
1247 async fn initialize(
1248 &self,
1249 message: Dispatch,
1250 client_connection: ConnectionTo<Conductor>,
1251 instantiator: Self::Instantiator,
1252 responder: &mut ConductorResponder<Self>,
1253 ) -> Result<Dispatch, agent_client_protocol::Error> {
1254 let invalid_request = || Error::invalid_request().data("expected `initialize` request");
1255
1256 // Not yet initialized - expect an InitializeProxy request.
1257 // Error if we get anything else.
1258 let Dispatch::Request(request, init_responder) = message else {
1259 message.respond_with_error(invalid_request(), client_connection.clone())?;
1260 return Err(invalid_request());
1261 };
1262 if !InitializeProxyRequest::matches_method(request.method()) {
1263 init_responder.respond_with_error(invalid_request())?;
1264 return Err(invalid_request());
1265 }
1266
1267 let InitializeProxyRequest { initialize } =
1268 match InitializeProxyRequest::parse_message(request.method(), request.params()) {
1269 Ok(r) => r,
1270 Err(error) => {
1271 init_responder.respond_with_error(error)?;
1272 return Err(invalid_request());
1273 }
1274 };
1275
1276 tracing::debug!("ensure_initialized: InitializeProxyRequest (proxy mode)");
1277
1278 // Instantiate proxies (no agent in proxy mode)
1279 let (modified_req, proxy_components) = instantiator.instantiate_proxies(initialize).await?;
1280
1281 // In proxy mode, our successor is the outer conductor (via our client connection)
1282 responder.successor = Arc::new(GrandSuccessor);
1283
1284 // Spawn the proxy components
1285 responder.spawn_proxies(client_connection.clone(), proxy_components)?;
1286
1287 Ok(Dispatch::Request(
1288 modified_req.to_untyped_message()?,
1289 init_responder,
1290 ))
1291 }
1292
1293 async fn handle_dispatch(
1294 &self,
1295 message: Dispatch,
1296 client_connection: ConnectionTo<Conductor>,
1297 conductor_tx: &mut mpsc::Sender<ConductorMessage>,
1298 ) -> Result<Handled<Dispatch>, agent_client_protocol::Error> {
1299 tracing::debug!(
1300 method = ?message.method(),
1301 ?message,
1302 "ConductorToConductor::handle_dispatch"
1303 );
1304 MatchDispatchFrom::new(message, &client_connection)
1305 .if_message_from(Agent, {
1306 // Messages from our successor arrive already unwrapped
1307 // (RemoteRoleStyle::Successor strips the SuccessorMessage envelope).
1308 async |message: Dispatch| {
1309 tracing::debug!(
1310 method = ?message.method(),
1311 "ConductorToConductor::handle_dispatch - matched Agent"
1312 );
1313 let mut conductor_tx = conductor_tx.clone();
1314 ConductorImpl::<Self>::incoming_message_from_agent(&mut conductor_tx, message)
1315 .await
1316 }
1317 })
1318 .await
1319 // Any incoming messages from the client are client-to-agent messages targeting the first component.
1320 .if_message_from(Client, async |message: Dispatch| {
1321 tracing::debug!(
1322 method = ?message.method(),
1323 "ConductorToConductor::handle_dispatch - matched Client"
1324 );
1325 let mut conductor_tx = conductor_tx.clone();
1326 ConductorImpl::<Self>::incoming_message_from_client(&mut conductor_tx, message)
1327 .await
1328 })
1329 .await
1330 .done()
1331 }
1332}
1333
1334pub trait ConductorSuccessor<Host: ConductorHostRole>: Send + Sync + 'static {
1335 /// Send a message to the successor.
1336 fn send_message<'a>(
1337 &self,
1338 message: Dispatch,
1339 connection_to_conductor: ConnectionTo<Host::Counterpart>,
1340 responder: &'a mut ConductorResponder<Host>,
1341 ) -> BoxFuture<'a, Result<(), agent_client_protocol::Error>>;
1342}
1343
1344impl<Host: ConductorHostRole> ConductorSuccessor<Host> for agent_client_protocol::Error {
1345 fn send_message<'a>(
1346 &self,
1347 #[expect(unused_variables)] message: Dispatch,
1348 #[expect(unused_variables)] connection_to_conductor: ConnectionTo<Host::Counterpart>,
1349 #[expect(unused_variables)] responder: &'a mut ConductorResponder<Host>,
1350 ) -> BoxFuture<'a, Result<(), agent_client_protocol::Error>> {
1351 let error = self.clone();
1352 Box::pin(std::future::ready(Err(error)))
1353 }
1354}
1355
1356/// A dummy type handling messages sent to the conductor's
1357/// successor when it is acting as a proxy.
1358struct GrandSuccessor;
1359
1360/// When the conductor is acting as an proxy, messages sent by
1361/// the last proxy go to the conductor's successor.
1362///
1363/// ```text
1364/// client --> Conductor -----------------------------> GrandSuccessor
1365/// | |
1366/// +-> Proxy[0] -> ... -> Proxy[n-1] -+
1367/// ```
1368impl ConductorSuccessor<Proxy> for GrandSuccessor {
1369 fn send_message<'a>(
1370 &self,
1371 message: Dispatch,
1372 connection: ConnectionTo<Conductor>,
1373 _responder: &'a mut ConductorResponder<Proxy>,
1374 ) -> BoxFuture<'a, Result<(), agent_client_protocol::Error>> {
1375 Box::pin(async move {
1376 debug!("Proxy mode: forwarding successor message to conductor's successor");
1377 connection.send_proxied_message_to(Agent, message)
1378 })
1379 }
1380}
1381
1382/// When the conductor is acting as an agent, messages sent by
1383/// the last proxy to its successor go to the internal agent
1384/// (`self`).
1385impl ConductorSuccessor<Agent> for ConnectionTo<Agent> {
1386 fn send_message<'a>(
1387 &self,
1388 message: Dispatch,
1389 connection: ConnectionTo<Client>,
1390 responder: &'a mut ConductorResponder<Agent>,
1391 ) -> BoxFuture<'a, Result<(), agent_client_protocol::Error>> {
1392 let connection_to_agent = self.clone();
1393 Box::pin(async move {
1394 debug!("Proxy mode: forwarding successor message to conductor's successor");
1395 responder
1396 .forward_message_to_agent(connection, message, connection_to_agent)
1397 .await
1398 })
1399 }
1400}