sacp/
component.rs

1//! Component abstraction for agents and proxies.
2//!
3//! This module provides the [`Component`] trait that defines the interface for things
4//! that can be run as part of a conductor's chain - agents, proxies, or any ACP-speaking component.
5//!
6//! ## Usage
7//!
8//! Components serve by forwarding to other components, creating a chain of message processors.
9//! The type parameter `L` is the link type that this component can serve as a transport for.
10//!
11//! To implement a component, implement the `serve` method:
12//!
13//! ```rust,ignore
14//! use sacp::component::Component;
15//! use sacp::link::AgentToClient;
16//!
17//! struct MyAgent {
18//!     // configuration fields
19//! }
20//!
21//! // An agent serves as a transport for AgentToClient connections
22//! impl Component<AgentToClient> for MyAgent {
23//!     async fn serve(self, client: impl Component<AgentToClient::ConnectsTo>) -> Result<(), sacp::Error> {
24//!         sacp::AgentToClient::builder()
25//!             .name("my-agent")
26//!             // configure handlers here
27//!             .serve(client)
28//!             .await
29//!     }
30//! }
31//! ```
32
33use futures::future::BoxFuture;
34use std::{fmt::Debug, future::Future, marker::PhantomData};
35
36use crate::{Channel, link::JrLink};
37
38/// A component that can participate in the Agent-Client Protocol.
39///
40/// This trait represents anything that can communicate via JSON-RPC messages over channels -
41/// agents, proxies, in-process connections, or any ACP-speaking component.
42///
43/// The type parameter `L` is the link type that this component can serve as a transport for.
44/// For example:
45/// - An agent implements `Component<AgentToClient>` - it serves connections where the
46///   local side is an agent talking to a client
47/// - A proxy implements `Component<ProxyToConductor>` - it serves connections where the
48///   local side is a proxy talking to a conductor
49/// - Transports like `Channel` implement `Component<L>` for all `L` since they're link-agnostic
50///
51/// # Component Types
52///
53/// The trait is implemented by several built-in types representing different communication patterns:
54///
55/// - **[`ByteStreams`]**: A component communicating over byte streams (stdin/stdout, sockets, etc.)
56/// - **[`Channel`]**: A component communicating via in-process message channels (for testing or direct connections)
57/// - **[`AcpAgent`]**: An external agent running in a separate process with stdio communication
58/// - **Custom components**: Proxies, transformers, or any ACP-aware service
59///
60/// # Two Ways to Serve
61///
62/// Components can be used in two ways:
63///
64/// 1. **`serve(client)`** - Serve by forwarding to another component (most components implement this)
65/// 2. **`into_server()`** - Convert into a channel endpoint and server future (base cases implement this)
66///
67/// Most components only need to implement `serve(client)` - the `into_server()` method has a default
68/// implementation that creates an intermediate channel and calls `serve`.
69///
70/// # Implementation Example
71///
72/// ```rust,ignore
73/// use sacp::{Component, peer::AgentToClient};
74///
75/// struct MyAgent {
76///     config: AgentConfig,
77/// }
78///
79/// impl Component<AgentToClient> for MyAgent {
80///     async fn serve(self, client: impl Component<<AgentToClient as JrLink>::ConnectsTo>) -> Result<(), sacp::Error> {
81///         // Set up connection that forwards to client
82///         sacp::AgentToClient::builder()
83///             .name("my-agent")
84///             .on_receive_request(async |req: MyRequest, cx| {
85///                 // Handle request
86///                 cx.respond(MyResponse { status: "ok".into() })
87///             })
88///             .serve(client)
89///             .await
90///     }
91/// }
92/// ```
93///
94/// # Heterogeneous Collections
95///
96/// For storing different component types in the same collection, use [`DynComponent`]:
97///
98/// ```rust,ignore
99/// use sacp::link::AgentToClient;
100///
101/// let components: Vec<DynComponent<AgentToClient>> = vec![
102///     DynComponent::new(proxy1),
103///     DynComponent::new(proxy2),
104///     DynComponent::new(agent),
105/// ];
106/// ```
107///
108/// [`ByteStreams`]: crate::ByteStreams
109/// [`AcpAgent`]: https://docs.rs/sacp-tokio/latest/sacp_tokio/struct.AcpAgent.html
110/// [`JrConnectionBuilder`]: crate::JrConnectionBuilder
111pub trait Component<L: JrLink>: Send + 'static {
112    /// Serve this component by forwarding to a client component.
113    ///
114    /// Most components implement this method to set up their connection and
115    /// forward messages to the provided client.
116    ///
117    /// # Arguments
118    ///
119    /// * `client` - The component to forward messages to (implements `Component<L::ConnectsTo>`)
120    ///
121    /// # Returns
122    ///
123    /// A future that resolves when the component stops serving, either successfully
124    /// or with an error. The future must be `Send`.
125    fn serve(
126        self,
127        client: impl Component<L::ConnectsTo>,
128    ) -> impl Future<Output = Result<(), crate::Error>> + Send;
129
130    /// Convert this component into a channel endpoint and server future.
131    ///
132    /// This method returns:
133    /// - A `Channel` that can be used to communicate with this component
134    /// - A `BoxFuture` that runs the component's server logic
135    ///
136    /// The default implementation creates an intermediate channel pair and calls `serve`
137    /// on one endpoint while returning the other endpoint for the caller to use.
138    ///
139    /// Base cases like `Channel` and `ByteStreams` override this to avoid unnecessary copying.
140    ///
141    /// # Returns
142    ///
143    /// A tuple of `(Channel, BoxFuture)` where the channel is for the caller to use
144    /// and the future must be spawned to run the server.
145    fn into_server(self) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>)
146    where
147        Self: Sized,
148    {
149        let (channel_a, channel_b) = Channel::duplex();
150        let future = Box::pin(self.serve(channel_b));
151        (channel_a, future)
152    }
153}
154
155/// Type-erased component trait for object-safe dynamic dispatch.
156///
157/// This trait is internal and used by [`DynComponent`]. Users should implement
158/// [`Component`] instead, which is automatically converted to `ErasedComponent`
159/// via a blanket implementation.
160trait ErasedComponent<L: JrLink>: Send {
161    fn type_name(&self) -> String;
162
163    fn serve_erased(
164        self: Box<Self>,
165        client: Box<dyn ErasedComponent<L::ConnectsTo>>,
166    ) -> BoxFuture<'static, Result<(), crate::Error>>;
167
168    fn into_server_erased(
169        self: Box<Self>,
170    ) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>);
171}
172
173/// Blanket implementation: any `Component<L>` can be type-erased.
174impl<C: Component<L>, L: JrLink> ErasedComponent<L> for C {
175    fn type_name(&self) -> String {
176        std::any::type_name::<C>().to_string()
177    }
178
179    fn serve_erased(
180        self: Box<Self>,
181        client: Box<dyn ErasedComponent<L::ConnectsTo>>,
182    ) -> BoxFuture<'static, Result<(), crate::Error>> {
183        Box::pin(async move {
184            (*self)
185                .serve(DynComponent {
186                    inner: client,
187                    _marker: PhantomData,
188                })
189                .await
190        })
191    }
192
193    fn into_server_erased(
194        self: Box<Self>,
195    ) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>) {
196        (*self).into_server()
197    }
198}
199
200/// A dynamically-typed component for heterogeneous collections.
201///
202/// This type wraps any [`Component`] implementation and provides dynamic dispatch,
203/// allowing you to store different component types in the same collection.
204///
205/// The type parameter `L` is the link type that all components in the
206/// collection can serve as transports for.
207///
208/// # Examples
209///
210/// ```rust,ignore
211/// use sacp::{DynComponent, peer::AgentToClient};
212///
213/// let components: Vec<DynComponent<AgentToClient>> = vec![
214///     DynComponent::new(Proxy1),
215///     DynComponent::new(Proxy2),
216///     DynComponent::new(Agent),
217/// ];
218/// ```
219pub struct DynComponent<L: JrLink> {
220    inner: Box<dyn ErasedComponent<L>>,
221    _marker: PhantomData<L>,
222}
223
224impl<L: JrLink> DynComponent<L> {
225    /// Create a new `DynComponent` from any type implementing [`Component`].
226    pub fn new<C: Component<L>>(component: C) -> Self {
227        Self {
228            inner: Box::new(component),
229            _marker: PhantomData,
230        }
231    }
232
233    /// Returns the type name of the wrapped component.
234    pub fn type_name(&self) -> String {
235        self.inner.type_name()
236    }
237}
238
239impl<L: JrLink> Component<L> for DynComponent<L> {
240    async fn serve(self, client: impl Component<L::ConnectsTo>) -> Result<(), crate::Error> {
241        self.inner
242            .serve_erased(Box::new(client) as Box<dyn ErasedComponent<L::ConnectsTo>>)
243            .await
244    }
245
246    fn into_server(self) -> (Channel, BoxFuture<'static, Result<(), crate::Error>>) {
247        self.inner.into_server_erased()
248    }
249}
250
251impl<L: JrLink> Debug for DynComponent<L> {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        f.debug_struct("DynComponent")
254            .field("type_name", &self.type_name())
255            .finish()
256    }
257}