1use a2a_rs_core::{AgentCard, Message, SendMessageResponse, Task};
7use async_trait::async_trait;
8use std::sync::Arc;
9
10pub type HandlerResult<T> = Result<T, HandlerError>;
12
13#[derive(Debug, thiserror::Error)]
15pub enum HandlerError {
16 #[error("Processing failed: {message}")]
17 ProcessingFailed {
18 message: String,
19 #[source]
20 source: Option<Box<dyn std::error::Error + Send + Sync>>,
21 },
22 #[error("Backend unavailable: {message}")]
23 BackendUnavailable {
24 message: String,
25 #[source]
26 source: Option<Box<dyn std::error::Error + Send + Sync>>,
27 },
28 #[error("Authentication required: {0}")]
29 AuthRequired(String),
30 #[error("Invalid input: {0}")]
31 InvalidInput(String),
32 #[error("Internal error: {0}")]
33 Internal(#[from] anyhow::Error),
34}
35
36impl HandlerError {
37 pub fn processing_failed(msg: impl Into<String>) -> Self {
38 Self::ProcessingFailed {
39 message: msg.into(),
40 source: None,
41 }
42 }
43
44 pub fn processing_failed_with<E>(msg: impl Into<String>, source: E) -> Self
45 where
46 E: std::error::Error + Send + Sync + 'static,
47 {
48 Self::ProcessingFailed {
49 message: msg.into(),
50 source: Some(Box::new(source)),
51 }
52 }
53
54 pub fn backend_unavailable(msg: impl Into<String>) -> Self {
55 Self::BackendUnavailable {
56 message: msg.into(),
57 source: None,
58 }
59 }
60
61 pub fn backend_unavailable_with<E>(msg: impl Into<String>, source: E) -> Self
62 where
63 E: std::error::Error + Send + Sync + 'static,
64 {
65 Self::BackendUnavailable {
66 message: msg.into(),
67 source: Some(Box::new(source)),
68 }
69 }
70}
71
72#[derive(Clone, Debug)]
74pub struct AuthContext {
75 pub user_id: String,
76 pub access_token: String,
77 pub metadata: Option<serde_json::Value>,
78}
79
80#[async_trait]
84pub trait MessageHandler: Send + Sync {
85 async fn handle_message(
87 &self,
88 message: Message,
89 auth: Option<AuthContext>,
90 ) -> HandlerResult<SendMessageResponse>;
91
92 fn agent_card(&self, base_url: &str) -> AgentCard;
94
95 async fn cancel_task(&self, _task_id: &str) -> HandlerResult<()> {
97 Ok(())
98 }
99
100 fn supports_streaming(&self) -> bool {
102 false
103 }
104
105 async fn extended_agent_card(
107 &self,
108 _base_url: &str,
109 _auth: &AuthContext,
110 ) -> Option<AgentCard> {
111 None
112 }
113}
114
115pub struct EchoHandler {
117 pub prefix: String,
118 pub agent_name: String,
119}
120
121impl Default for EchoHandler {
122 fn default() -> Self {
123 Self {
124 prefix: "echo:".to_string(),
125 agent_name: "Echo Agent".to_string(),
126 }
127 }
128}
129
130#[async_trait]
131impl MessageHandler for EchoHandler {
132 async fn handle_message(
133 &self,
134 message: Message,
135 _auth: Option<AuthContext>,
136 ) -> HandlerResult<SendMessageResponse> {
137 use a2a_rs_core::{now_iso8601, Part, Role, TaskState, TaskStatus};
138 use uuid::Uuid;
139
140 let text = message
141 .parts
142 .iter()
143 .filter_map(|p| p.text.as_deref())
144 .collect::<Vec<_>>()
145 .join("\n");
146
147 let task_id = Uuid::new_v4().to_string();
148 let context_id = message.context_id.clone().unwrap_or_default();
149
150 let response = Message {
151 message_id: Uuid::new_v4().to_string(),
152 context_id: message.context_id.clone(),
153 task_id: None,
154 role: Role::Agent,
155 parts: vec![Part::text(format!("{} {}", self.prefix, text))],
156 metadata: None,
157 extensions: vec![],
158 reference_task_ids: None,
159 };
160
161 Ok(SendMessageResponse::Task(Task {
162 id: task_id,
163 context_id,
164 status: TaskStatus {
165 state: TaskState::Completed,
166 message: None,
167 timestamp: Some(now_iso8601()),
168 },
169 history: Some(vec![message, response]),
170 artifacts: None,
171 metadata: None,
172 }))
173 }
174
175 fn agent_card(&self, base_url: &str) -> AgentCard {
176 use a2a_rs_core::{AgentCapabilities, AgentInterface, AgentProvider, AgentSkill, PROTOCOL_VERSION};
177
178 AgentCard {
179 name: self.agent_name.clone(),
180 description: "Simple echo agent for testing A2A protocol".to_string(),
181 supported_interfaces: vec![AgentInterface {
182 url: format!("{}/v1/rpc", base_url),
183 protocol_binding: "JSONRPC".to_string(),
184 protocol_version: PROTOCOL_VERSION.to_string(),
185 tenant: None,
186 }],
187 provider: Some(AgentProvider {
188 organization: "A2A Demo".to_string(),
189 url: "https://github.com/a2a-protocol".to_string(),
190 }),
191 version: PROTOCOL_VERSION.to_string(),
192 documentation_url: None,
193 capabilities: AgentCapabilities::default(),
194 security_schemes: Default::default(),
195 security_requirements: vec![],
196 default_input_modes: vec!["text/plain".to_string()],
197 default_output_modes: vec!["text/plain".to_string()],
198 skills: vec![AgentSkill {
199 id: "echo".to_string(),
200 name: "Echo".to_string(),
201 description: "Echoes back the user's message".to_string(),
202 tags: vec!["demo".to_string(), "echo".to_string()],
203 examples: vec![],
204 input_modes: vec![],
205 output_modes: vec![],
206 security_requirements: vec![],
207 }],
208 signatures: vec![],
209 icon_url: None,
210 }
211 }
212}
213
214pub type BoxedHandler = Arc<dyn MessageHandler>;