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