1use crate::SecurityScheme;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
10pub enum TransportProtocol {
11 #[default]
13 #[serde(rename = "JSONRPC")]
14 JsonRpc,
15 #[serde(rename = "GRPC")]
17 Grpc,
18 #[serde(rename = "HTTP+JSON")]
20 HttpJson,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
25pub struct AgentInterface {
26 pub transport: TransportProtocol,
28 pub url: String,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
34pub struct AgentExtension {
35 pub uri: String,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub description: Option<String>,
40 #[serde(skip_serializing_if = "Option::is_none")]
42 pub required: Option<bool>,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub params: Option<HashMap<String, serde_json::Value>>,
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq)]
50pub struct AgentCapabilities {
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub streaming: Option<bool>,
54 #[serde(skip_serializing_if = "Option::is_none", rename = "pushNotifications")]
56 pub push_notifications: Option<bool>,
57 #[serde(
59 skip_serializing_if = "Option::is_none",
60 rename = "stateTransitionHistory"
61 )]
62 pub state_transition_history: Option<bool>,
63 #[serde(skip_serializing_if = "Vec::is_empty", default)]
65 pub extensions: Vec<AgentExtension>,
66}
67
68#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
70pub struct AgentProvider {
71 pub organization: String,
73 pub url: String,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
79pub struct AgentSkill {
80 pub id: String,
82 pub name: String,
84 pub description: String,
86 pub tags: Vec<String>,
88 #[serde(skip_serializing_if = "Vec::is_empty", default)]
90 pub examples: Vec<String>,
91 #[serde(skip_serializing_if = "Vec::is_empty", rename = "inputModes", default)]
93 pub input_modes: Vec<String>,
94 #[serde(skip_serializing_if = "Vec::is_empty", rename = "outputModes", default)]
96 pub output_modes: Vec<String>,
97 #[serde(skip_serializing_if = "Vec::is_empty", default)]
99 pub security: Vec<HashMap<String, Vec<String>>>,
100}
101
102#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
104pub struct AgentCardSignature {
105 #[serde(rename = "protected")]
107 pub protected_header: String,
108 pub signature: String,
110 #[serde(skip_serializing_if = "Option::is_none")]
112 pub header: Option<HashMap<String, serde_json::Value>>,
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
117pub struct AgentCard {
118 pub name: String,
120 pub description: String,
122 pub version: String,
124 #[serde(rename = "protocolVersion", default = "default_protocol_version")]
126 pub protocol_version: String,
127 pub url: String,
129 #[serde(rename = "preferredTransport", default)]
131 pub preferred_transport: TransportProtocol,
132 pub capabilities: AgentCapabilities,
134 #[serde(rename = "defaultInputModes")]
136 pub default_input_modes: Vec<String>,
137 #[serde(rename = "defaultOutputModes")]
139 pub default_output_modes: Vec<String>,
140 pub skills: Vec<AgentSkill>,
142 #[serde(skip_serializing_if = "Option::is_none")]
144 pub provider: Option<AgentProvider>,
145 #[serde(
147 skip_serializing_if = "Vec::is_empty",
148 rename = "additionalInterfaces",
149 default
150 )]
151 pub additional_interfaces: Vec<AgentInterface>,
152 #[serde(skip_serializing_if = "Option::is_none", rename = "documentationUrl")]
154 pub documentation_url: Option<String>,
155 #[serde(skip_serializing_if = "Option::is_none", rename = "iconUrl")]
157 pub icon_url: Option<String>,
158 #[serde(skip_serializing_if = "Vec::is_empty", default)]
160 pub security: Vec<HashMap<String, Vec<String>>>,
161 #[serde(skip_serializing_if = "Option::is_none", rename = "securitySchemes")]
163 pub security_schemes: Option<HashMap<String, SecurityScheme>>,
164 #[serde(skip_serializing_if = "Vec::is_empty", default)]
166 pub signatures: Vec<AgentCardSignature>,
167 #[serde(
169 skip_serializing_if = "Option::is_none",
170 rename = "supportsAuthenticatedExtendedCard"
171 )]
172 pub supports_authenticated_extended_card: Option<bool>,
173}
174
175fn default_protocol_version() -> String {
176 crate::PROTOCOL_VERSION.to_string()
177}
178
179impl AgentCard {
180 pub fn new(
182 name: impl Into<String>,
183 description: impl Into<String>,
184 version: impl Into<String>,
185 url: impl Into<String>,
186 ) -> Self {
187 Self {
188 name: name.into(),
189 description: description.into(),
190 version: version.into(),
191 protocol_version: default_protocol_version(),
192 url: url.into(),
193 preferred_transport: TransportProtocol::default(),
194 capabilities: AgentCapabilities::default(),
195 default_input_modes: vec!["text/plain".to_string()],
196 default_output_modes: vec!["text/plain".to_string()],
197 skills: Vec::new(),
198 provider: None,
199 additional_interfaces: Vec::new(),
200 documentation_url: None,
201 icon_url: None,
202 security: Vec::new(),
203 security_schemes: None,
204 signatures: Vec::new(),
205 supports_authenticated_extended_card: None,
206 }
207 }
208
209 pub fn with_name(mut self, name: impl Into<String>) -> Self {
211 self.name = name.into();
212 self
213 }
214
215 pub fn with_description(mut self, description: impl Into<String>) -> Self {
217 self.description = description.into();
218 self
219 }
220
221 pub fn with_version(mut self, version: impl Into<String>) -> Self {
223 self.version = version.into();
224 self
225 }
226
227 pub fn with_protocol_version(mut self, protocol_version: impl Into<String>) -> Self {
229 self.protocol_version = protocol_version.into();
230 self
231 }
232
233 pub fn with_url(mut self, url: impl Into<String>) -> Self {
235 self.url = url.into();
236 self
237 }
238
239 pub fn with_preferred_transport(mut self, transport: TransportProtocol) -> Self {
241 self.preferred_transport = transport;
242 self
243 }
244
245 pub fn with_capabilities(mut self, capabilities: AgentCapabilities) -> Self {
247 self.capabilities = capabilities;
248 self
249 }
250
251 pub fn with_streaming(mut self, enabled: bool) -> Self {
253 self.capabilities.streaming = Some(enabled);
254 self
255 }
256
257 pub fn with_push_notifications(mut self, enabled: bool) -> Self {
259 self.capabilities.push_notifications = Some(enabled);
260 self
261 }
262
263 pub fn with_state_transition_history(mut self, enabled: bool) -> Self {
265 self.capabilities.state_transition_history = Some(enabled);
266 self
267 }
268
269 pub fn add_extension(mut self, extension: AgentExtension) -> Self {
271 self.capabilities.extensions.push(extension);
272 self
273 }
274
275 pub fn with_default_input_modes(mut self, modes: Vec<String>) -> Self {
277 self.default_input_modes = modes;
278 self
279 }
280
281 pub fn add_input_mode(mut self, mode: impl Into<String>) -> Self {
283 self.default_input_modes.push(mode.into());
284 self
285 }
286
287 pub fn with_default_output_modes(mut self, modes: Vec<String>) -> Self {
289 self.default_output_modes = modes;
290 self
291 }
292
293 pub fn add_output_mode(mut self, mode: impl Into<String>) -> Self {
295 self.default_output_modes.push(mode.into());
296 self
297 }
298
299 pub fn with_skills(mut self, skills: Vec<AgentSkill>) -> Self {
301 self.skills = skills;
302 self
303 }
304
305 pub fn add_skill(mut self, skill: AgentSkill) -> Self {
307 self.skills.push(skill);
308 self
309 }
310
311 pub fn add_skill_with<F>(mut self, id: impl Into<String>, name: impl Into<String>, f: F) -> Self
313 where
314 F: FnOnce(AgentSkill) -> AgentSkill,
315 {
316 let skill = AgentSkill::new(id.into(), name.into());
317 self.skills.push(f(skill));
318 self
319 }
320
321 pub fn with_provider(
323 mut self,
324 organization: impl Into<String>,
325 url: impl Into<String>,
326 ) -> Self {
327 self.provider = Some(AgentProvider {
328 organization: organization.into(),
329 url: url.into(),
330 });
331 self
332 }
333
334 pub fn with_additional_interfaces(mut self, interfaces: Vec<AgentInterface>) -> Self {
336 self.additional_interfaces = interfaces;
337 self
338 }
339
340 pub fn add_interface(mut self, transport: TransportProtocol, url: impl Into<String>) -> Self {
342 self.additional_interfaces.push(AgentInterface {
343 transport,
344 url: url.into(),
345 });
346 self
347 }
348
349 pub fn with_documentation_url(mut self, url: impl Into<String>) -> Self {
351 self.documentation_url = Some(url.into());
352 self
353 }
354
355 pub fn with_icon_url(mut self, url: impl Into<String>) -> Self {
357 self.icon_url = Some(url.into());
358 self
359 }
360
361 pub fn with_security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
363 self.security = security;
364 self
365 }
366
367 pub fn add_security_requirement(mut self, requirement: HashMap<String, Vec<String>>) -> Self {
369 self.security.push(requirement);
370 self
371 }
372
373 pub fn with_security_schemes(
375 mut self,
376 schemes: HashMap<String, crate::SecurityScheme>,
377 ) -> Self {
378 self.security_schemes = Some(schemes);
379 self
380 }
381
382 pub fn with_authenticated_extended_card(mut self, supported: bool) -> Self {
384 self.supports_authenticated_extended_card = Some(supported);
385 self
386 }
387
388 pub fn with_signatures(mut self, signatures: Vec<AgentCardSignature>) -> Self {
390 self.signatures = signatures;
391 self
392 }
393
394 pub fn add_signature(mut self, signature: AgentCardSignature) -> Self {
396 self.signatures.push(signature);
397 self
398 }
399}
400
401impl AgentSkill {
402 pub fn new(id: String, name: String) -> Self {
404 Self {
405 id,
406 name,
407 description: String::new(),
408 tags: Vec::new(),
409 examples: Vec::new(),
410 input_modes: Vec::new(),
411 output_modes: Vec::new(),
412 security: Vec::new(),
413 }
414 }
415
416 pub fn with_description(mut self, description: impl Into<String>) -> Self {
418 self.description = description.into();
419 self
420 }
421
422 pub fn add_tag(mut self, tag: impl Into<String>) -> Self {
424 self.tags.push(tag.into());
425 self
426 }
427
428 pub fn with_tags(mut self, tags: Vec<String>) -> Self {
430 self.tags = tags;
431 self
432 }
433
434 pub fn add_example(mut self, example: impl Into<String>) -> Self {
436 self.examples.push(example.into());
437 self
438 }
439
440 pub fn with_examples(mut self, examples: Vec<String>) -> Self {
442 self.examples = examples;
443 self
444 }
445
446 pub fn add_input_mode(mut self, mode: impl Into<String>) -> Self {
448 self.input_modes.push(mode.into());
449 self
450 }
451
452 pub fn with_input_modes(mut self, modes: Vec<String>) -> Self {
454 self.input_modes = modes;
455 self
456 }
457
458 pub fn add_output_mode(mut self, mode: impl Into<String>) -> Self {
460 self.output_modes.push(mode.into());
461 self
462 }
463
464 pub fn with_output_modes(mut self, modes: Vec<String>) -> Self {
466 self.output_modes = modes;
467 self
468 }
469
470 pub fn add_security(mut self, security: HashMap<String, Vec<String>>) -> Self {
472 self.security.push(security);
473 self
474 }
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 #[test]
482 fn test_agent_card_new() {
483 let card = AgentCard::new(
484 "Test Agent",
485 "A test agent",
486 "1.0.0",
487 "http://localhost:8080",
488 );
489
490 assert_eq!(card.name, "Test Agent");
491 assert_eq!(card.description, "A test agent");
492 assert_eq!(card.version, "1.0.0");
493 assert_eq!(card.url, "http://localhost:8080");
494 }
495
496 #[test]
497 fn test_agent_card_with_capabilities() {
498 let card = AgentCard::new(
499 "Test Agent",
500 "A test agent",
501 "1.0.0",
502 "http://localhost:8080",
503 )
504 .with_streaming(true)
505 .with_push_notifications(true)
506 .with_state_transition_history(false);
507
508 assert_eq!(card.capabilities.streaming, Some(true));
509 assert_eq!(card.capabilities.push_notifications, Some(true));
510 assert_eq!(card.capabilities.state_transition_history, Some(false));
511 }
512
513 #[test]
514 fn test_agent_card_with_skill() {
515 let card = AgentCard::new(
516 "Test Agent",
517 "A test agent",
518 "1.0.0",
519 "http://localhost:8080",
520 )
521 .add_skill_with("skill1", "Test Skill", |s| {
522 s.with_description("A test skill")
523 .add_tag("test")
524 .add_tag("example")
525 .add_example("Example usage")
526 });
527
528 assert_eq!(card.skills.len(), 1);
529 let skill = &card.skills[0];
530 assert_eq!(skill.id, "skill1");
531 assert_eq!(skill.name, "Test Skill");
532 assert_eq!(skill.description, "A test skill");
533 assert_eq!(skill.tags, vec!["test", "example"]);
534 assert_eq!(skill.examples, vec!["Example usage"]);
535 }
536
537 #[test]
538 fn test_agent_card_defaults() {
539 let card = AgentCard::new(
540 "Test Agent",
541 "Test Description",
542 "1.0.0",
543 "http://localhost:8080",
544 );
545
546 assert_eq!(card.default_input_modes, vec!["text/plain"]);
547 assert_eq!(card.default_output_modes, vec!["text/plain"]);
548 }
549
550 #[test]
551 fn test_modify_existing_card() {
552 let card = AgentCard::new(
553 "Original Agent",
554 "Original description",
555 "1.0.0",
556 "http://localhost:8080",
557 )
558 .with_name("Modified Agent")
559 .with_version("2.0.0")
560 .with_streaming(true);
561
562 assert_eq!(card.name, "Modified Agent");
563 assert_eq!(card.description, "Original description");
564 assert_eq!(card.version, "2.0.0");
565 assert_eq!(card.capabilities.streaming, Some(true));
566 }
567}