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(&self, _base_url: &str, _auth: &AuthContext) -> Option<AgentCard> {
107 None
108 }
109}
110
111pub struct EchoHandler {
113 pub prefix: String,
114 pub agent_name: String,
115}
116
117impl Default for EchoHandler {
118 fn default() -> Self {
119 Self {
120 prefix: "echo:".to_string(),
121 agent_name: "Echo Agent".to_string(),
122 }
123 }
124}
125
126#[async_trait]
127impl MessageHandler for EchoHandler {
128 async fn handle_message(
129 &self,
130 message: Message,
131 _auth: Option<AuthContext>,
132 ) -> HandlerResult<SendMessageResponse> {
133 use a2a_rs_core::{now_iso8601, Part, Role, TaskState, TaskStatus};
134 use uuid::Uuid;
135
136 let text = message
137 .parts
138 .iter()
139 .filter_map(|p| p.as_text())
140 .collect::<Vec<_>>()
141 .join("\n");
142
143 let task_id = Uuid::new_v4().to_string();
144 let context_id = message.context_id.clone().unwrap_or_default();
145
146 let response = Message {
147 kind: "message".to_string(),
148 message_id: Uuid::new_v4().to_string(),
149 context_id: message.context_id.clone(),
150 task_id: None,
151 role: Role::Agent,
152 parts: vec![Part::text(format!("{} {}", self.prefix, text))],
153 metadata: None,
154 extensions: vec![],
155 reference_task_ids: None,
156 };
157
158 Ok(SendMessageResponse::Task(Task {
159 kind: "task".to_string(),
160 id: task_id,
161 context_id,
162 status: TaskStatus {
163 state: TaskState::Completed,
164 message: None,
165 timestamp: Some(now_iso8601()),
166 },
167 history: Some(vec![message, response]),
168 artifacts: None,
169 metadata: None,
170 }))
171 }
172
173 fn agent_card(&self, base_url: &str) -> AgentCard {
174 use a2a_rs_core::{
175 AgentCapabilities, AgentInterface, AgentProvider, AgentSkill, PROTOCOL_VERSION,
176 };
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>;