1use serde::{Deserialize, Serialize};
19
20use crate::extensions::{AgentCardSignature, AgentExtension};
21use crate::security::{NamedSecuritySchemes, SecurityRequirement};
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub struct AgentInterface {
29 pub url: String,
31
32 pub protocol_binding: String,
34
35 pub protocol_version: String,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub tenant: Option<String>,
41}
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
47#[serde(rename_all = "camelCase")]
48#[non_exhaustive]
49pub struct AgentCapabilities {
50 #[serde(skip_serializing_if = "Option::is_none")]
52 pub streaming: Option<bool>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub push_notifications: Option<bool>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub extended_agent_card: Option<bool>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
64 pub extensions: Option<Vec<AgentExtension>>,
65}
66
67impl AgentCapabilities {
68 #[must_use]
70 pub const fn none() -> Self {
71 Self {
72 streaming: None,
73 push_notifications: None,
74 extended_agent_card: None,
75 extensions: None,
76 }
77 }
78
79 #[must_use]
81 pub const fn with_streaming(mut self, streaming: bool) -> Self {
82 self.streaming = Some(streaming);
83 self
84 }
85
86 #[must_use]
88 pub const fn with_push_notifications(mut self, push: bool) -> Self {
89 self.push_notifications = Some(push);
90 self
91 }
92
93 #[must_use]
95 pub const fn with_extended_agent_card(mut self, extended: bool) -> Self {
96 self.extended_agent_card = Some(extended);
97 self
98 }
99}
100
101impl Default for AgentCapabilities {
102 fn default() -> Self {
103 Self::none()
104 }
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct AgentProvider {
113 pub organization: String,
115
116 pub url: String,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
124#[serde(rename_all = "camelCase")]
125pub struct AgentSkill {
126 pub id: String,
128
129 pub name: String,
131
132 pub description: String,
134
135 pub tags: Vec<String>,
137
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub examples: Option<Vec<String>>,
141
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub input_modes: Option<Vec<String>>,
145
146 #[serde(skip_serializing_if = "Option::is_none")]
148 pub output_modes: Option<Vec<String>>,
149
150 #[serde(skip_serializing_if = "Option::is_none")]
152 pub security_requirements: Option<Vec<SecurityRequirement>>,
153}
154
155#[derive(Debug, Clone, Serialize, Deserialize)]
167#[serde(rename_all = "camelCase")]
168pub struct AgentCard {
169 pub name: String,
171
172 pub description: String,
174
175 pub version: String,
177
178 pub supported_interfaces: Vec<AgentInterface>,
182
183 pub default_input_modes: Vec<String>,
185
186 pub default_output_modes: Vec<String>,
188
189 pub skills: Vec<AgentSkill>,
193
194 pub capabilities: AgentCapabilities,
196
197 #[serde(skip_serializing_if = "Option::is_none")]
199 pub provider: Option<AgentProvider>,
200
201 #[serde(skip_serializing_if = "Option::is_none")]
203 pub icon_url: Option<String>,
204
205 #[serde(skip_serializing_if = "Option::is_none")]
207 pub documentation_url: Option<String>,
208
209 #[serde(skip_serializing_if = "Option::is_none")]
211 pub security_schemes: Option<NamedSecuritySchemes>,
212
213 #[serde(skip_serializing_if = "Option::is_none")]
215 pub security_requirements: Option<Vec<SecurityRequirement>>,
216
217 #[serde(skip_serializing_if = "Option::is_none")]
219 pub signatures: Option<Vec<AgentCardSignature>>,
220}
221
222#[cfg(test)]
225mod tests {
226 use super::*;
227
228 fn minimal_card() -> AgentCard {
229 AgentCard {
230 name: "Test Agent".into(),
231 description: "A test agent".into(),
232 version: "1.0.0".into(),
233 supported_interfaces: vec![AgentInterface {
234 url: "https://agent.example.com/rpc".into(),
235 protocol_binding: "JSONRPC".into(),
236 protocol_version: "1.0.0".into(),
237 tenant: None,
238 }],
239 default_input_modes: vec!["text/plain".into()],
240 default_output_modes: vec!["text/plain".into()],
241 skills: vec![AgentSkill {
242 id: "echo".into(),
243 name: "Echo".into(),
244 description: "Echoes input".into(),
245 tags: vec!["echo".into()],
246 examples: None,
247 input_modes: None,
248 output_modes: None,
249 security_requirements: None,
250 }],
251 capabilities: AgentCapabilities::none(),
252 provider: None,
253 icon_url: None,
254 documentation_url: None,
255 security_schemes: None,
256 security_requirements: None,
257 signatures: None,
258 }
259 }
260
261 #[test]
262 fn agent_card_roundtrip() {
263 let card = minimal_card();
264 let json = serde_json::to_string(&card).expect("serialize");
265 assert!(json.contains("\"supportedInterfaces\""));
266 assert!(json.contains("\"protocolBinding\":\"JSONRPC\""));
267 assert!(json.contains("\"protocolVersion\":\"1.0.0\""));
268 assert!(
269 !json.contains("\"preferredTransport\""),
270 "v1.0 removed this field"
271 );
272
273 let back: AgentCard = serde_json::from_str(&json).expect("deserialize");
274 assert_eq!(back.name, "Test Agent");
275 assert_eq!(back.supported_interfaces[0].protocol_binding, "JSONRPC");
276 }
277
278 #[test]
279 fn optional_fields_omitted() {
280 let card = minimal_card();
281 let json = serde_json::to_string(&card).expect("serialize");
282 assert!(!json.contains("\"provider\""), "provider should be absent");
283 assert!(!json.contains("\"iconUrl\""), "iconUrl should be absent");
284 assert!(
285 !json.contains("\"securitySchemes\""),
286 "securitySchemes should be absent"
287 );
288 }
289
290 #[test]
291 fn extended_agent_card_in_capabilities() {
292 let mut card = minimal_card();
293 card.capabilities.extended_agent_card = Some(true);
294 let json = serde_json::to_string(&card).expect("serialize");
295 assert!(json.contains("\"extendedAgentCard\":true"));
296 }
297
298 #[test]
299 fn wire_format_security_requirements_field_name() {
300 use crate::security::{SecurityRequirement, StringList};
301 use std::collections::HashMap;
302
303 let mut card = minimal_card();
304 card.security_requirements = Some(vec![SecurityRequirement {
305 schemes: HashMap::from([("bearer".into(), StringList { list: vec![] })]),
306 }]);
307 let json = serde_json::to_string(&card).unwrap();
308 assert!(
310 json.contains("\"securityRequirements\""),
311 "field must be securityRequirements: {json}"
312 );
313 assert!(
314 !json.contains("\"security\":"),
315 "must not have bare 'security' field: {json}"
316 );
317 }
318
319 #[test]
320 fn wire_format_skill_security_requirements() {
321 use crate::security::{SecurityRequirement, StringList};
322 use std::collections::HashMap;
323
324 let skill = AgentSkill {
325 id: "s1".into(),
326 name: "Skill".into(),
327 description: "A skill".into(),
328 tags: vec![],
329 examples: None,
330 input_modes: None,
331 output_modes: None,
332 security_requirements: Some(vec![SecurityRequirement {
333 schemes: HashMap::from([(
334 "oauth2".into(),
335 StringList {
336 list: vec!["read".into()],
337 },
338 )]),
339 }]),
340 };
341 let json = serde_json::to_string(&skill).unwrap();
342 assert!(
343 json.contains("\"securityRequirements\""),
344 "skill must use securityRequirements: {json}"
345 );
346 }
347
348 #[test]
349 fn wire_format_capabilities_no_state_transition_history() {
350 let card = minimal_card();
351 let json = serde_json::to_string(&card).unwrap();
352 assert!(
353 !json.contains("stateTransitionHistory"),
354 "stateTransitionHistory must not appear: {json}"
355 );
356 }
357}