Skip to main content

minimal_agent/
minimal_agent.rs

1//! Minimal agent example using the new declarative API
2//!
3//! This example shows how to create a simple agent with just ~30 lines of code.
4//! The agent echoes back any message it receives.
5
6use a2a_agents::core::{AgentBuilder, BuildError};
7use a2a_rs::{
8    InMemoryTaskStorage,
9    domain::{A2AError, Message, Part, Role, Task, TaskState},
10    port::AsyncMessageHandler,
11};
12use async_trait::async_trait;
13use uuid::Uuid;
14
15/// Simple echo handler that echoes back messages
16#[derive(Clone)]
17struct EchoHandler;
18
19#[async_trait]
20impl AsyncMessageHandler for EchoHandler {
21    async fn process_message(
22        &self,
23        task_id: &str,
24        message: &Message,
25        _session_id: Option<&str>,
26    ) -> Result<Task, A2AError> {
27        // Extract text from the message
28        let text = message
29            .parts
30            .iter()
31            .find_map(|part| match part {
32                Part::Text { text, .. } => Some(text.clone()),
33                _ => None,
34            })
35            .unwrap_or_else(|| "No text provided".to_string());
36
37        // Create echo response
38        let response = Message::builder()
39            .role(Role::Agent)
40            .parts(vec![Part::text(format!("Echo: {}", text))])
41            .message_id(Uuid::new_v4().to_string())
42            .context_id(message.context_id.clone().unwrap_or_default())
43            .build();
44
45        // Return completed task with echo response
46        Ok(Task::builder()
47            .id(task_id.to_string())
48            .context_id(message.context_id.clone().unwrap_or_default())
49            .status(a2a_rs::domain::TaskStatus {
50                state: TaskState::Completed,
51                message: Some(response.clone()),
52                timestamp: Some(chrono::Utc::now()),
53            })
54            .history(vec![message.clone(), response])
55            .build())
56    }
57
58    async fn validate_message(&self, message: &Message) -> Result<(), A2AError> {
59        if message.parts.is_empty() {
60            return Err(A2AError::ValidationError {
61                field: "parts".to_string(),
62                message: "Message must contain at least one part".to_string(),
63            });
64        }
65        Ok(())
66    }
67}
68
69#[tokio::main]
70async fn main() -> Result<(), BuildError> {
71    // Initialize logging
72    tracing_subscriber::fmt()
73        .with_env_filter(
74            tracing_subscriber::EnvFilter::from_default_env()
75                .add_directive(tracing::Level::INFO.into()),
76        )
77        .init();
78
79    println!("🚀 Starting Echo Agent with declarative configuration");
80    println!();
81
82    // Build and run the agent from configuration file
83    // This is all you need - the rest is handled by the framework!
84    AgentBuilder::from_file("examples/minimal_agent.toml")?
85        .with_handler(EchoHandler)
86        .with_storage(InMemoryTaskStorage::new())
87        .build()?
88        .run()
89        .await
90        .map_err(|e| BuildError::RuntimeError(e.to_string()))?;
91
92    Ok(())
93}