1use bon::Builder;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub enum TransportProtocol {
11 #[serde(rename = "JSONRPC")]
12 JsonRpc,
13 #[serde(rename = "GRPC")]
14 Grpc,
15 #[serde(rename = "HTTP+JSON")]
16 HttpJson,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct AgentInterface {
35 pub url: String,
37 pub transport: String,
39}
40
41#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct AgentExtension {
60 pub uri: String,
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub description: Option<String>,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub required: Option<bool>,
68 #[serde(skip_serializing_if = "Option::is_none")]
70 pub params: Option<HashMap<String, serde_json::Value>>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct AgentCardSignature {
80 pub protected: String,
82 pub signature: String,
84 #[serde(skip_serializing_if = "Option::is_none")]
86 pub header: Option<HashMap<String, serde_json::Value>>,
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct AgentProvider {
95 pub organization: String,
96 pub url: String,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
106#[serde(tag = "type", rename_all = "camelCase")]
107pub enum SecurityScheme {
108 #[serde(rename = "apiKey")]
109 ApiKey {
110 #[serde(rename = "in")]
111 location: String, name: String,
113 #[serde(skip_serializing_if = "Option::is_none")]
114 description: Option<String>,
115 },
116 #[serde(rename = "http")]
117 Http {
118 scheme: String,
119 #[serde(skip_serializing_if = "Option::is_none", rename = "bearerFormat")]
120 bearer_format: Option<String>,
121 #[serde(skip_serializing_if = "Option::is_none")]
122 description: Option<String>,
123 },
124 #[serde(rename = "oauth2")]
125 OAuth2 {
126 flows: Box<OAuthFlows>,
127 #[serde(skip_serializing_if = "Option::is_none")]
128 description: Option<String>,
129 #[serde(skip_serializing_if = "Option::is_none", rename = "metadataUrl")]
131 metadata_url: Option<String>,
132 },
133 #[serde(rename = "openIdConnect")]
134 OpenIdConnect {
135 #[serde(rename = "openIdConnectUrl")]
136 open_id_connect_url: String,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 description: Option<String>,
139 },
140 #[serde(rename = "mutualTLS")]
142 MutualTls {
143 #[serde(skip_serializing_if = "Option::is_none")]
144 description: Option<String>,
145 },
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, Default)]
157pub struct OAuthFlows {
158 #[serde(skip_serializing_if = "Option::is_none", rename = "authorizationCode")]
159 pub authorization_code: Option<AuthorizationCodeOAuthFlow>,
160 #[serde(skip_serializing_if = "Option::is_none", rename = "clientCredentials")]
161 pub client_credentials: Option<ClientCredentialsOAuthFlow>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub implicit: Option<ImplicitOAuthFlow>,
164 #[serde(skip_serializing_if = "Option::is_none")]
165 pub password: Option<PasswordOAuthFlow>,
166}
167
168#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct AuthorizationCodeOAuthFlow {
175 #[serde(rename = "authorizationUrl")]
176 pub authorization_url: String,
177 #[serde(rename = "tokenUrl")]
178 pub token_url: String,
179 #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
180 pub refresh_url: Option<String>,
181 pub scopes: HashMap<String, String>,
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize)]
190pub struct ClientCredentialsOAuthFlow {
191 #[serde(rename = "tokenUrl")]
192 pub token_url: String,
193 #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
194 pub refresh_url: Option<String>,
195 pub scopes: HashMap<String, String>,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct ImplicitOAuthFlow {
205 #[serde(rename = "authorizationUrl")]
206 pub authorization_url: String,
207 #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
208 pub refresh_url: Option<String>,
209 pub scopes: HashMap<String, String>,
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
218pub struct PasswordOAuthFlow {
219 #[serde(rename = "tokenUrl")]
220 pub token_url: String,
221 #[serde(skip_serializing_if = "Option::is_none", rename = "refreshUrl")]
222 pub refresh_url: Option<String>,
223 pub scopes: HashMap<String, String>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, Default)]
234pub struct AgentCapabilities {
235 #[serde(default)]
236 pub streaming: bool,
237 #[serde(default, rename = "pushNotifications")]
238 pub push_notifications: bool,
239 #[serde(default, rename = "stateTransitionHistory")]
240 pub state_transition_history: bool,
241 #[serde(skip_serializing_if = "Option::is_none")]
243 pub extensions: Option<Vec<AgentExtension>>,
244}
245
246#[derive(Debug, Clone, Serialize, Deserialize)]
248pub struct AgentSkill {
249 pub id: String,
250 pub name: String,
251 pub description: String,
252 pub tags: Vec<String>,
253 #[serde(skip_serializing_if = "Option::is_none")]
254 pub examples: Option<Vec<String>>,
255 #[serde(skip_serializing_if = "Option::is_none", rename = "inputModes")]
256 pub input_modes: Option<Vec<String>>,
257 #[serde(skip_serializing_if = "Option::is_none", rename = "outputModes")]
258 pub output_modes: Option<Vec<String>>,
259 #[serde(skip_serializing_if = "Option::is_none")]
261 pub security: Option<Vec<HashMap<String, Vec<String>>>>,
262}
263
264impl AgentSkill {
265 pub fn new(id: String, name: String, description: String, tags: Vec<String>) -> Self {
267 Self {
268 id,
269 name,
270 description,
271 tags,
272 examples: None,
273 input_modes: None,
274 output_modes: None,
275 security: None,
276 }
277 }
278
279 pub fn with_examples(mut self, examples: Vec<String>) -> Self {
281 self.examples = Some(examples);
282 self
283 }
284
285 pub fn with_input_modes(mut self, input_modes: Vec<String>) -> Self {
287 self.input_modes = Some(input_modes);
288 self
289 }
290
291 pub fn with_output_modes(mut self, output_modes: Vec<String>) -> Self {
293 self.output_modes = Some(output_modes);
294 self
295 }
296
297 pub fn with_security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
299 self.security = Some(security);
300 self
301 }
302
303 #[allow(clippy::too_many_arguments)]
305 pub fn comprehensive(
306 id: String,
307 name: String,
308 description: String,
309 tags: Vec<String>,
310 examples: Option<Vec<String>>,
311 input_modes: Option<Vec<String>>,
312 output_modes: Option<Vec<String>>,
313 security: Option<Vec<HashMap<String, Vec<String>>>>,
314 ) -> Self {
315 Self {
316 id,
317 name,
318 description,
319 tags,
320 examples,
321 input_modes,
322 output_modes,
323 security,
324 }
325 }
326}
327
328#[derive(Debug, Clone, Serialize, Deserialize, Builder)]
330pub struct AgentCard {
331 pub name: String,
332 pub description: String,
333 pub url: String,
334 #[serde(skip_serializing_if = "Option::is_none")]
335 pub provider: Option<AgentProvider>,
336 pub version: String,
337 #[serde(default = "default_protocol_version", rename = "protocolVersion")]
339 #[builder(default = default_protocol_version())]
340 pub protocol_version: String,
341 #[serde(default = "default_preferred_transport", rename = "preferredTransport")]
343 #[builder(default = default_preferred_transport())]
344 pub preferred_transport: String,
345 #[serde(
347 skip_serializing_if = "Option::is_none",
348 rename = "additionalInterfaces"
349 )]
350 pub additional_interfaces: Option<Vec<AgentInterface>>,
351 #[serde(skip_serializing_if = "Option::is_none", rename = "iconUrl")]
353 pub icon_url: Option<String>,
354 #[serde(skip_serializing_if = "Option::is_none", rename = "documentationUrl")]
355 pub documentation_url: Option<String>,
356 pub capabilities: AgentCapabilities,
357 #[serde(skip_serializing_if = "Option::is_none", rename = "securitySchemes")]
358 pub security_schemes: Option<HashMap<String, SecurityScheme>>,
359 #[serde(skip_serializing_if = "Option::is_none")]
360 pub security: Option<Vec<HashMap<String, Vec<String>>>>,
361 #[serde(default = "default_input_modes", rename = "defaultInputModes")]
362 pub default_input_modes: Vec<String>,
363 #[serde(default = "default_output_modes", rename = "defaultOutputModes")]
364 pub default_output_modes: Vec<String>,
365 pub skills: Vec<AgentSkill>,
366 #[serde(skip_serializing_if = "Option::is_none")]
368 pub signatures: Option<Vec<AgentCardSignature>>,
369 #[serde(
370 skip_serializing_if = "Option::is_none",
371 rename = "supportsAuthenticatedExtendedCard"
372 )]
373 pub supports_authenticated_extended_card: Option<bool>,
374}
375
376fn default_input_modes() -> Vec<String> {
377 vec!["text".to_string()]
378}
379
380fn default_output_modes() -> Vec<String> {
381 vec!["text".to_string()]
382}
383
384fn default_protocol_version() -> String {
385 "0.3.0".to_string()
386}
387
388fn default_preferred_transport() -> String {
389 "JSONRPC".to_string()
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
398pub struct PushNotificationAuthenticationInfo {
399 pub schemes: Vec<String>,
400 #[serde(skip_serializing_if = "Option::is_none")]
401 pub credentials: Option<String>,
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
422pub struct PushNotificationConfig {
423 #[serde(skip_serializing_if = "Option::is_none")]
426 pub id: Option<String>,
427 pub url: String,
428 pub token: Option<String>,
429 #[serde(skip_serializing_if = "Option::is_none")]
430 pub authentication: Option<PushNotificationAuthenticationInfo>,
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436 use serde_json::json;
437
438 #[test]
439 fn test_security_scheme_api_key_serialization() {
440 let scheme = SecurityScheme::ApiKey {
441 location: "header".to_string(),
442 name: "X-API-Key".to_string(),
443 description: Some("API Key authentication".to_string()),
444 };
445
446 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
447 assert_eq!(json_value["type"], "apiKey");
448 assert_eq!(json_value["in"], "header");
449 assert_eq!(json_value["name"], "X-API-Key");
450 }
451
452 #[test]
453 fn test_security_scheme_http_serialization() {
454 let scheme = SecurityScheme::Http {
455 scheme: "bearer".to_string(),
456 bearer_format: Some("JWT".to_string()),
457 description: Some("Bearer token authentication".to_string()),
458 };
459
460 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
461 assert_eq!(json_value["type"], "http");
462 assert_eq!(json_value["scheme"], "bearer");
463 assert_eq!(json_value["bearerFormat"], "JWT");
464 }
465
466 #[test]
467 fn test_security_scheme_mtls_serialization() {
468 let scheme = SecurityScheme::MutualTls {
469 description: Some("Mutual TLS authentication".to_string()),
470 };
471
472 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
473 assert_eq!(json_value["type"], "mutualTLS");
474 assert_eq!(json_value["description"], "Mutual TLS authentication");
475 }
476
477 #[test]
478 fn test_security_scheme_oauth2_with_metadata() {
479 let flows = OAuthFlows {
480 authorization_code: Some(AuthorizationCodeOAuthFlow {
481 authorization_url: "https://example.com/oauth/authorize".to_string(),
482 token_url: "https://example.com/oauth/token".to_string(),
483 refresh_url: None,
484 scopes: HashMap::new(),
485 }),
486 client_credentials: None,
487 implicit: None,
488 password: None,
489 };
490
491 let scheme = SecurityScheme::OAuth2 {
492 flows: Box::new(flows),
493 description: Some("OAuth2 authentication".to_string()),
494 metadata_url: Some(
495 "https://example.com/.well-known/oauth-authorization-server".to_string(),
496 ),
497 };
498
499 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
500 assert_eq!(json_value["type"], "oauth2");
501 assert_eq!(
502 json_value["metadataUrl"],
503 "https://example.com/.well-known/oauth-authorization-server"
504 );
505 }
506
507 #[test]
508 fn test_agent_card_with_signature() {
509 let mut signature_header = HashMap::new();
510 signature_header.insert("alg".to_string(), json!("RS256"));
511
512 let signature = AgentCardSignature {
513 protected: "eyJhbGciOiJSUzI1NiJ9".to_string(),
514 signature: "dGVzdF9zaWduYXR1cmU".to_string(),
515 header: Some(signature_header),
516 };
517
518 let card = AgentCard {
519 name: "Test Agent".to_string(),
520 description: "Test description".to_string(),
521 url: "https://example.com".to_string(),
522 provider: None,
523 version: "1.0.0".to_string(),
524 protocol_version: "0.3.0".to_string(),
525 preferred_transport: "JSONRPC".to_string(),
526 additional_interfaces: None,
527 icon_url: None,
528 documentation_url: None,
529 capabilities: AgentCapabilities::default(),
530 security_schemes: None,
531 security: None,
532 default_input_modes: vec!["text".to_string()],
533 default_output_modes: vec!["text".to_string()],
534 skills: Vec::new(),
535 signatures: Some(vec![signature]),
536 supports_authenticated_extended_card: Some(true),
537 };
538
539 let json_value = serde_json::to_value(&card).expect("Failed to serialize AgentCard");
540 assert!(json_value["signatures"].is_array());
541 assert_eq!(
542 json_value["signatures"][0]["protected"],
543 "eyJhbGciOiJSUzI1NiJ9"
544 );
545 assert_eq!(json_value["supportsAuthenticatedExtendedCard"], true);
546 assert_eq!(json_value["protocolVersion"], "0.3.0");
547 }
548
549 #[test]
550 fn test_agent_skill_with_security() {
551 let mut security_req = HashMap::new();
552 security_req.insert("oauth2".to_string(), vec!["read:users".to_string()]);
553
554 let skill = AgentSkill {
555 id: "test-skill".to_string(),
556 name: "Test Skill".to_string(),
557 description: "A test skill".to_string(),
558 tags: Vec::new(),
559 examples: None,
560 input_modes: None,
561 output_modes: None,
562 security: Some(vec![security_req]),
563 };
564
565 let json_value = serde_json::to_value(&skill).expect("Failed to serialize AgentSkill");
566 assert!(json_value["security"].is_array());
567 assert_eq!(json_value["security"][0]["oauth2"][0], "read:users");
568 }
569
570 #[test]
571 fn test_agent_card_with_security_schemes() {
572 let mut security_schemes = HashMap::new();
573 security_schemes.insert(
574 "bearer".to_string(),
575 SecurityScheme::Http {
576 scheme: "bearer".to_string(),
577 bearer_format: Some("JWT".to_string()),
578 description: None,
579 },
580 );
581 security_schemes.insert(
582 "mtls".to_string(),
583 SecurityScheme::MutualTls {
584 description: Some("Client certificate authentication".to_string()),
585 },
586 );
587
588 let mut security_req = HashMap::new();
589 security_req.insert("bearer".to_string(), Vec::new());
590
591 let card = AgentCard {
592 name: "Secure Agent".to_string(),
593 description: "Secure agent description".to_string(),
594 url: "https://example.com".to_string(),
595 provider: None,
596 version: "1.0.0".to_string(),
597 protocol_version: "0.3.0".to_string(),
598 preferred_transport: "JSONRPC".to_string(),
599 additional_interfaces: None,
600 icon_url: None,
601 documentation_url: None,
602 capabilities: AgentCapabilities::default(),
603 security_schemes: Some(security_schemes),
604 security: Some(vec![security_req]),
605 default_input_modes: vec!["text".to_string()],
606 default_output_modes: vec!["text".to_string()],
607 skills: Vec::new(),
608 signatures: None,
609 supports_authenticated_extended_card: None,
610 };
611
612 let json_value = serde_json::to_value(&card).expect("Failed to serialize AgentCard");
613 assert!(json_value["securitySchemes"].is_object());
614 assert_eq!(json_value["securitySchemes"]["bearer"]["type"], "http");
615 assert_eq!(json_value["securitySchemes"]["mtls"]["type"], "mutualTLS");
616 assert!(json_value["security"].is_array());
617 }
618}