1use crate::core::config::{AgentConfig, AuthConfig, StorageConfig};
9use a2a_rs::adapter::{BearerTokenAuthenticator, ConnectRpcAdapter, HttpServer, SimpleAgentInfo};
10use a2a_rs::port::{
11 AsyncMessageHandler, AsyncNotificationManager, AsyncStreamingHandler, AsyncTaskLifecycle,
12 AsyncTaskQuery,
13};
14use std::sync::Arc;
15use tracing::{info, warn};
16
17#[cfg(feature = "auth")]
18use a2a_rs::adapter::{JwtAuthenticator, OAuth2Authenticator};
19#[cfg(feature = "auth")]
20use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
21#[cfg(feature = "auth")]
22use std::collections::HashMap;
23
24pub struct AgentServer<H, S> {
26 config: AgentConfig,
27 handler: Arc<H>,
28 storage: Arc<S>,
29 streaming: Option<Arc<dyn AsyncStreamingHandler>>,
34}
35
36pub(crate) fn agent_info_from_config(config: &AgentConfig, base_url: String) -> SimpleAgentInfo {
41 let mut agent_info = SimpleAgentInfo::new(config.agent.name.clone(), base_url)
48 .with_preferred_transport(a2a_rs::domain::PROTOCOL_BINDING_CONNECTRPC.to_string());
49
50 if let Some(ref description) = config.agent.description {
51 agent_info = agent_info.with_description(description.clone());
52 }
53
54 if let Some(ref provider) = config.agent.provider {
55 agent_info = agent_info.with_provider(provider.name.clone(), provider.url.clone());
56 }
57
58 if let Some(ref doc_url) = config.agent.documentation_url {
59 agent_info = agent_info.with_documentation_url(doc_url.clone());
60 }
61
62 if let Some(ref version) = config.agent.version {
63 agent_info = agent_info.with_version(version.clone());
64 }
65
66 let mut schemes = std::collections::HashMap::new();
68 match &config.server.auth {
69 crate::core::config::AuthConfig::None => {}
70 crate::core::config::AuthConfig::Bearer { format, .. } => {
71 schemes.insert(
72 "bearer".to_string(),
73 a2a_rs::domain::SecurityScheme::http(
74 "bearer".to_string(),
75 format.clone(),
76 Some("Bearer token authentication".to_string()),
77 ),
78 );
79 }
80 crate::core::config::AuthConfig::ApiKey { location, name, .. } => {
81 schemes.insert(
82 "api_key".to_string(),
83 a2a_rs::domain::SecurityScheme::api_key(
84 name.clone(),
85 location.clone(),
86 Some("API Key authentication".to_string()),
87 ),
88 );
89 }
90 crate::core::config::AuthConfig::Jwt {
91 issuer, audience, ..
92 } => {
93 schemes.insert(
94 "jwt".to_string(),
95 a2a_rs::domain::SecurityScheme::http(
96 "bearer".to_string(),
97 Some("JWT".to_string()),
98 Some(format!(
99 "JWT authentication (issuer: {:?}, audience: {:?})",
100 issuer, audience
101 )),
102 ),
103 );
104 }
105 crate::core::config::AuthConfig::OAuth2 {
106 flow,
107 token_url,
108 authorization_url,
109 scopes,
110 ..
111 } => {
112 let scopes_map: std::collections::HashMap<String, String> =
113 scopes.iter().map(|s| (s.clone(), s.clone())).collect();
114 let flows = if flow == "client_credentials" {
115 a2a_rs::domain::OAuthFlows::client_credentials(
116 a2a_rs::domain::ClientCredentialsOAuthFlow {
117 token_url: token_url.clone(),
118 refresh_url: String::new(),
119 scopes: scopes_map,
120 ..Default::default()
121 },
122 )
123 } else {
124 a2a_rs::domain::OAuthFlows::authorization_code(
125 a2a_rs::domain::AuthorizationCodeOAuthFlow {
126 authorization_url: authorization_url.clone(),
127 token_url: token_url.clone(),
128 refresh_url: String::new(),
129 scopes: scopes_map,
130 ..Default::default()
131 },
132 )
133 };
134 schemes.insert(
135 "oauth2".to_string(),
136 a2a_rs::domain::SecurityScheme::oauth2(
137 flows,
138 Some("OAuth2 authentication".to_string()),
139 None,
140 ),
141 );
142 }
143 }
144 if !schemes.is_empty() {
145 agent_info = agent_info.with_security_schemes(schemes);
146 }
147
148 if config.features.streaming {
150 agent_info = agent_info.with_streaming();
151 }
152
153 if config.features.push_notifications {
154 agent_info = agent_info.with_push_notifications();
155 }
156
157 if config.features.state_history {
158 agent_info = agent_info.with_state_transition_history();
159 }
160
161 if config.features.authenticated_card {
162 agent_info = agent_info.with_authenticated_extended_card();
163 }
164
165 if let Some(ref ap2_config) = config.features.extensions.ap2 {
167 let roles_json: Vec<serde_json::Value> = ap2_config
168 .roles
169 .iter()
170 .map(|r| serde_json::Value::String(r.clone()))
171 .collect();
172
173 let mut params = std::collections::HashMap::new();
174 params.insert("roles".to_string(), serde_json::Value::Array(roles_json));
175 let params_val = serde_json::Value::Object(params.into_iter().collect());
176 let params_struct: buffa_types::google::protobuf::Struct =
177 serde_json::from_value(params_val).unwrap_or_default();
178
179 let ext = a2a_rs::domain::AgentExtension {
180 uri: "https://github.com/google-agentic-commerce/ap2/tree/v0.1".to_string(),
181 description: "Agent Payments Protocol (AP2) v0.1".to_string(),
182 required: ap2_config.required,
183 params: buffa::MessageField::some(params_struct),
184 ..Default::default()
185 };
186
187 agent_info = agent_info.add_extension(ext);
188 info!("💳 AP2 extension enabled (roles: {:?})", ap2_config.roles);
189 }
190
191 for skill in &config.skills {
193 agent_info = agent_info.add_comprehensive_skill(
194 skill.id.clone(),
195 skill.name.clone(),
196 skill.description.clone(),
197 if skill.keywords.is_empty() {
198 None
199 } else {
200 Some(skill.keywords.clone())
201 },
202 if skill.examples.is_empty() {
203 None
204 } else {
205 Some(skill.examples.clone())
206 },
207 Some(skill.input_formats.clone()),
208 Some(skill.output_formats.clone()),
209 );
210 }
211
212 agent_info
213}
214
215impl<H, S> AgentServer<H, S>
216where
217 H: AsyncMessageHandler + Clone + Send + Sync + 'static,
218 S: AsyncTaskLifecycle
219 + AsyncTaskQuery
220 + AsyncNotificationManager
221 + Clone
222 + Send
223 + Sync
224 + 'static,
225{
226 pub fn new(config: AgentConfig, handler: Arc<H>, storage: Arc<S>) -> Self {
228 Self {
229 config,
230 handler,
231 storage,
232 streaming: None,
233 }
234 }
235
236 pub fn with_streaming(mut self, streaming: Arc<dyn AsyncStreamingHandler>) -> Self {
243 self.streaming = Some(streaming);
244 self
245 }
246
247 fn build_agent_info(&self, base_url: String) -> SimpleAgentInfo {
249 agent_info_from_config(&self.config, base_url)
250 }
251
252 pub async fn start_http(&self) -> Result<(), ServerError> {
254 if self.config.server.http_port == 0 {
255 return Err(ServerError::ServerNotConfigured(
256 "HTTP port is 0".to_string(),
257 ));
258 }
259
260 let base_url = format!(
261 "http://{}:{}",
262 self.config.server.host, self.config.server.http_port
263 );
264 let agent_info = self.build_agent_info(base_url);
265
266 let mut processor = ConnectRpcAdapter::new(
267 (*self.handler).clone(),
268 (*self.storage).clone(),
269 (*self.storage).clone(),
270 agent_info.clone(),
271 );
272
273 if let Some(streaming) = &self.streaming {
277 processor = processor.with_streaming_handler(streaming.clone());
278 info!("📡 Streaming backend wired into transport (SSE subscribers live)");
279 }
280
281 let bind_address = format!(
282 "{}:{}",
283 self.config.server.host, self.config.server.http_port
284 );
285
286 info!("🌐 Starting HTTP server on {}", bind_address);
287 self.print_agent_info("HTTP", &self.config.server.http_port.to_string());
288
289 match &self.config.server.auth {
290 AuthConfig::None => {
291 let server = HttpServer::new(processor, agent_info, bind_address);
292 server
293 .start()
294 .await
295 .map_err(|e| ServerError::ServerError(e.to_string()))
296 }
297 AuthConfig::Bearer { tokens, format } => {
298 info!(
299 "🔐 Authentication: Bearer token ({} token(s){})",
300 tokens.len(),
301 format
302 .as_ref()
303 .map(|f| format!(", format: {}", f))
304 .unwrap_or_default()
305 );
306 let authenticator = BearerTokenAuthenticator::new(tokens.clone());
307 let server =
308 HttpServer::with_auth(processor, agent_info, bind_address, authenticator);
309 server
310 .start()
311 .await
312 .map_err(|e| ServerError::ServerError(e.to_string()))
313 }
314 AuthConfig::ApiKey {
315 keys,
316 location,
317 name,
318 } => {
319 warn!(
320 "🔐 API key authentication configured ({} {}, {} key(s)) but not yet supported, using no auth",
321 location,
322 name,
323 keys.len()
324 );
325 let server = HttpServer::new(processor, agent_info, bind_address);
326 server
327 .start()
328 .await
329 .map_err(|e| ServerError::ServerError(e.to_string()))
330 }
331 #[cfg(feature = "auth")]
332 AuthConfig::Jwt {
333 secret,
334 rsa_pem_path,
335 algorithm,
336 issuer,
337 audience,
338 } => {
339 info!("🔐 Authentication: JWT (algorithm: {})", algorithm);
340
341 let mut authenticator = if let Some(secret) = secret {
342 JwtAuthenticator::new_with_secret(secret.as_bytes())
343 } else if let Some(pem_path) = rsa_pem_path {
344 let pem_data = std::fs::read(pem_path).map_err(|e| {
345 ServerError::ServerError(format!("Failed to read RSA PEM file: {}", e))
346 })?;
347 JwtAuthenticator::new_with_rsa_pem(&pem_data).map_err(|e| {
348 ServerError::ServerError(format!(
349 "Failed to create JWT authenticator: {}",
350 e
351 ))
352 })?
353 } else {
354 return Err(ServerError::ServerError(
355 "JWT authentication requires either 'secret' or 'rsa_pem_path'".to_string(),
356 ));
357 };
358
359 if let Some(iss) = issuer {
360 authenticator = authenticator.with_issuer(iss.clone());
361 info!(" Issuer: {}", iss);
362 }
363 if let Some(aud) = audience {
364 authenticator = authenticator.with_audience(aud.clone());
365 info!(" Audience: {}", aud);
366 }
367
368 let server =
369 HttpServer::with_auth(processor, agent_info, bind_address, authenticator);
370 server
371 .start()
372 .await
373 .map_err(|e| ServerError::ServerError(e.to_string()))
374 }
375 #[cfg(not(feature = "auth"))]
376 AuthConfig::Jwt { .. } => Err(ServerError::ServerError(
377 "JWT authentication requires the 'auth' feature to be enabled".to_string(),
378 )),
379 #[cfg(feature = "auth")]
380 AuthConfig::OAuth2 {
381 client_id,
382 client_secret,
383 authorization_url,
384 token_url,
385 redirect_url,
386 flow,
387 scopes,
388 } => {
389 info!("🔐 Authentication: OAuth2 (flow: {})", flow);
390 info!(" Authorization URL: {}", authorization_url);
391 info!(" Token URL: {}", token_url);
392
393 let client_id = ClientId::new(client_id.clone());
394 let client_secret = ClientSecret::new(client_secret.clone());
395 let auth_url = AuthUrl::new(authorization_url.clone()).map_err(|e| {
396 ServerError::ServerError(format!("Invalid authorization URL: {}", e))
397 })?;
398 let token_url = TokenUrl::new(token_url.clone())
399 .map_err(|e| ServerError::ServerError(format!("Invalid token URL: {}", e)))?;
400
401 let scopes_map: HashMap<String, String> =
402 scopes.iter().map(|s| (s.clone(), s.clone())).collect();
403
404 let authenticator = if flow == "client_credentials" {
405 OAuth2Authenticator::new_client_credentials(
406 client_id,
407 client_secret,
408 token_url,
409 scopes_map,
410 )
411 } else {
412 let redirect_url = RedirectUrl::new(
414 redirect_url
415 .clone()
416 .unwrap_or_else(|| "http://localhost:8080/callback".to_string()),
417 )
418 .map_err(|e| {
419 ServerError::ServerError(format!("Invalid redirect URL: {}", e))
420 })?;
421
422 info!(" Redirect URL: {}", redirect_url.as_str());
423
424 OAuth2Authenticator::new_authorization_code(
425 client_id,
426 Some(client_secret),
427 auth_url,
428 token_url,
429 redirect_url,
430 scopes_map,
431 )
432 };
433
434 let server =
435 HttpServer::with_auth(processor, agent_info, bind_address, authenticator);
436 server
437 .start()
438 .await
439 .map_err(|e| ServerError::ServerError(e.to_string()))
440 }
441 #[cfg(not(feature = "auth"))]
442 AuthConfig::OAuth2 { .. } => Err(ServerError::ServerError(
443 "OAuth2 authentication requires the 'auth' feature to be enabled".to_string(),
444 )),
445 }
446 }
447
448 pub async fn run(self) -> Result<(), ServerError> {
450 if self.config.features.mcp_server.enabled {
452 return self.run_as_mcp_server().await;
453 }
454
455 if self.config.server.http_port > 0 {
457 self.start_http().await
458 } else {
459 Err(ServerError::ServerNotConfigured(
460 "No servers configured".to_string(),
461 ))
462 }
463 }
464
465 async fn run_as_mcp_server(self) -> Result<(), ServerError> {
473 use crate::core::mcp;
474 use a2a_rs::services::AgentInfoProvider;
475
476 info!("🔌 Running agent in MCP server mode");
477
478 let base_url = format!(
482 "http://{}:{}",
483 self.config.server.host, self.config.server.http_port
484 );
485
486 let agent_info = self.build_agent_info(base_url);
487 let agent_card = agent_info
488 .get_agent_card()
489 .await
490 .map_err(|e| ServerError::ServerError(format!("Failed to get agent card: {}", e)))?;
491
492 let handler = (*self.handler).clone();
493 mcp::run_mcp_server(&self.config.features.mcp_server, agent_card, handler)
494 .await
495 .map_err(|e| ServerError::ServerError(format!("MCP server error: {}", e)))
496 }
497
498 fn print_agent_info(&self, server_type: &str, port: &str) {
500 info!("📋 Agent: {}", self.config.agent.name);
501 if let Some(ref desc) = self.config.agent.description {
502 info!(" Description: {}", desc);
503 }
504 info!(" {} port: {}", server_type, port);
505
506 match &self.config.server.storage {
507 StorageConfig::InMemory => info!("💾 Storage: In-memory (non-persistent)"),
508 StorageConfig::Sqlx { url, .. } => info!("💾 Storage: SQLx ({})", url),
509 }
510
511 if !self.config.skills.is_empty() {
512 info!("🛠️ Skills: {}", self.config.skills.len());
513 for skill in &self.config.skills {
514 info!(" - {} ({})", skill.name, skill.id);
515 }
516 }
517 }
518}
519
520#[derive(Debug, thiserror::Error)]
522pub enum ServerError {
523 #[error("Server not configured: {0}")]
524 ServerNotConfigured(String),
525
526 #[error("Server error: {0}")]
527 ServerError(String),
528
529 #[error("Storage error: {0}")]
530 StorageError(String),
531}