1use crate::types::{AgentCapabilities, AgentCard, AgentProvider, AgentSkill};
5
6pub struct AgentCardBuilder {
7 name: String,
8 description: String,
9 url: String,
10 version: String,
11 capabilities: AgentCapabilities,
12 skills: Vec<AgentSkill>,
13 provider: Option<AgentProvider>,
14 input_modes: Vec<String>,
15 output_modes: Vec<String>,
16}
17
18impl AgentCardBuilder {
19 #[must_use]
20 pub fn new(
21 name: impl Into<String>,
22 url: impl Into<String>,
23 version: impl Into<String>,
24 ) -> Self {
25 Self {
26 name: name.into(),
27 description: String::new(),
28 url: url.into(),
29 version: version.into(),
30 capabilities: AgentCapabilities::default(),
31 skills: Vec::new(),
32 provider: None,
33 input_modes: Vec::new(),
34 output_modes: Vec::new(),
35 }
36 }
37
38 #[must_use]
39 pub fn description(mut self, desc: impl Into<String>) -> Self {
40 self.description = desc.into();
41 self
42 }
43
44 #[must_use]
45 pub fn streaming(mut self, enabled: bool) -> Self {
46 self.capabilities.streaming = enabled;
47 self
48 }
49
50 #[must_use]
51 pub fn push_notifications(mut self, enabled: bool) -> Self {
52 self.capabilities.push_notifications = enabled;
53 self
54 }
55
56 #[must_use]
57 pub fn skill(mut self, skill: AgentSkill) -> Self {
58 self.skills.push(skill);
59 self
60 }
61
62 #[must_use]
63 pub fn provider(mut self, org: impl Into<String>, url: impl Into<Option<String>>) -> Self {
64 self.provider = Some(AgentProvider {
65 organization: org.into(),
66 url: url.into(),
67 });
68 self
69 }
70
71 #[must_use]
72 pub fn default_input_modes(mut self, modes: Vec<String>) -> Self {
73 self.input_modes = modes;
74 self
75 }
76
77 #[must_use]
78 pub fn default_output_modes(mut self, modes: Vec<String>) -> Self {
79 self.output_modes = modes;
80 self
81 }
82
83 #[must_use]
84 pub fn build(self) -> AgentCard {
85 AgentCard {
86 name: self.name,
87 description: self.description,
88 url: self.url,
89 version: self.version,
90 provider: self.provider,
91 capabilities: self.capabilities,
92 default_input_modes: self.input_modes,
93 default_output_modes: self.output_modes,
94 skills: self.skills,
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn builder_minimal() {
105 let card = AgentCardBuilder::new("agent", "http://localhost", "0.1.0").build();
106 assert_eq!(card.name, "agent");
107 assert_eq!(card.url, "http://localhost");
108 assert_eq!(card.version, "0.1.0");
109 assert!(card.description.is_empty());
110 assert!(!card.capabilities.streaming);
111 assert!(card.skills.is_empty());
112 }
113
114 #[test]
115 fn builder_full() {
116 let card = AgentCardBuilder::new("zeph", "http://localhost:8080", "0.5.0")
117 .description("AI agent")
118 .streaming(true)
119 .push_notifications(false)
120 .provider("TestOrg", Some("https://test.org".into()))
121 .default_input_modes(vec!["text".into()])
122 .default_output_modes(vec!["text".into()])
123 .skill(AgentSkill {
124 id: "s1".into(),
125 name: "Skill One".into(),
126 description: "Does things".into(),
127 tags: vec!["test".into()],
128 examples: vec![],
129 input_modes: vec![],
130 output_modes: vec![],
131 })
132 .build();
133
134 assert_eq!(card.description, "AI agent");
135 assert!(card.capabilities.streaming);
136 assert!(!card.capabilities.push_notifications);
137 assert_eq!(card.provider.as_ref().unwrap().organization, "TestOrg");
138 assert_eq!(
139 card.provider.as_ref().unwrap().url.as_deref(),
140 Some("https://test.org")
141 );
142 assert_eq!(card.default_input_modes, vec!["text"]);
143 assert_eq!(card.skills.len(), 1);
144 assert_eq!(card.skills[0].id, "s1");
145 }
146
147 #[test]
148 fn builder_card_serializes() {
149 let card = AgentCardBuilder::new("test", "http://example.com", "1.0.0")
150 .description("test agent")
151 .build();
152 let json = serde_json::to_string(&card).unwrap();
153 assert!(json.contains("\"name\":\"test\""));
154 assert!(json.contains("\"defaultInputModes\"").not());
155 }
156
157 trait Not {
158 fn not(&self) -> bool;
159 }
160 impl Not for bool {
161 fn not(&self) -> bool {
162 !*self
163 }
164 }
165}