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