pub trait ConnectTo<R: Role>: Send + 'static {
// Required method
fn connect_to(
self,
client: impl ConnectTo<R::Counterpart>,
) -> impl Future<Output = Result<()>> + Send;
// Provided method
fn into_channel_and_future(self) -> (Channel, Option<ConnectionDriver>)
where Self: Sized { ... }
}Expand description
A component that can exchange JSON-RPC messages to an endpoint playing the role R
(e.g., an ACP Agent or an MCP Server).
This trait represents anything that can communicate via JSON-RPC messages over channels - agents, proxies, in-process connections, or any ACP-speaking component.
The type parameter R is the role that this component connects to (its counterpart).
For example:
- An agent implements
ConnectTo<Client>to connect to clients - A proxy implements
ConnectTo<Conductor>to connect to conductors - Transports like
ChannelimplementConnectTo<R>for everyRbecause they are role-agnostic
§Component Types
The trait is implemented by several built-in types representing different communication patterns:
Lines: A component communicating over asynchronous line streamsByteStreams: A component communicating over byte streams (stdin/stdout, sockets, etc.)Channel: A component communicating via in-process message channels (for testing or direct connections)- Custom components: Proxies, transformers, or any ACP-aware service
AcpAgent: An external agent running in a separate process with stdio communication
§Two Ways to Connect
Components can be used in two ways:
connect_to(client)- Connect directly to another component (most components implement this)into_channel_and_future()- Obtain a channel endpoint and optional owned driver
Most components only need to implement connect_to(client). The
into_channel_and_future() method has a default implementation that creates an intermediate
channel and calls connect_to.
§Implementation Example
use agent_client_protocol::{Agent, Client, ConnectTo, Result};
struct MyAgent;
impl ConnectTo<Client> for MyAgent {
async fn connect_to(self, client: impl ConnectTo<Agent>) -> Result<()> {
Agent.builder()
.name("my-agent")
.connect_to(client)
.await
}
}§Heterogeneous Collections
For storing different component types in the same collection, use DynConnectTo:
use agent_client_protocol::{Channel, Client, DynConnectTo};
let (first, _first_peer) = Channel::duplex();
let (second, _second_peer) = Channel::duplex();
let components: Vec<DynConnectTo<Client>> = vec![
DynConnectTo::new(first),
DynConnectTo::new(second),
];
assert_eq!(components.len(), 2);Required Methods§
Sourcefn connect_to(
self,
client: impl ConnectTo<R::Counterpart>,
) -> impl Future<Output = Result<()>> + Send
fn connect_to( self, client: impl ConnectTo<R::Counterpart>, ) -> impl Future<Output = Result<()>> + Send
Connect this component to another component.
Most components implement this method to set up their connection and exchange messages with the provided component.
§Arguments
client- The component to connect to (implementsConnectTo<R::Counterpart>)
§Returns
A future that resolves when the connection ends, either successfully
or with an error. The future must be Send.
A component that buffers outbound messages should not return Ok(())
merely because its client completed: it should first finish messages the
client already transferred to it. This lets wrappers preserve graceful
drain guarantees through to the physical transport sink. Errors may
still terminate the connection immediately.
Provided Methods§
Sourcefn into_channel_and_future(self) -> (Channel, Option<ConnectionDriver>)where
Self: Sized,
fn into_channel_and_future(self) -> (Channel, Option<ConnectionDriver>)where
Self: Sized,
Convert this component into a channel endpoint and optional owned driver.
The returned Channel is the canonical frame-aware boundary. It carries
complete TransportFrame values so default
adapters preserve batch grouping.
This method returns:
- A
Channelthat can be used to communicate with this component Some(ConnectionDriver)when the component owns work to driveNonewhen the channel halves alone own the endpoint’s lifetime
The default implementation creates an intermediate channel pair and calls connect_to
on one endpoint while returning the other endpoint for the caller to use.
Base cases like Channel and ByteStreams override this to avoid unnecessary copying.
§Returns
A tuple of (Channel, Option<ConnectionDriver>). Owned drivers must be
polled concurrently with channel traffic. Successful owned completion
ends the endpoint after draining accepted output. None is not EOF:
preserve both independent channel half-closes.
Absence must be handled explicitly; the optional driver is not awaitable:
use agent_client_protocol::{Channel, ConnectTo, UntypedRole};
let (channel, _peer) = Channel::duplex();
let (_channel, driver) = ConnectTo::<UntypedRole>::into_channel_and_future(channel);
driver.await?;Once present, the owned driver itself is awaitable:
use agent_client_protocol::{Channel, ConnectionDriver, Result};
async fn drive_owned_work((_channel, driver): (Channel, Option<ConnectionDriver>)) -> Result<()> {
if let Some(driver) = driver {
// In a real adapter, also poll the channel traffic concurrently.
driver.await?;
}
Ok(())
}Dyn Compatibility§
This trait is not dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementors§
impl ConnectTo<Client> for AgentProtocolRouter
unstable_protocol_v2 only.impl ConnectTo<Conductor> for ProxyProtocolRouter
unstable_protocol_v2 only.impl<Counterpart: AcpAgentCounterpartRole> ConnectTo<Counterpart> for AcpAgent
process and non-target_family=wasm only.impl<Counterpart: Role> ConnectTo<Counterpart> for Stdio
stdio and non-target_family=wasm only.