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