1use crate::SecurityScheme;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
10pub enum TransportProtocol {
11 #[serde(rename = "JSONRPC")]
13 JsonRpc,
14 #[serde(rename = "GRPC")]
16 Grpc,
17 #[serde(rename = "HTTP+JSON")]
19 HttpJson,
20}
21
22impl Default for TransportProtocol {
23 fn default() -> Self {
24 TransportProtocol::JsonRpc
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct AgentInterface {
31 pub transport: TransportProtocol,
33 pub url: String,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct AgentExtension {
40 pub uri: String,
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub description: Option<String>,
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub required: Option<bool>,
48 #[serde(skip_serializing_if = "Option::is_none")]
50 pub params: Option<HashMap<String, serde_json::Value>>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize, Default)]
55pub struct AgentCapabilities {
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub streaming: Option<bool>,
59 #[serde(skip_serializing_if = "Option::is_none", rename = "pushNotifications")]
61 pub push_notifications: Option<bool>,
62 #[serde(
64 skip_serializing_if = "Option::is_none",
65 rename = "stateTransitionHistory"
66 )]
67 pub state_transition_history: Option<bool>,
68 #[serde(skip_serializing_if = "Vec::is_empty", default)]
70 pub extensions: Vec<AgentExtension>,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct AgentProvider {
76 pub organization: String,
78 pub url: String,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct AgentSkill {
85 pub id: String,
87 pub name: String,
89 pub description: String,
91 pub tags: Vec<String>,
93 #[serde(skip_serializing_if = "Vec::is_empty", default)]
95 pub examples: Vec<String>,
96 #[serde(skip_serializing_if = "Vec::is_empty", rename = "inputModes", default)]
98 pub input_modes: Vec<String>,
99 #[serde(skip_serializing_if = "Vec::is_empty", rename = "outputModes", default)]
101 pub output_modes: Vec<String>,
102 #[serde(skip_serializing_if = "Vec::is_empty", default)]
104 pub security: Vec<HashMap<String, Vec<String>>>,
105}
106
107#[derive(Debug, Clone, Serialize, Deserialize)]
109pub struct AgentCardSignature {
110 #[serde(rename = "protected")]
112 pub protected_header: String,
113 pub signature: String,
115 #[serde(skip_serializing_if = "Option::is_none")]
117 pub header: Option<HashMap<String, serde_json::Value>>,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct AgentCard {
123 pub name: String,
125 pub description: String,
127 pub version: String,
129 #[serde(rename = "protocolVersion", default = "default_protocol_version")]
131 pub protocol_version: String,
132 pub url: String,
134 #[serde(rename = "preferredTransport", default)]
136 pub preferred_transport: TransportProtocol,
137 pub capabilities: AgentCapabilities,
139 #[serde(rename = "defaultInputModes")]
141 pub default_input_modes: Vec<String>,
142 #[serde(rename = "defaultOutputModes")]
144 pub default_output_modes: Vec<String>,
145 pub skills: Vec<AgentSkill>,
147 #[serde(skip_serializing_if = "Option::is_none")]
149 pub provider: Option<AgentProvider>,
150 #[serde(
152 skip_serializing_if = "Vec::is_empty",
153 rename = "additionalInterfaces",
154 default
155 )]
156 pub additional_interfaces: Vec<AgentInterface>,
157 #[serde(skip_serializing_if = "Option::is_none", rename = "documentationUrl")]
159 pub documentation_url: Option<String>,
160 #[serde(skip_serializing_if = "Option::is_none", rename = "iconUrl")]
162 pub icon_url: Option<String>,
163 #[serde(skip_serializing_if = "Vec::is_empty", default)]
165 pub security: Vec<HashMap<String, Vec<String>>>,
166 #[serde(skip_serializing_if = "Option::is_none", rename = "securitySchemes")]
168 pub security_schemes: Option<HashMap<String, SecurityScheme>>,
169 #[serde(skip_serializing_if = "Vec::is_empty", default)]
171 pub signatures: Vec<AgentCardSignature>,
172 #[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 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 pub fn with_name(mut self, name: impl Into<String>) -> Self {
216 self.name = name.into();
217 self
218 }
219
220 pub fn with_description(mut self, description: impl Into<String>) -> Self {
222 self.description = description.into();
223 self
224 }
225
226 pub fn with_version(mut self, version: impl Into<String>) -> Self {
228 self.version = version.into();
229 self
230 }
231
232 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 pub fn with_url(mut self, url: impl Into<String>) -> Self {
240 self.url = url.into();
241 self
242 }
243
244 pub fn with_preferred_transport(mut self, transport: TransportProtocol) -> Self {
246 self.preferred_transport = transport;
247 self
248 }
249
250 pub fn with_capabilities(mut self, capabilities: AgentCapabilities) -> Self {
252 self.capabilities = capabilities;
253 self
254 }
255
256 pub fn with_streaming(mut self, enabled: bool) -> Self {
258 self.capabilities.streaming = Some(enabled);
259 self
260 }
261
262 pub fn with_push_notifications(mut self, enabled: bool) -> Self {
264 self.capabilities.push_notifications = Some(enabled);
265 self
266 }
267
268 pub fn with_state_transition_history(mut self, enabled: bool) -> Self {
270 self.capabilities.state_transition_history = Some(enabled);
271 self
272 }
273
274 pub fn add_extension(mut self, extension: AgentExtension) -> Self {
276 self.capabilities.extensions.push(extension);
277 self
278 }
279
280 pub fn with_default_input_modes(mut self, modes: Vec<String>) -> Self {
282 self.default_input_modes = modes;
283 self
284 }
285
286 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 pub fn with_default_output_modes(mut self, modes: Vec<String>) -> Self {
294 self.default_output_modes = modes;
295 self
296 }
297
298 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 pub fn with_skills(mut self, skills: Vec<AgentSkill>) -> Self {
306 self.skills = skills;
307 self
308 }
309
310 pub fn add_skill(mut self, skill: AgentSkill) -> Self {
312 self.skills.push(skill);
313 self
314 }
315
316 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 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 pub fn with_additional_interfaces(mut self, interfaces: Vec<AgentInterface>) -> Self {
341 self.additional_interfaces = interfaces;
342 self
343 }
344
345 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 pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
356 self.documentation_url = Some(url.into());
357 self
358 }
359
360 pub fn with_icon_url(mut self, url: impl Into<String>) -> Self {
362 self.icon_url = Some(url.into());
363 self
364 }
365
366 pub fn with_security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
368 self.security = security;
369 self
370 }
371
372 pub fn add_security_requirement(mut self, requirement: HashMap<String, Vec<String>>) -> Self {
374 self.security.push(requirement);
375 self
376 }
377
378 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 pub fn with_authenticated_extended_card(mut self, supported: bool) -> Self {
389 self.supports_authenticated_extended_card = Some(supported);
390 self
391 }
392
393 pub fn with_signatures(mut self, signatures: Vec<AgentCardSignature>) -> Self {
395 self.signatures = signatures;
396 self
397 }
398
399 pub fn add_signature(mut self, signature: AgentCardSignature) -> Self {
401 self.signatures.push(signature);
402 self
403 }
404}
405
406impl AgentSkill {
407 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 pub fn with_description(mut self, description: impl Into<String>) -> Self {
423 self.description = description.into();
424 self
425 }
426
427 pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
429 self.tags.push(tag.into());
430 self
431 }
432
433 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
435 self.tags = tags;
436 self
437 }
438
439 pub fn add_example(mut self, example: impl Into<String>) -> Self {
441 self.examples.push(example.into());
442 self
443 }
444
445 pub fn with_examples(mut self, examples: Vec<String>) -> Self {
447 self.examples = examples;
448 self
449 }
450
451 pub fn add_input_mode(mut self, mode: impl Into<String>) -> Self {
453 self.input_modes.push(mode.into());
454 self
455 }
456
457 pub fn with_input_modes(mut self, modes: Vec<String>) -> Self {
459 self.input_modes = modes;
460 self
461 }
462
463 pub fn add_output_mode(mut self, mode: impl Into<String>) -> Self {
465 self.output_modes.push(mode.into());
466 self
467 }
468
469 pub fn with_output_modes(mut self, modes: Vec<String>) -> Self {
471 self.output_modes = modes;
472 self
473 }
474
475 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}