systemprompt_models/a2a/agent_card/
mod.rs1mod extension;
8mod skill;
9
10pub use extension::{ARTIFACT_RENDERING_URI, AgentCapabilities, AgentExtension};
11pub use skill::{AgentCardSignature, AgentInterface, AgentProvider, AgentSkill};
12
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16use super::security::SecurityScheme;
17use super::transport::ProtocolBinding;
18
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
20#[serde(rename_all = "camelCase")]
21pub struct AgentCard {
22 pub name: String,
23 pub description: String,
24 pub supported_interfaces: Vec<AgentInterface>,
25 pub version: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub icon_url: Option<String>,
28 #[serde(skip_serializing_if = "Option::is_none")]
29 pub provider: Option<AgentProvider>,
30 #[serde(skip_serializing_if = "Option::is_none")]
31 pub documentation_url: Option<String>,
32 pub capabilities: AgentCapabilities,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub security_schemes: Option<HashMap<String, SecurityScheme>>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub security: Option<Vec<HashMap<String, Vec<String>>>>,
37 pub default_input_modes: Vec<String>,
38 pub default_output_modes: Vec<String>,
39 #[serde(default)]
40 pub skills: Vec<AgentSkill>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub supports_authenticated_extended_card: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
44 pub signatures: Option<Vec<AgentCardSignature>>,
45}
46
47impl AgentCard {
48 #[must_use]
49 pub fn builder(
50 name: String,
51 description: String,
52 url: String,
53 version: String,
54 ) -> AgentCardBuilder {
55 AgentCardBuilder::new(name, description, url, version)
56 }
57
58 #[must_use]
59 pub fn url(&self) -> Option<&str> {
60 self.supported_interfaces.first().map(|i| i.url.as_str())
61 }
62
63 #[must_use]
64 pub fn has_mcp_extension(&self) -> bool {
65 self.capabilities
66 .extensions
67 .as_ref()
68 .is_some_and(|exts| exts.iter().any(|ext| ext.uri == "systemprompt:mcp-tools"))
69 }
70
71 pub fn ensure_mcp_extension(&mut self) {
72 if self.has_mcp_extension() {
73 return;
74 }
75
76 self.capabilities
77 .extensions
78 .get_or_insert_with(Vec::new)
79 .push(AgentExtension::mcp_tools_extension());
80 }
81}
82
83#[derive(Debug)]
84pub struct AgentCardBuilder {
85 agent_card: AgentCard,
86}
87
88impl AgentCardBuilder {
89 #[must_use]
90 pub fn new(name: String, description: String, url: String, version: String) -> Self {
91 Self {
92 agent_card: AgentCard {
93 name,
94 description,
95 supported_interfaces: vec![AgentInterface {
96 url,
97 protocol_binding: ProtocolBinding::JsonRpc,
98 protocol_version: "1.0.0".to_owned(),
99 }],
100 version,
101 icon_url: None,
102 provider: None,
103 documentation_url: None,
104 capabilities: AgentCapabilities::default(),
105 security_schemes: None,
106 security: None,
107 default_input_modes: vec!["text/plain".to_owned()],
108 default_output_modes: vec!["text/plain".to_owned()],
109 skills: Vec::new(),
110 supports_authenticated_extended_card: Some(false),
111 signatures: None,
112 },
113 }
114 }
115
116 #[must_use]
117 pub const fn with_streaming(mut self) -> Self {
118 self.agent_card.capabilities.streaming = Some(true);
119 self
120 }
121
122 #[must_use]
123 pub const fn with_push_notifications(mut self) -> Self {
124 self.agent_card.capabilities.push_notifications = Some(true);
125 self
126 }
127
128 #[must_use]
129 pub fn with_provider(mut self, organization: String, url: String) -> Self {
130 self.agent_card.provider = Some(AgentProvider { organization, url });
131 self
132 }
133
134 #[must_use]
135 pub fn build(self) -> AgentCard {
136 self.agent_card
137 }
138}