1use crate::core::config::{AgentConfig, ConfigError, StorageConfig};
7use crate::core::server::AgentServer;
8use a2a_rs::domain::{A2AError, ContextId, Task, TaskId, TaskPushNotificationConfig, TaskState};
9use a2a_rs::port::{
10 AsyncMessageHandler, AsyncNotificationManager, AsyncStreamingHandler, AsyncTaskLifecycle,
11 AsyncTaskQuery,
12};
13use a2a_rs::{HttpPushNotificationSender, InMemoryTaskStorage};
14use async_trait::async_trait;
15use std::path::Path;
16use std::sync::Arc;
17
18#[cfg(feature = "sqlx")]
19use a2a_rs::adapter::storage::SqlxTaskStorage;
20
21#[derive(Clone)]
24pub enum AutoStorage {
25 InMemory(InMemoryTaskStorage),
26 #[cfg(feature = "sqlx")]
27 Sqlx(SqlxTaskStorage),
28}
29
30#[async_trait]
31impl AsyncTaskLifecycle for AutoStorage {
32 async fn create(&self, id: &TaskId, context_id: &ContextId) -> Result<Task, A2AError> {
33 match self {
34 AutoStorage::InMemory(s) => s.create(id, context_id).await,
35 #[cfg(feature = "sqlx")]
36 AutoStorage::Sqlx(s) => s.create(id, context_id).await,
37 }
38 }
39
40 async fn get(&self, id: &TaskId, history_length: Option<u32>) -> Result<Task, A2AError> {
41 match self {
42 AutoStorage::InMemory(s) => s.get(id, history_length).await,
43 #[cfg(feature = "sqlx")]
44 AutoStorage::Sqlx(s) => s.get(id, history_length).await,
45 }
46 }
47
48 async fn update_status(
49 &self,
50 id: &TaskId,
51 state: TaskState,
52 message: Option<a2a_rs::domain::Message>,
53 ) -> Result<Task, A2AError> {
54 match self {
55 AutoStorage::InMemory(s) => s.update_status(id, state, message).await,
56 #[cfg(feature = "sqlx")]
57 AutoStorage::Sqlx(s) => s.update_status(id, state, message).await,
58 }
59 }
60
61 async fn cancel(&self, id: &TaskId) -> Result<Task, A2AError> {
62 match self {
63 AutoStorage::InMemory(s) => s.cancel(id).await,
64 #[cfg(feature = "sqlx")]
65 AutoStorage::Sqlx(s) => s.cancel(id).await,
66 }
67 }
68
69 async fn exists(&self, id: &TaskId) -> Result<bool, A2AError> {
70 match self {
71 AutoStorage::InMemory(s) => s.exists(id).await,
72 #[cfg(feature = "sqlx")]
73 AutoStorage::Sqlx(s) => s.exists(id).await,
74 }
75 }
76}
77
78#[async_trait]
79impl AsyncTaskQuery for AutoStorage {
80 async fn list(
81 &self,
82 params: &a2a_rs::domain::ListTasksParams,
83 ) -> Result<a2a_rs::domain::ListTasksResult, A2AError> {
84 match self {
85 AutoStorage::InMemory(s) => s.list(params).await,
86 #[cfg(feature = "sqlx")]
87 AutoStorage::Sqlx(s) => s.list(params).await,
88 }
89 }
90}
91
92#[async_trait]
93impl AsyncNotificationManager for AutoStorage {
94 async fn set_config(
95 &self,
96 config: &TaskPushNotificationConfig,
97 ) -> Result<TaskPushNotificationConfig, A2AError> {
98 match self {
99 AutoStorage::InMemory(s) => s.set_config(config).await,
100 #[cfg(feature = "sqlx")]
101 AutoStorage::Sqlx(s) => s.set_config(config).await,
102 }
103 }
104
105 async fn get_config(
106 &self,
107 params: &a2a_rs::domain::GetTaskPushNotificationConfigParams,
108 ) -> Result<TaskPushNotificationConfig, A2AError> {
109 match self {
110 AutoStorage::InMemory(s) => s.get_config(params).await,
111 #[cfg(feature = "sqlx")]
112 AutoStorage::Sqlx(s) => s.get_config(params).await,
113 }
114 }
115
116 async fn list_configs(
117 &self,
118 params: &a2a_rs::domain::ListTaskPushNotificationConfigsParams,
119 ) -> Result<Vec<TaskPushNotificationConfig>, A2AError> {
120 match self {
121 AutoStorage::InMemory(s) => s.list_configs(params).await,
122 #[cfg(feature = "sqlx")]
123 AutoStorage::Sqlx(s) => s.list_configs(params).await,
124 }
125 }
126
127 async fn delete_config(
128 &self,
129 params: &a2a_rs::domain::DeleteTaskPushNotificationConfigParams,
130 ) -> Result<(), A2AError> {
131 match self {
132 AutoStorage::InMemory(s) => s.delete_config(params).await,
133 #[cfg(feature = "sqlx")]
134 AutoStorage::Sqlx(s) => s.delete_config(params).await,
135 }
136 }
137}
138
139impl AutoStorage {
140 pub async fn from_config(config: &StorageConfig) -> Result<Self, BuildError> {
142 match config {
143 StorageConfig::InMemory => {
144 let push_sender = a2a_rs::adapter::HttpPushNotificationSender::new()
145 .with_timeout(30)
146 .with_max_retries(3);
147 let storage = a2a_rs::InMemoryTaskStorage::with_push_sender(push_sender);
148 Ok(AutoStorage::InMemory(storage))
149 }
150 #[cfg(feature = "sqlx")]
151 StorageConfig::Sqlx {
152 url,
153 enable_logging,
154 ..
155 } => {
156 if *enable_logging {
157 tracing::info!("SQL query logging enabled");
158 }
159
160 let storage = a2a_rs::adapter::storage::SqlxTaskStorage::new(url)
161 .await
162 .map_err(|e| {
163 BuildError::StorageError(format!("Failed to create SQLx storage: {}", e))
164 })?;
165
166 Ok(AutoStorage::Sqlx(storage))
167 }
168 #[cfg(not(feature = "sqlx"))]
169 StorageConfig::Sqlx { .. } => Err(BuildError::StorageError(
170 "SQLx storage requested but 'sqlx' feature is not enabled".to_string(),
171 )),
172 }
173 }
174
175 pub fn push_notifier(&self) -> Arc<dyn a2a_rs::port::AsyncPushNotifier> {
177 match self {
178 AutoStorage::InMemory(s) => s.push_notifier(),
179 #[cfg(feature = "sqlx")]
180 AutoStorage::Sqlx(s) => s.push_notifier(),
181 }
182 }
183}
184
185pub struct AgentBuilder<H = (), S = ()> {
187 config: AgentConfig,
188 handler: Option<H>,
189 storage: Option<S>,
190 streaming: Option<Arc<dyn AsyncStreamingHandler>>,
191}
192
193impl AgentBuilder<(), ()> {
194 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, ConfigError> {
196 let config = AgentConfig::from_file(path)?;
197 Ok(Self {
198 config,
199 handler: None,
200 storage: None,
201 streaming: None,
202 })
203 }
204
205 pub fn from_toml(toml: &str) -> Result<Self, ConfigError> {
207 let config = AgentConfig::from_toml(toml)?;
208 Ok(Self {
209 config,
210 handler: None,
211 storage: None,
212 streaming: None,
213 })
214 }
215
216 pub fn new(config: AgentConfig) -> Self {
218 Self {
219 config,
220 handler: None,
221 storage: None,
222 streaming: None,
223 }
224 }
225}
226
227impl<H, S> AgentBuilder<H, S> {
228 pub fn with_handler<NewH>(self, handler: NewH) -> AgentBuilder<NewH, S>
230 where
231 NewH: AsyncMessageHandler + Clone + Send + Sync + 'static,
232 {
233 AgentBuilder {
234 config: self.config,
235 handler: Some(handler),
236 storage: self.storage,
237 streaming: self.streaming,
238 }
239 }
240
241 pub fn with_storage<NewS>(self, storage: NewS) -> AgentBuilder<H, NewS>
243 where
244 NewS: AsyncTaskLifecycle
245 + AsyncTaskQuery
246 + AsyncNotificationManager
247 + Clone
248 + Send
249 + Sync
250 + 'static,
251 {
252 AgentBuilder {
253 config: self.config,
254 handler: self.handler,
255 storage: Some(storage),
256 streaming: self.streaming,
257 }
258 }
259
260 pub fn with_streaming(mut self, streaming: impl AsyncStreamingHandler + 'static) -> Self {
269 self.streaming = Some(Arc::new(streaming));
270 self
271 }
272
273 pub fn config(&self) -> &AgentConfig {
275 &self.config
276 }
277
278 pub async fn agent_card(&self) -> Result<a2a_rs::domain::AgentCard, a2a_rs::domain::A2AError> {
283 use a2a_rs::services::AgentInfoProvider;
284 crate::core::server::agent_info_from_config(&self.config, self.config.agent_url())
285 .get_agent_card()
286 .await
287 }
288
289 pub fn with_config<F>(mut self, f: F) -> Self
291 where
292 F: FnOnce(&mut AgentConfig),
293 {
294 f(&mut self.config);
295 self
296 }
297}
298
299impl<H, S> AgentBuilder<H, S>
300where
301 H: AsyncMessageHandler + Clone + Send + Sync + 'static,
302 S: AsyncTaskLifecycle
303 + AsyncTaskQuery
304 + AsyncNotificationManager
305 + Clone
306 + Send
307 + Sync
308 + 'static,
309{
310 pub fn build(self) -> Result<AgentServer<H, S>, BuildError> {
312 let handler = self.handler.ok_or(BuildError::MissingHandler)?;
313 let storage = self.storage.ok_or(BuildError::MissingStorage)?;
314
315 let mut runtime = AgentServer::new(self.config, Arc::new(handler), Arc::new(storage));
316 if let Some(streaming) = self.streaming {
317 runtime = runtime.with_streaming(streaming);
318 }
319 Ok(runtime)
320 }
321}
322
323impl<H> AgentBuilder<H, ()>
324where
325 H: AsyncMessageHandler + Clone + Send + Sync + 'static,
326{
327 pub async fn build_with_auto_storage(self) -> Result<AgentServer<H, AutoStorage>, BuildError> {
331 let handler = self.handler.ok_or(BuildError::MissingHandler)?;
332 let streaming = self.streaming;
333
334 let storage = match &self.config.server.storage {
335 StorageConfig::InMemory => {
336 let push_sender = HttpPushNotificationSender::new()
337 .with_timeout(30)
338 .with_max_retries(3);
339 let storage = InMemoryTaskStorage::with_push_sender(push_sender);
340 AutoStorage::InMemory(storage)
341 }
342 #[cfg(feature = "sqlx")]
343 StorageConfig::Sqlx {
344 url,
345 enable_logging,
346 ..
347 } => {
348 if *enable_logging {
349 tracing::info!("SQL query logging enabled");
350 }
351
352 let storage = SqlxTaskStorage::new(url).await.map_err(|e| {
353 BuildError::StorageError(format!("Failed to create SQLx storage: {}", e))
354 })?;
355
356 AutoStorage::Sqlx(storage)
357 }
358 #[cfg(not(feature = "sqlx"))]
359 StorageConfig::Sqlx { .. } => {
360 return Err(BuildError::StorageError(
361 "SQLx storage requested but 'sqlx' feature is not enabled".to_string(),
362 ));
363 }
364 };
365
366 let mut runtime = AgentServer::new(self.config, Arc::new(handler), Arc::new(storage));
367 if let Some(streaming) = streaming {
368 runtime = runtime.with_streaming(streaming);
369 }
370 Ok(runtime)
371 }
372
373 #[cfg(feature = "sqlx")]
376 pub async fn build_with_auto_storage_and_migrations(
377 self,
378 migrations: &'static [&'static str],
379 ) -> Result<AgentServer<H, AutoStorage>, BuildError> {
380 let handler = self.handler.ok_or(BuildError::MissingHandler)?;
381 let streaming = self.streaming;
382
383 let storage = match &self.config.server.storage {
384 StorageConfig::InMemory => {
385 tracing::warn!(
386 "Migrations provided but using in-memory storage - migrations ignored"
387 );
388 let push_sender = HttpPushNotificationSender::new()
389 .with_timeout(30)
390 .with_max_retries(3);
391 let storage = InMemoryTaskStorage::with_push_sender(push_sender);
392 AutoStorage::InMemory(storage)
393 }
394 StorageConfig::Sqlx {
395 url,
396 enable_logging,
397 ..
398 } => {
399 if *enable_logging {
400 tracing::info!("SQL query logging enabled");
401 }
402
403 let storage = SqlxTaskStorage::with_migrations(url, migrations)
404 .await
405 .map_err(|e| {
406 BuildError::StorageError(format!("Failed to create SQLx storage: {}", e))
407 })?;
408
409 AutoStorage::Sqlx(storage)
410 }
411 };
412
413 let mut runtime = AgentServer::new(self.config, Arc::new(handler), Arc::new(storage));
414 if let Some(streaming) = streaming {
415 runtime = runtime.with_streaming(streaming);
416 }
417 Ok(runtime)
418 }
419}
420
421#[derive(Debug, thiserror::Error)]
423pub enum BuildError {
424 #[error("Handler must be set before building")]
425 MissingHandler,
426
427 #[error("Storage must be set before building")]
428 MissingStorage,
429
430 #[error("Configuration error: {0}")]
431 ConfigError(#[from] ConfigError),
432
433 #[error("Storage error: {0}")]
434 StorageError(String),
435
436 #[error("Runtime error: {0}")]
437 RuntimeError(String),
438}
439
440#[cfg(test)]
441mod tests {
442 use super::*;
443
444 #[test]
445 fn test_builder_from_toml() {
446 let toml = r#"
447 [agent]
448 name = "Test Agent"
449
450 [server]
451 http_port = 9000
452 "#;
453
454 let builder = AgentBuilder::from_toml(toml).unwrap();
455 assert_eq!(builder.config().agent.name, "Test Agent");
456 assert_eq!(builder.config().server.http_port, 9000);
457 }
458
459 #[test]
460 fn test_builder_config_modification() {
461 let toml = r#"
462 [agent]
463 name = "Test Agent"
464 "#;
465
466 let builder = AgentBuilder::from_toml(toml)
467 .unwrap()
468 .with_config(|config| {
469 config.server.http_port = 7000;
470 });
471
472 assert_eq!(builder.config().server.http_port, 7000);
473 }
474}