Skip to main content

anyclaw_sdk_agent/
adapter.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use anyclaw_sdk_types::{
5    InitializeParams, InitializeResult, PermissionRequest, SessionNewParams, SessionNewResult,
6    SessionPromptParams, SessionUpdateEvent,
7};
8
9use crate::error::AgentSdkError;
10
11/// Per-method hooks for ACP lifecycle. Implement this trait to intercept and transform
12/// ACP messages between the supervisor and agent subprocess.
13///
14/// All methods have default passthrough implementations — override only the hooks you need.
15pub trait AgentAdapter: Send + Sync + 'static {
16    /// Transform `initialize` request params before they reach the agent.
17    fn on_initialize_params(
18        &self,
19        params: InitializeParams,
20    ) -> impl Future<Output = Result<InitializeParams, AgentSdkError>> + Send {
21        async move { Ok(params) }
22    }
23
24    /// Transform `initialize` response before it reaches the supervisor.
25    fn on_initialize_result(
26        &self,
27        result: InitializeResult,
28    ) -> impl Future<Output = Result<InitializeResult, AgentSdkError>> + Send {
29        async move { Ok(result) }
30    }
31
32    /// Transform `session/new` request params before they reach the agent.
33    fn on_session_new_params(
34        &self,
35        params: SessionNewParams,
36    ) -> impl Future<Output = Result<SessionNewParams, AgentSdkError>> + Send {
37        async move { Ok(params) }
38    }
39
40    /// Transform `session/new` response before it reaches the supervisor.
41    fn on_session_new_result(
42        &self,
43        result: SessionNewResult,
44    ) -> impl Future<Output = Result<SessionNewResult, AgentSdkError>> + Send {
45        async move { Ok(result) }
46    }
47
48    /// Transform `session/prompt` request params before they reach the agent.
49    fn on_session_prompt_params(
50        &self,
51        params: SessionPromptParams,
52    ) -> impl Future<Output = Result<SessionPromptParams, AgentSdkError>> + Send {
53        async move { Ok(params) }
54    }
55
56    /// Transform a `session/update` streaming event before it reaches the supervisor.
57    fn on_session_update(
58        &self,
59        event: SessionUpdateEvent,
60    ) -> impl Future<Output = Result<SessionUpdateEvent, AgentSdkError>> + Send {
61        async move { Ok(event) }
62    }
63
64    /// Transform a permission request event before it reaches the supervisor.
65    fn on_permission_request(
66        &self,
67        request: PermissionRequest,
68    ) -> impl Future<Output = Result<PermissionRequest, AgentSdkError>> + Send {
69        async move { Ok(request) }
70    }
71}
72
73/// Dyn-compatible alias for [`AgentAdapter`]. Use `Box<dyn DynAgentAdapter>` for runtime dispatch.
74/// Implementors write `impl AgentAdapter for X`; the blanket impl provides `DynAgentAdapter` automatically.
75pub trait DynAgentAdapter: Send + Sync + 'static {
76    /// Dyn-compatible version of [`AgentAdapter::on_initialize_params`].
77    fn on_initialize_params<'a>(
78        &'a self,
79        params: InitializeParams,
80    ) -> Pin<Box<dyn Future<Output = Result<InitializeParams, AgentSdkError>> + Send + 'a>>;
81
82    /// Dyn-compatible version of [`AgentAdapter::on_initialize_result`].
83    fn on_initialize_result<'a>(
84        &'a self,
85        result: InitializeResult,
86    ) -> Pin<Box<dyn Future<Output = Result<InitializeResult, AgentSdkError>> + Send + 'a>>;
87
88    /// Dyn-compatible version of [`AgentAdapter::on_session_new_params`].
89    fn on_session_new_params<'a>(
90        &'a self,
91        params: SessionNewParams,
92    ) -> Pin<Box<dyn Future<Output = Result<SessionNewParams, AgentSdkError>> + Send + 'a>>;
93
94    /// Dyn-compatible version of [`AgentAdapter::on_session_new_result`].
95    fn on_session_new_result<'a>(
96        &'a self,
97        result: SessionNewResult,
98    ) -> Pin<Box<dyn Future<Output = Result<SessionNewResult, AgentSdkError>> + Send + 'a>>;
99
100    /// Dyn-compatible version of [`AgentAdapter::on_session_prompt_params`].
101    fn on_session_prompt_params<'a>(
102        &'a self,
103        params: SessionPromptParams,
104    ) -> Pin<Box<dyn Future<Output = Result<SessionPromptParams, AgentSdkError>> + Send + 'a>>;
105
106    /// Dyn-compatible version of [`AgentAdapter::on_session_update`].
107    fn on_session_update<'a>(
108        &'a self,
109        event: SessionUpdateEvent,
110    ) -> Pin<Box<dyn Future<Output = Result<SessionUpdateEvent, AgentSdkError>> + Send + 'a>>;
111
112    /// Dyn-compatible version of [`AgentAdapter::on_permission_request`].
113    fn on_permission_request<'a>(
114        &'a self,
115        request: PermissionRequest,
116    ) -> Pin<Box<dyn Future<Output = Result<PermissionRequest, AgentSdkError>> + Send + 'a>>;
117}
118
119impl<T: AgentAdapter> DynAgentAdapter for T {
120    fn on_initialize_params<'a>(
121        &'a self,
122        params: InitializeParams,
123    ) -> Pin<Box<dyn Future<Output = Result<InitializeParams, AgentSdkError>> + Send + 'a>> {
124        Box::pin(AgentAdapter::on_initialize_params(self, params))
125    }
126
127    fn on_initialize_result<'a>(
128        &'a self,
129        result: InitializeResult,
130    ) -> Pin<Box<dyn Future<Output = Result<InitializeResult, AgentSdkError>> + Send + 'a>> {
131        Box::pin(AgentAdapter::on_initialize_result(self, result))
132    }
133
134    fn on_session_new_params<'a>(
135        &'a self,
136        params: SessionNewParams,
137    ) -> Pin<Box<dyn Future<Output = Result<SessionNewParams, AgentSdkError>> + Send + 'a>> {
138        Box::pin(AgentAdapter::on_session_new_params(self, params))
139    }
140
141    fn on_session_new_result<'a>(
142        &'a self,
143        result: SessionNewResult,
144    ) -> Pin<Box<dyn Future<Output = Result<SessionNewResult, AgentSdkError>> + Send + 'a>> {
145        Box::pin(AgentAdapter::on_session_new_result(self, result))
146    }
147
148    fn on_session_prompt_params<'a>(
149        &'a self,
150        params: SessionPromptParams,
151    ) -> Pin<Box<dyn Future<Output = Result<SessionPromptParams, AgentSdkError>> + Send + 'a>> {
152        Box::pin(AgentAdapter::on_session_prompt_params(self, params))
153    }
154
155    fn on_session_update<'a>(
156        &'a self,
157        event: SessionUpdateEvent,
158    ) -> Pin<Box<dyn Future<Output = Result<SessionUpdateEvent, AgentSdkError>> + Send + 'a>> {
159        Box::pin(AgentAdapter::on_session_update(self, event))
160    }
161
162    fn on_permission_request<'a>(
163        &'a self,
164        request: PermissionRequest,
165    ) -> Pin<Box<dyn Future<Output = Result<PermissionRequest, AgentSdkError>> + Send + 'a>> {
166        Box::pin(AgentAdapter::on_permission_request(self, request))
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173    use anyclaw_sdk_types::PermissionOption;
174    use anyclaw_sdk_types::{
175        ClientCapabilities, ContentBlock, InitializeParams, InitializeResult, PermissionRequest,
176        SessionNewParams, SessionNewResult, SessionPromptParams, SessionUpdateEvent,
177        SessionUpdateType, TextContent,
178    };
179    use rstest::rstest;
180
181    struct DefaultAdapter;
182
183    impl AgentAdapter for DefaultAdapter {}
184
185    #[rstest]
186    #[tokio::test]
187    async fn when_default_adapter_on_initialize_params_then_passthrough() {
188        let adapter = DefaultAdapter;
189        let params = InitializeParams {
190            protocol_version: 1,
191            capabilities: ClientCapabilities { experimental: None },
192            options: None,
193            meta: None,
194        };
195        let output = AgentAdapter::on_initialize_params(&adapter, params.clone())
196            .await
197            .unwrap();
198        assert_eq!(output, params);
199    }
200
201    #[rstest]
202    #[tokio::test]
203    async fn when_default_adapter_on_initialize_result_then_passthrough() {
204        let adapter = DefaultAdapter;
205        let result = InitializeResult {
206            protocol_version: 1,
207            agent_capabilities: None,
208            defaults: None,
209            meta: None,
210        };
211        let output = AgentAdapter::on_initialize_result(&adapter, result.clone())
212            .await
213            .unwrap();
214        assert_eq!(output, result);
215    }
216
217    #[rstest]
218    #[tokio::test]
219    async fn when_default_adapter_on_session_new_params_then_passthrough() {
220        let adapter = DefaultAdapter;
221        let params = SessionNewParams {
222            session_id: None,
223            cwd: "/tmp".into(),
224            mcp_servers: vec![],
225            meta: None,
226        };
227        let output = AgentAdapter::on_session_new_params(&adapter, params.clone())
228            .await
229            .unwrap();
230        assert_eq!(output, params);
231    }
232
233    #[rstest]
234    #[tokio::test]
235    async fn when_default_adapter_on_session_new_result_then_passthrough() {
236        let adapter = DefaultAdapter;
237        let result = SessionNewResult {
238            session_id: "sess-1".into(),
239            meta: None,
240        };
241        let output = AgentAdapter::on_session_new_result(&adapter, result.clone())
242            .await
243            .unwrap();
244        assert_eq!(output, result);
245    }
246
247    #[rstest]
248    #[tokio::test]
249    async fn when_default_adapter_on_session_prompt_params_then_passthrough() {
250        let adapter = DefaultAdapter;
251        let params = SessionPromptParams {
252            session_id: "sess-1".into(),
253            prompt: vec![ContentBlock::Text(TextContent::new("hello"))],
254            meta: None,
255        };
256        let output = AgentAdapter::on_session_prompt_params(&adapter, params.clone())
257            .await
258            .unwrap();
259        assert_eq!(output, params);
260    }
261
262    #[rstest]
263    #[tokio::test]
264    async fn when_default_adapter_on_session_update_then_passthrough() {
265        let adapter = DefaultAdapter;
266        let event = SessionUpdateEvent {
267            session_id: "sess-1".into(),
268            update: SessionUpdateType::Result {
269                content: Some("done".into()),
270                is_error: false,
271            },
272        };
273        let output = AgentAdapter::on_session_update(&adapter, event.clone())
274            .await
275            .unwrap();
276        assert_eq!(output, event);
277    }
278
279    #[rstest]
280    #[tokio::test]
281    async fn when_default_adapter_on_permission_request_then_passthrough() {
282        let adapter = DefaultAdapter;
283        let request = PermissionRequest {
284            request_id: "perm-1".into(),
285            description: "Allow?".into(),
286            options: vec![PermissionOption {
287                option_id: "allow".into(),
288                label: "Allow".into(),
289            }],
290        };
291        let output = AgentAdapter::on_permission_request(&adapter, request.clone())
292            .await
293            .unwrap();
294        assert_eq!(output, request);
295    }
296
297    struct PermissionRewritingAdapter;
298
299    impl AgentAdapter for PermissionRewritingAdapter {
300        async fn on_permission_request(
301            &self,
302            mut request: PermissionRequest,
303        ) -> Result<PermissionRequest, AgentSdkError> {
304            request.description = format!("REWRITTEN: {}", request.description);
305            Ok(request)
306        }
307    }
308
309    #[rstest]
310    #[tokio::test]
311    async fn when_custom_adapter_overrides_permission_request_then_transformed() {
312        let adapter = PermissionRewritingAdapter;
313        let request = PermissionRequest {
314            request_id: "perm-1".into(),
315            description: "Allow?".into(),
316            options: vec![],
317        };
318        let output = AgentAdapter::on_permission_request(&adapter, request)
319            .await
320            .unwrap();
321        assert_eq!(output.description, "REWRITTEN: Allow?");
322        assert_eq!(output.request_id, "perm-1");
323    }
324
325    #[rstest]
326    #[tokio::test]
327    async fn when_custom_adapter_on_non_overridden_hook_then_passthrough() {
328        let adapter = PermissionRewritingAdapter;
329        let result = SessionNewResult {
330            session_id: "sess-2".into(),
331            meta: None,
332        };
333        let output = AgentAdapter::on_session_new_result(&adapter, result.clone())
334            .await
335            .unwrap();
336        assert_eq!(output, result);
337    }
338
339    #[rstest]
340    #[tokio::test]
341    async fn when_dyn_adapter_dispatches_typed_params_then_compiles() {
342        let adapter: Box<dyn DynAgentAdapter> = Box::new(DefaultAdapter);
343        let params = InitializeParams {
344            protocol_version: 1,
345            capabilities: ClientCapabilities { experimental: None },
346            options: None,
347            meta: None,
348        };
349        let output = adapter.on_initialize_params(params.clone()).await.unwrap();
350        assert_eq!(output, params);
351    }
352}