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}