a2a_types/
agent_card.rs

1use crate::SecurityScheme;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4// ============================================================================
5// A2A Agent Card and Discovery Types
6// ============================================================================
7
8/// Supported A2A transport protocols.
9#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
10pub enum TransportProtocol {
11    /// JSON-RPC 2.0 over HTTP
12    #[default]
13    #[serde(rename = "JSONRPC")]
14    JsonRpc,
15    /// gRPC over HTTP/2
16    #[serde(rename = "GRPC")]
17    Grpc,
18    /// REST-style HTTP with JSON
19    #[serde(rename = "HTTP+JSON")]
20    HttpJson,
21}
22
23/// Declares a combination of a target URL and a transport protocol for interacting with the agent.
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct AgentInterface {
26    /// The transport protocol supported at this URL.
27    pub transport: TransportProtocol,
28    /// The URL where this interface is available.
29    pub url: String,
30}
31
32/// A declaration of a protocol extension supported by an Agent.
33#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct AgentExtension {
35    /// The unique URI identifying the extension.
36    pub uri: String,
37    /// A human-readable description of how this agent uses the extension.
38    #[serde(skip_serializing_if = "Option::is_none")]
39    pub description: Option<String>,
40    /// If true, the client must understand and comply with the extension's requirements.
41    #[serde(skip_serializing_if = "Option::is_none")]
42    pub required: Option<bool>,
43    /// Optional, extension-specific configuration parameters.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    pub params: Option<HashMap<String, serde_json::Value>>,
46}
47
48/// Defines optional capabilities supported by an agent.
49#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
50pub struct AgentCapabilities {
51    /// Indicates if the agent supports Server-Sent Events (SSE) for streaming responses.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub streaming: Option<bool>,
54    /// Indicates if the agent supports sending push notifications for asynchronous task updates.
55    #[serde(skip_serializing_if = "Option::is_none", rename = "pushNotifications")]
56    pub push_notifications: Option<bool>,
57    /// Indicates if the agent provides a history of state transitions for a task.
58    #[serde(
59        skip_serializing_if = "Option::is_none",
60        rename = "stateTransitionHistory"
61    )]
62    pub state_transition_history: Option<bool>,
63    /// A list of protocol extensions supported by the agent.
64    #[serde(skip_serializing_if = "Vec::is_empty", default)]
65    pub extensions: Vec<AgentExtension>,
66}
67
68/// Represents the service provider of an agent.
69#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct AgentProvider {
71    /// The name of the agent provider's organization.
72    pub organization: String,
73    /// A URL for the agent provider's website or relevant documentation.
74    pub url: String,
75}
76
77/// Represents a distinct capability or function that an agent can perform.
78#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79pub struct AgentSkill {
80    /// A unique identifier for the agent's skill.
81    pub id: String,
82    /// A human-readable name for the skill.
83    pub name: String,
84    /// A detailed description of the skill.
85    pub description: String,
86    /// A set of keywords describing the skill's capabilities.
87    pub tags: Vec<String>,
88    /// Example prompts or scenarios that this skill can handle.
89    #[serde(skip_serializing_if = "Vec::is_empty", default)]
90    pub examples: Vec<String>,
91    /// The set of supported input MIME types for this skill, overriding the agent's defaults.
92    #[serde(skip_serializing_if = "Vec::is_empty", rename = "inputModes", default)]
93    pub input_modes: Vec<String>,
94    /// The set of supported output MIME types for this skill, overriding the agent's defaults.
95    #[serde(skip_serializing_if = "Vec::is_empty", rename = "outputModes", default)]
96    pub output_modes: Vec<String>,
97    /// Security schemes necessary for the agent to leverage this skill.
98    #[serde(skip_serializing_if = "Vec::is_empty", default)]
99    pub security: Vec<HashMap<String, Vec<String>>>,
100}
101
102/// Represents a JWS signature of an AgentCard.
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
104pub struct AgentCardSignature {
105    /// The protected JWS header for the signature (Base64url-encoded).
106    #[serde(rename = "protected")]
107    pub protected_header: String,
108    /// The computed signature (Base64url-encoded).
109    pub signature: String,
110    /// The unprotected JWS header values.
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub header: Option<HashMap<String, serde_json::Value>>,
113}
114
115/// The AgentCard is a self-describing manifest for an agent.
116#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117pub struct AgentCard {
118    /// A human-readable name for the agent.
119    pub name: String,
120    /// A human-readable description of the agent.
121    pub description: String,
122    /// The agent's own version number.
123    pub version: String,
124    /// The version of the A2A protocol this agent supports.
125    #[serde(rename = "protocolVersion", default = "default_protocol_version")]
126    pub protocol_version: String,
127    /// The preferred endpoint URL for interacting with the agent.
128    pub url: String,
129    /// The transport protocol for the preferred endpoint.
130    #[serde(rename = "preferredTransport", default)]
131    pub preferred_transport: TransportProtocol,
132    /// A declaration of optional capabilities supported by the agent.
133    pub capabilities: AgentCapabilities,
134    /// Default set of supported input MIME types for all skills.
135    #[serde(rename = "defaultInputModes")]
136    pub default_input_modes: Vec<String>,
137    /// Default set of supported output MIME types for all skills.
138    #[serde(rename = "defaultOutputModes")]
139    pub default_output_modes: Vec<String>,
140    /// The set of skills that the agent can perform.
141    pub skills: Vec<AgentSkill>,
142    /// Information about the agent's service provider.
143    #[serde(skip_serializing_if = "Option::is_none")]
144    pub provider: Option<AgentProvider>,
145    /// A list of additional supported interfaces (transport and URL combinations).
146    #[serde(
147        skip_serializing_if = "Vec::is_empty",
148        rename = "additionalInterfaces",
149        default
150    )]
151    pub additional_interfaces: Vec<AgentInterface>,
152    /// An optional URL to the agent's documentation.
153    #[serde(skip_serializing_if = "Option::is_none", rename = "documentationUrl")]
154    pub documentation_url: Option<String>,
155    /// An optional URL to an icon for the agent.
156    #[serde(skip_serializing_if = "Option::is_none", rename = "iconUrl")]
157    pub icon_url: Option<String>,
158    /// A list of security requirement objects that apply to all agent interactions.
159    #[serde(skip_serializing_if = "Vec::is_empty", default)]
160    pub security: Vec<HashMap<String, Vec<String>>>,
161    /// A declaration of the security schemes available to authorize requests.
162    #[serde(skip_serializing_if = "Option::is_none", rename = "securitySchemes")]
163    pub security_schemes: Option<HashMap<String, SecurityScheme>>,
164    /// JSON Web Signatures computed for this AgentCard.
165    #[serde(skip_serializing_if = "Vec::is_empty", default)]
166    pub signatures: Vec<AgentCardSignature>,
167    /// If true, the agent can provide an extended agent card to authenticated users.
168    #[serde(
169        skip_serializing_if = "Option::is_none",
170        rename = "supportsAuthenticatedExtendedCard"
171    )]
172    pub supports_authenticated_extended_card: Option<bool>,
173}
174
175fn default_protocol_version() -> String {
176    crate::PROTOCOL_VERSION.to_string()
177}
178
179impl AgentCard {
180    /// Create a new AgentCard with minimal required fields
181    pub fn new(
182        name: impl Into<String>,
183        description: impl Into<String>,
184        version: impl Into<String>,
185        url: impl Into<String>,
186    ) -> Self {
187        Self {
188            name: name.into(),
189            description: description.into(),
190            version: version.into(),
191            protocol_version: default_protocol_version(),
192            url: url.into(),
193            preferred_transport: TransportProtocol::default(),
194            capabilities: AgentCapabilities::default(),
195            default_input_modes: vec!["text/plain".to_string()],
196            default_output_modes: vec!["text/plain".to_string()],
197            skills: Vec::new(),
198            provider: None,
199            additional_interfaces: Vec::new(),
200            documentation_url: None,
201            icon_url: None,
202            security: Vec::new(),
203            security_schemes: None,
204            signatures: Vec::new(),
205            supports_authenticated_extended_card: None,
206        }
207    }
208
209    /// Set the agent's name
210    pub fn with_name(mut self, name: impl Into<String>) -> Self {
211        self.name = name.into();
212        self
213    }
214
215    /// Set the agent's description
216    pub fn with_description(mut self, description: impl Into<String>) -> Self {
217        self.description = description.into();
218        self
219    }
220
221    /// Set the agent's version
222    pub fn with_version(mut self, version: impl Into<String>) -> Self {
223        self.version = version.into();
224        self
225    }
226
227    /// Set the A2A protocol version
228    pub fn with_protocol_version(mut self, protocol_version: impl Into<String>) -> Self {
229        self.protocol_version = protocol_version.into();
230        self
231    }
232
233    /// Set the agent's URL endpoint
234    pub fn with_url(mut self, url: impl Into<String>) -> Self {
235        self.url = url.into();
236        self
237    }
238
239    /// Set the preferred transport protocol
240    pub fn with_preferred_transport(mut self, transport: TransportProtocol) -> Self {
241        self.preferred_transport = transport;
242        self
243    }
244
245    /// Set the agent's capabilities
246    pub fn with_capabilities(mut self, capabilities: AgentCapabilities) -> Self {
247        self.capabilities = capabilities;
248        self
249    }
250
251    /// Enable streaming capability
252    pub fn with_streaming(mut self, enabled: bool) -> Self {
253        self.capabilities.streaming = Some(enabled);
254        self
255    }
256
257    /// Enable push notifications capability
258    pub fn with_push_notifications(mut self, enabled: bool) -> Self {
259        self.capabilities.push_notifications = Some(enabled);
260        self
261    }
262
263    /// Enable state transition history capability
264    pub fn with_state_transition_history(mut self, enabled: bool) -> Self {
265        self.capabilities.state_transition_history = Some(enabled);
266        self
267    }
268
269    /// Add an extension to the capabilities
270    pub fn add_extension(mut self, extension: AgentExtension) -> Self {
271        self.capabilities.extensions.push(extension);
272        self
273    }
274
275    /// Set default input modes (replaces existing)
276    pub fn with_default_input_modes(mut self, modes: Vec<String>) -> Self {
277        self.default_input_modes = modes;
278        self
279    }
280
281    /// Add a default input mode
282    pub fn add_input_mode(mut self, mode: impl Into<String>) -> Self {
283        self.default_input_modes.push(mode.into());
284        self
285    }
286
287    /// Set default output modes (replaces existing)
288    pub fn with_default_output_modes(mut self, modes: Vec<String>) -> Self {
289        self.default_output_modes = modes;
290        self
291    }
292
293    /// Add a default output mode
294    pub fn add_output_mode(mut self, mode: impl Into<String>) -> Self {
295        self.default_output_modes.push(mode.into());
296        self
297    }
298
299    /// Set skills (replaces existing)
300    pub fn with_skills(mut self, skills: Vec<AgentSkill>) -> Self {
301        self.skills = skills;
302        self
303    }
304
305    /// Add a skill
306    pub fn add_skill(mut self, skill: AgentSkill) -> Self {
307        self.skills.push(skill);
308        self
309    }
310
311    /// Create a skill using a builder pattern and add it
312    pub fn add_skill_with<F>(mut self, id: impl Into<String>, name: impl Into<String>, f: F) -> Self
313    where
314        F: FnOnce(AgentSkill) -> AgentSkill,
315    {
316        let skill = AgentSkill::new(id.into(), name.into());
317        self.skills.push(f(skill));
318        self
319    }
320
321    /// Set the provider information
322    pub fn with_provider(
323        mut self,
324        organization: impl Into<String>,
325        url: impl Into<String>,
326    ) -> Self {
327        self.provider = Some(AgentProvider {
328            organization: organization.into(),
329            url: url.into(),
330        });
331        self
332    }
333
334    /// Set additional interfaces (replaces existing)
335    pub fn with_additional_interfaces(mut self, interfaces: Vec<AgentInterface>) -> Self {
336        self.additional_interfaces = interfaces;
337        self
338    }
339
340    /// Add an additional interface
341    pub fn add_interface(mut self, transport: TransportProtocol, url: impl Into<String>) -> Self {
342        self.additional_interfaces.push(AgentInterface {
343            transport,
344            url: url.into(),
345        });
346        self
347    }
348
349    /// Set documentation URL
350    pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
351        self.documentation_url = Some(url.into());
352        self
353    }
354
355    /// Set icon URL
356    pub fn with_icon_url(mut self, url: impl Into<String>) -> Self {
357        self.icon_url = Some(url.into());
358        self
359    }
360
361    /// Set security requirements
362    pub fn with_security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
363        self.security = security;
364        self
365    }
366
367    /// Add a security requirement
368    pub fn add_security_requirement(mut self, requirement: HashMap<String, Vec<String>>) -> Self {
369        self.security.push(requirement);
370        self
371    }
372
373    /// Set security schemes
374    pub fn with_security_schemes(
375        mut self,
376        schemes: HashMap<String, crate::SecurityScheme>,
377    ) -> Self {
378        self.security_schemes = Some(schemes);
379        self
380    }
381
382    /// Enable authenticated extended card support
383    pub fn with_authenticated_extended_card(mut self, supported: bool) -> Self {
384        self.supports_authenticated_extended_card = Some(supported);
385        self
386    }
387
388    /// Set signatures (replaces existing)
389    pub fn with_signatures(mut self, signatures: Vec<AgentCardSignature>) -> Self {
390        self.signatures = signatures;
391        self
392    }
393
394    /// Add a signature
395    pub fn add_signature(mut self, signature: AgentCardSignature) -> Self {
396        self.signatures.push(signature);
397        self
398    }
399}
400
401impl AgentSkill {
402    /// Create a new skill with required fields
403    pub fn new(id: String, name: String) -> Self {
404        Self {
405            id,
406            name,
407            description: String::new(),
408            tags: Vec::new(),
409            examples: Vec::new(),
410            input_modes: Vec::new(),
411            output_modes: Vec::new(),
412            security: Vec::new(),
413        }
414    }
415
416    /// Set the skill description
417    pub fn with_description(mut self, description: impl Into<String>) -> Self {
418        self.description = description.into();
419        self
420    }
421
422    /// Add a tag
423    pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
424        self.tags.push(tag.into());
425        self
426    }
427
428    /// Set tags (replaces existing)
429    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
430        self.tags = tags;
431        self
432    }
433
434    /// Add an example
435    pub fn add_example(mut self, example: impl Into<String>) -> Self {
436        self.examples.push(example.into());
437        self
438    }
439
440    /// Set examples (replaces existing)
441    pub fn with_examples(mut self, examples: Vec<String>) -> Self {
442        self.examples = examples;
443        self
444    }
445
446    /// Add an input mode
447    pub fn add_input_mode(mut self, mode: impl Into<String>) -> Self {
448        self.input_modes.push(mode.into());
449        self
450    }
451
452    /// Set input modes (replaces existing)
453    pub fn with_input_modes(mut self, modes: Vec<String>) -> Self {
454        self.input_modes = modes;
455        self
456    }
457
458    /// Add an output mode
459    pub fn add_output_mode(mut self, mode: impl Into<String>) -> Self {
460        self.output_modes.push(mode.into());
461        self
462    }
463
464    /// Set output modes (replaces existing)
465    pub fn with_output_modes(mut self, modes: Vec<String>) -> Self {
466        self.output_modes = modes;
467        self
468    }
469
470    /// Add a security requirement
471    pub fn add_security(mut self, security: HashMap<String, Vec<String>>) -> Self {
472        self.security.push(security);
473        self
474    }
475}
476
477#[cfg(test)]
478mod tests {
479    use super::*;
480
481    #[test]
482    fn test_agent_card_new() {
483        let card = AgentCard::new(
484            "Test Agent",
485            "A test agent",
486            "1.0.0",
487            "http://localhost:8080",
488        );
489
490        assert_eq!(card.name, "Test Agent");
491        assert_eq!(card.description, "A test agent");
492        assert_eq!(card.version, "1.0.0");
493        assert_eq!(card.url, "http://localhost:8080");
494    }
495
496    #[test]
497    fn test_agent_card_with_capabilities() {
498        let card = AgentCard::new(
499            "Test Agent",
500            "A test agent",
501            "1.0.0",
502            "http://localhost:8080",
503        )
504        .with_streaming(true)
505        .with_push_notifications(true)
506        .with_state_transition_history(false);
507
508        assert_eq!(card.capabilities.streaming, Some(true));
509        assert_eq!(card.capabilities.push_notifications, Some(true));
510        assert_eq!(card.capabilities.state_transition_history, Some(false));
511    }
512
513    #[test]
514    fn test_agent_card_with_skill() {
515        let card = AgentCard::new(
516            "Test Agent",
517            "A test agent",
518            "1.0.0",
519            "http://localhost:8080",
520        )
521        .add_skill_with("skill1", "Test Skill", |s| {
522            s.with_description("A test skill")
523                .add_tag("test")
524                .add_tag("example")
525                .add_example("Example usage")
526        });
527
528        assert_eq!(card.skills.len(), 1);
529        let skill = &card.skills[0];
530        assert_eq!(skill.id, "skill1");
531        assert_eq!(skill.name, "Test Skill");
532        assert_eq!(skill.description, "A test skill");
533        assert_eq!(skill.tags, vec!["test", "example"]);
534        assert_eq!(skill.examples, vec!["Example usage"]);
535    }
536
537    #[test]
538    fn test_agent_card_defaults() {
539        let card = AgentCard::new(
540            "Test Agent",
541            "Test Description",
542            "1.0.0",
543            "http://localhost:8080",
544        );
545
546        assert_eq!(card.default_input_modes, vec!["text/plain"]);
547        assert_eq!(card.default_output_modes, vec!["text/plain"]);
548    }
549
550    #[test]
551    fn test_modify_existing_card() {
552        let card = AgentCard::new(
553            "Original Agent",
554            "Original description",
555            "1.0.0",
556            "http://localhost:8080",
557        )
558        .with_name("Modified Agent")
559        .with_version("2.0.0")
560        .with_streaming(true);
561
562        assert_eq!(card.name, "Modified Agent");
563        assert_eq!(card.description, "Original description");
564        assert_eq!(card.version, "2.0.0");
565        assert_eq!(card.capabilities.streaming, Some(true));
566    }
567}