1use serde::{Deserialize, Serialize};
21
22use crate::extensions::{AgentCardSignature, AgentExtension};
23use crate::security::{NamedSecuritySchemes, SecurityRequirement};
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
29#[serde(rename_all = "camelCase")]
30pub struct AgentInterface {
31 pub url: String,
33
34 pub protocol_binding: String,
36
37 pub protocol_version: String,
39
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub tenant: Option<String>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50#[non_exhaustive]
51pub struct AgentCapabilities {
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub streaming: Option<bool>,
55
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub push_notifications: Option<bool>,
59
60 #[serde(skip_serializing_if = "Option::is_none")]
62 pub extended_agent_card: Option<bool>,
63
64 #[serde(skip_serializing_if = "Option::is_none")]
66 pub extensions: Option<Vec<AgentExtension>>,
67}
68
69impl AgentCapabilities {
70 #[must_use]
72 pub const fn none() -> Self {
73 Self {
74 streaming: None,
75 push_notifications: None,
76 extended_agent_card: None,
77 extensions: None,
78 }
79 }
80
81 #[must_use]
83 pub const fn with_streaming(mut self, streaming: bool) -> Self {
84 self.streaming = Some(streaming);
85 self
86 }
87
88 #[must_use]
90 pub const fn with_push_notifications(mut self, push: bool) -> Self {
91 self.push_notifications = Some(push);
92 self
93 }
94
95 #[must_use]
97 pub const fn with_extended_agent_card(mut self, extended: bool) -> Self {
98 self.extended_agent_card = Some(extended);
99 self
100 }
101}
102
103impl Default for AgentCapabilities {
104 fn default() -> Self {
105 Self::none()
106 }
107}
108
109#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct AgentProvider {
115 pub organization: String,
117
118 pub url: String,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct AgentSkill {
128 pub id: String,
130
131 pub name: String,
133
134 pub description: String,
136
137 pub tags: Vec<String>,
139
140 #[serde(skip_serializing_if = "Option::is_none")]
142 pub examples: Option<Vec<String>>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub input_modes: Option<Vec<String>>,
147
148 #[serde(skip_serializing_if = "Option::is_none")]
150 pub output_modes: Option<Vec<String>>,
151
152 #[serde(skip_serializing_if = "Option::is_none")]
154 pub security_requirements: Option<Vec<SecurityRequirement>>,
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(rename_all = "camelCase")]
170pub struct AgentCard {
171 pub name: String,
173
174 #[serde(skip_serializing_if = "Option::is_none")]
179 pub url: Option<String>,
180
181 pub description: String,
183
184 pub version: String,
186
187 pub supported_interfaces: Vec<AgentInterface>,
191
192 pub default_input_modes: Vec<String>,
194
195 pub default_output_modes: Vec<String>,
197
198 pub skills: Vec<AgentSkill>,
202
203 pub capabilities: AgentCapabilities,
205
206 #[serde(skip_serializing_if = "Option::is_none")]
208 pub provider: Option<AgentProvider>,
209
210 #[serde(skip_serializing_if = "Option::is_none")]
212 pub icon_url: Option<String>,
213
214 #[serde(skip_serializing_if = "Option::is_none")]
216 pub documentation_url: Option<String>,
217
218 #[serde(skip_serializing_if = "Option::is_none")]
220 pub security_schemes: Option<NamedSecuritySchemes>,
221
222 #[serde(skip_serializing_if = "Option::is_none")]
224 pub security_requirements: Option<Vec<SecurityRequirement>>,
225
226 #[serde(skip_serializing_if = "Option::is_none")]
228 pub signatures: Option<Vec<AgentCardSignature>>,
229}
230
231impl AgentCard {
232 pub const fn validate(&self) -> Result<(), &'static str> {
240 if self.name.is_empty() {
241 return Err("agent card name must not be empty");
242 }
243 if self.supported_interfaces.is_empty() {
244 return Err("agent card must have at least one supported interface");
245 }
246 Ok(())
247 }
248}
249
250#[cfg(test)]
253mod tests {
254 use super::*;
255
256 fn minimal_card() -> AgentCard {
257 AgentCard {
258 url: None,
259 name: "Test Agent".into(),
260 description: "A test agent".into(),
261 version: "1.0.0".into(),
262 supported_interfaces: vec![AgentInterface {
263 url: "https://agent.example.com/rpc".into(),
264 protocol_binding: "JSONRPC".into(),
265 protocol_version: "1.0.0".into(),
266 tenant: None,
267 }],
268 default_input_modes: vec!["text/plain".into()],
269 default_output_modes: vec!["text/plain".into()],
270 skills: vec![AgentSkill {
271 id: "echo".into(),
272 name: "Echo".into(),
273 description: "Echoes input".into(),
274 tags: vec!["echo".into()],
275 examples: None,
276 input_modes: None,
277 output_modes: None,
278 security_requirements: None,
279 }],
280 capabilities: AgentCapabilities::none(),
281 provider: None,
282 icon_url: None,
283 documentation_url: None,
284 security_schemes: None,
285 security_requirements: None,
286 signatures: None,
287 }
288 }
289
290 #[test]
291 fn agent_card_roundtrip() {
292 let card = minimal_card();
293 let json = serde_json::to_string(&card).expect("serialize");
294 assert!(json.contains("\"supportedInterfaces\""));
295 assert!(json.contains("\"protocolBinding\":\"JSONRPC\""));
296 assert!(json.contains("\"protocolVersion\":\"1.0.0\""));
297 assert!(
298 !json.contains("\"preferredTransport\""),
299 "v1.0 removed this field"
300 );
301
302 let back: AgentCard = serde_json::from_str(&json).expect("deserialize");
303 assert_eq!(back.name, "Test Agent");
304 assert_eq!(back.supported_interfaces[0].protocol_binding, "JSONRPC");
305 }
306
307 #[test]
308 fn optional_fields_omitted() {
309 let card = minimal_card();
310 let json = serde_json::to_string(&card).expect("serialize");
311 assert!(!json.contains("\"provider\""), "provider should be absent");
312 assert!(!json.contains("\"iconUrl\""), "iconUrl should be absent");
313 assert!(
314 !json.contains("\"securitySchemes\""),
315 "securitySchemes should be absent"
316 );
317 }
318
319 #[test]
320 fn extended_agent_card_in_capabilities() {
321 let mut card = minimal_card();
322 card.capabilities.extended_agent_card = Some(true);
323 let json = serde_json::to_string(&card).expect("serialize");
324 assert!(json.contains("\"extendedAgentCard\":true"));
325 }
326
327 #[test]
328 fn wire_format_security_requirements_field_name() {
329 use crate::security::{SecurityRequirement, StringList};
330 use std::collections::HashMap;
331
332 let mut card = minimal_card();
333 card.security_requirements = Some(vec![SecurityRequirement {
334 schemes: HashMap::from([("bearer".into(), StringList { list: vec![] })]),
335 }]);
336 let json = serde_json::to_string(&card).unwrap();
337 assert!(
339 json.contains("\"securityRequirements\""),
340 "field must be securityRequirements: {json}"
341 );
342 assert!(
343 !json.contains("\"security\":"),
344 "must not have bare 'security' field: {json}"
345 );
346 }
347
348 #[test]
349 fn wire_format_skill_security_requirements() {
350 use crate::security::{SecurityRequirement, StringList};
351 use std::collections::HashMap;
352
353 let skill = AgentSkill {
354 id: "s1".into(),
355 name: "Skill".into(),
356 description: "A skill".into(),
357 tags: vec![],
358 examples: None,
359 input_modes: None,
360 output_modes: None,
361 security_requirements: Some(vec![SecurityRequirement {
362 schemes: HashMap::from([(
363 "oauth2".into(),
364 StringList {
365 list: vec!["read".into()],
366 },
367 )]),
368 }]),
369 };
370 let json = serde_json::to_string(&skill).unwrap();
371 assert!(
372 json.contains("\"securityRequirements\""),
373 "skill must use securityRequirements: {json}"
374 );
375 }
376
377 #[test]
378 fn wire_format_capabilities_no_state_transition_history() {
379 let card = minimal_card();
380 let json = serde_json::to_string(&card).unwrap();
381 assert!(
382 !json.contains("stateTransitionHistory"),
383 "stateTransitionHistory must not appear: {json}"
384 );
385 }
386
387 #[test]
390 fn capabilities_none_all_fields_unset() {
391 let caps = AgentCapabilities::none();
392 assert!(caps.streaming.is_none());
393 assert!(caps.push_notifications.is_none());
394 assert!(caps.extended_agent_card.is_none());
395 assert!(caps.extensions.is_none());
396 }
397
398 #[test]
399 fn capabilities_default_equals_none() {
400 let def = AgentCapabilities::default();
401 let none = AgentCapabilities::none();
402 assert_eq!(def.streaming, none.streaming);
403 assert_eq!(def.push_notifications, none.push_notifications);
404 assert_eq!(def.extended_agent_card, none.extended_agent_card);
405 }
406
407 #[test]
408 fn capabilities_with_streaming_sets_field() {
409 let caps = AgentCapabilities::none().with_streaming(true);
410 assert_eq!(caps.streaming, Some(true));
411 assert!(caps.push_notifications.is_none());
412 assert!(caps.extended_agent_card.is_none());
413
414 let caps = AgentCapabilities::none().with_streaming(false);
415 assert_eq!(caps.streaming, Some(false));
416 }
417
418 #[test]
419 fn capabilities_with_push_notifications_sets_field() {
420 let caps = AgentCapabilities::none().with_push_notifications(true);
421 assert_eq!(caps.push_notifications, Some(true));
422 assert!(caps.streaming.is_none());
423 assert!(caps.extended_agent_card.is_none());
424
425 let caps = AgentCapabilities::none().with_push_notifications(false);
426 assert_eq!(caps.push_notifications, Some(false));
427 }
428
429 #[test]
430 fn capabilities_with_extended_agent_card_sets_field() {
431 let caps = AgentCapabilities::none().with_extended_agent_card(true);
432 assert_eq!(caps.extended_agent_card, Some(true));
433 assert!(caps.streaming.is_none());
434 assert!(caps.push_notifications.is_none());
435
436 let caps = AgentCapabilities::none().with_extended_agent_card(false);
437 assert_eq!(caps.extended_agent_card, Some(false));
438 }
439
440 #[test]
441 fn capabilities_builder_chaining() {
442 let caps = AgentCapabilities::none()
443 .with_streaming(true)
444 .with_push_notifications(false)
445 .with_extended_agent_card(true);
446 assert_eq!(caps.streaming, Some(true));
447 assert_eq!(caps.push_notifications, Some(false));
448 assert_eq!(caps.extended_agent_card, Some(true));
449 }
450
451 #[test]
454 fn validate_minimal_card_ok() {
455 let card = minimal_card();
456 assert!(card.validate().is_ok());
457 }
458
459 #[test]
460 fn validate_empty_name_returns_error() {
461 let mut card = minimal_card();
462 card.name = String::new();
463 let err = card.validate().unwrap_err();
464 assert!(err.contains("name"), "error should mention name: {err}");
465 }
466
467 #[test]
468 fn validate_empty_supported_interfaces_returns_error() {
469 let mut card = minimal_card();
470 card.supported_interfaces = vec![];
471 let err = card.validate().unwrap_err();
472 assert!(
473 err.contains("supported interface"),
474 "error should mention supported interface: {err}"
475 );
476 }
477}