1use std::collections::HashMap;
2
3pub use crate::domain::generated::{
5 APIKeySecurityScheme, AgentCapabilities, AgentCard, AgentCardSignature, AgentExtension,
6 AgentInterface, AgentProvider, AgentSkill, AuthenticationInfo, AuthorizationCodeOAuthFlow,
7 ClientCredentialsOAuthFlow, DeviceCodeOAuthFlow, HTTPAuthSecurityScheme, ImplicitOAuthFlow,
8 MutualTlsSecurityScheme, OAuth2SecurityScheme, OAuthFlows, OpenIdConnectSecurityScheme,
9 PasswordOAuthFlow, SecurityRequirement, SecurityScheme, StringList, o_auth_flows,
10 security_scheme,
11};
12
13pub type PushNotificationAuthenticationInfo = AuthenticationInfo;
14
15pub const PROTOCOL_BINDING_JSONRPC: &str = "JSONRPC";
18
19pub const PROTOCOL_BINDING_CONNECTRPC: &str = "CONNECTRPC";
23
24pub const PROTOCOL_BINDING_HTTP_JSON: &str = "HTTP+JSON";
27
28impl AgentSkill {
29 pub fn new(id: String, name: String, description: String, tags: Vec<String>) -> Self {
31 Self {
32 id,
33 name,
34 description,
35 tags,
36 ..Default::default()
37 }
38 }
39
40 pub fn with_examples(mut self, examples: Vec<String>) -> Self {
42 self.examples = examples;
43 self
44 }
45
46 pub fn with_input_modes(mut self, input_modes: Vec<String>) -> Self {
48 self.input_modes = input_modes;
49 self
50 }
51
52 pub fn with_output_modes(mut self, output_modes: Vec<String>) -> Self {
54 self.output_modes = output_modes;
55 self
56 }
57
58 pub fn with_security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
60 self.security_requirements = security
61 .into_iter()
62 .map(|req| {
63 let schemes = req
64 .into_iter()
65 .map(|(k, v)| {
66 (
67 k,
68 StringList {
69 list: v,
70 ..Default::default()
71 },
72 )
73 })
74 .collect();
75 SecurityRequirement {
76 schemes,
77 ..Default::default()
78 }
79 })
80 .collect();
81 self
82 }
83
84 #[allow(clippy::too_many_arguments)]
86 pub fn comprehensive(
87 id: String,
88 name: String,
89 description: String,
90 tags: Vec<String>,
91 examples: Option<Vec<String>>,
92 input_modes: Option<Vec<String>>,
93 output_modes: Option<Vec<String>>,
94 security: Option<Vec<HashMap<String, Vec<String>>>>,
95 ) -> Self {
96 let mut skill = Self::new(id, name, description, tags);
97 if let Some(ex) = examples {
98 skill = skill.with_examples(ex);
99 }
100 if let Some(im) = input_modes {
101 skill = skill.with_input_modes(im);
102 }
103 if let Some(om) = output_modes {
104 skill = skill.with_output_modes(om);
105 }
106 if let Some(sec) = security {
107 skill = skill.with_security(sec);
108 }
109 skill
110 }
111}
112
113impl SecurityScheme {
114 pub fn api_key(name: String, location: String, description: Option<String>) -> Self {
115 Self {
116 scheme: Some(security_scheme::Scheme::ApiKeySecurityScheme(Box::new(
117 APIKeySecurityScheme {
118 name,
119 location,
120 description: description.unwrap_or_default(),
121 ..Default::default()
122 },
123 ))),
124 ..Default::default()
125 }
126 }
127
128 pub fn http(
129 scheme_name: String,
130 bearer_format: Option<String>,
131 description: Option<String>,
132 ) -> Self {
133 Self {
134 scheme: Some(security_scheme::Scheme::HttpAuthSecurityScheme(Box::new(
135 HTTPAuthSecurityScheme {
136 scheme: scheme_name,
137 bearer_format: bearer_format.unwrap_or_default(),
138 description: description.unwrap_or_default(),
139 ..Default::default()
140 },
141 ))),
142 ..Default::default()
143 }
144 }
145
146 pub fn oauth2(
147 flows: OAuthFlows,
148 description: Option<String>,
149 oauth2_metadata_url: Option<String>,
150 ) -> Self {
151 Self {
152 scheme: Some(security_scheme::Scheme::Oauth2SecurityScheme(Box::new(
153 OAuth2SecurityScheme {
154 flows: ::buffa::MessageField::some(flows),
155 description: description.unwrap_or_default(),
156 oauth2_metadata_url: oauth2_metadata_url.unwrap_or_default(),
157 ..Default::default()
158 },
159 ))),
160 ..Default::default()
161 }
162 }
163
164 pub fn open_id_connect(open_id_connect_url: String, description: Option<String>) -> Self {
165 Self {
166 scheme: Some(security_scheme::Scheme::OpenIdConnectSecurityScheme(
167 Box::new(OpenIdConnectSecurityScheme {
168 open_id_connect_url,
169 description: description.unwrap_or_default(),
170 ..Default::default()
171 }),
172 )),
173 ..Default::default()
174 }
175 }
176
177 pub fn mutual_tls(description: Option<String>) -> Self {
178 Self {
179 scheme: Some(security_scheme::Scheme::MtlsSecurityScheme(Box::new(
180 MutualTlsSecurityScheme {
181 description: description.unwrap_or_default(),
182 ..Default::default()
183 },
184 ))),
185 ..Default::default()
186 }
187 }
188}
189
190impl OAuthFlows {
191 pub fn authorization_code(flow: AuthorizationCodeOAuthFlow) -> Self {
192 Self {
193 flow: Some(o_auth_flows::Flow::AuthorizationCode(Box::new(flow))),
194 ..Default::default()
195 }
196 }
197
198 pub fn client_credentials(flow: ClientCredentialsOAuthFlow) -> Self {
199 Self {
200 flow: Some(o_auth_flows::Flow::ClientCredentials(Box::new(flow))),
201 ..Default::default()
202 }
203 }
204
205 pub fn device_code(flow: DeviceCodeOAuthFlow) -> Self {
206 Self {
207 flow: Some(o_auth_flows::Flow::DeviceCode(Box::new(flow))),
208 ..Default::default()
209 }
210 }
211}
212
213impl AgentCapabilities {
214 pub fn streaming(&self) -> bool {
215 self.streaming.unwrap_or(false)
216 }
217
218 pub fn push_notifications(&self) -> bool {
219 self.push_notifications.unwrap_or(false)
220 }
221
222 pub fn extended_agent_card(&self) -> bool {
223 self.extended_agent_card.unwrap_or(false)
224 }
225}
226
227impl AgentCardSignature {
228 pub fn new(
229 protected: String,
230 signature: String,
231 header: Option<::buffa_types::google::protobuf::Struct>,
232 ) -> Self {
233 Self {
234 protected,
235 signature,
236 header: header.into(),
237 ..Default::default()
238 }
239 }
240}
241
242impl AgentCard {
243 pub fn builder() -> AgentCardBuilder {
244 AgentCardBuilder::new()
245 }
246
247 pub fn url(&self) -> &str {
248 self.supported_interfaces
249 .first()
250 .map(|i| i.url.as_str())
251 .unwrap_or("")
252 }
253
254 pub fn protocol_version(&self) -> &str {
255 self.supported_interfaces
256 .first()
257 .map(|i| i.protocol_version.as_str())
258 .unwrap_or("1.0")
259 }
260
261 pub fn preferred_transport(&self) -> &str {
262 self.supported_interfaces
263 .first()
264 .map(|i| i.protocol_binding.as_str())
265 .unwrap_or(PROTOCOL_BINDING_JSONRPC)
266 }
267
268 pub fn supports_extended_agent_card(&self) -> bool {
269 self.capabilities.extended_agent_card.unwrap_or(false)
270 }
271}
272
273pub struct AgentCardBuilder {
274 name: String,
275 description: String,
276 url: String,
277 provider: Option<AgentProvider>,
278 version: String,
279 protocol_version: Option<String>,
280 preferred_transport: Option<String>,
281 supported_interfaces: Vec<AgentInterface>,
282 icon_url: Option<String>,
283 documentation_url: Option<String>,
284 capabilities: Option<AgentCapabilities>,
285 security_schemes: HashMap<String, SecurityScheme>,
286 security_requirements: Vec<SecurityRequirement>,
287 default_input_modes: Vec<String>,
288 default_output_modes: Vec<String>,
289 skills: Vec<AgentSkill>,
290 signatures: Vec<AgentCardSignature>,
291}
292
293impl Default for AgentCardBuilder {
294 fn default() -> Self {
295 Self::new()
296 }
297}
298
299impl AgentCardBuilder {
300 pub fn new() -> Self {
301 Self {
302 name: String::new(),
303 description: String::new(),
304 url: String::new(),
305 provider: None,
306 version: String::new(),
307 protocol_version: None,
308 preferred_transport: None,
309 supported_interfaces: Vec::new(),
310 icon_url: None,
311 documentation_url: None,
312 capabilities: None,
313 security_schemes: HashMap::new(),
314 security_requirements: Vec::new(),
315 default_input_modes: vec!["text".to_string()],
316 default_output_modes: vec!["text".to_string()],
317 skills: Vec::new(),
318 signatures: Vec::new(),
319 }
320 }
321
322 pub fn name(mut self, name: String) -> Self {
323 self.name = name;
324 self
325 }
326
327 pub fn description(mut self, description: String) -> Self {
328 self.description = description;
329 self
330 }
331
332 pub fn url(mut self, url: String) -> Self {
333 self.url = url;
334 self
335 }
336
337 pub fn provider(mut self, provider: AgentProvider) -> Self {
338 self.provider = Some(provider);
339 self
340 }
341
342 pub fn version(mut self, version: String) -> Self {
343 self.version = version;
344 self
345 }
346
347 pub fn protocol_version(mut self, protocol_version: String) -> Self {
348 self.protocol_version = Some(protocol_version);
349 self
350 }
351
352 pub fn preferred_transport(mut self, preferred_transport: String) -> Self {
353 self.preferred_transport = Some(preferred_transport);
354 self
355 }
356
357 pub fn additional_interfaces(mut self, interfaces: Vec<AgentInterface>) -> Self {
358 self.supported_interfaces.extend(interfaces);
359 self
360 }
361
362 pub fn icon_url(mut self, icon_url: String) -> Self {
363 self.icon_url = Some(icon_url);
364 self
365 }
366
367 pub fn documentation_url(mut self, documentation_url: String) -> Self {
368 self.documentation_url = Some(documentation_url);
369 self
370 }
371
372 pub fn capabilities(mut self, capabilities: AgentCapabilities) -> Self {
373 self.capabilities = Some(capabilities);
374 self
375 }
376
377 pub fn security_schemes(mut self, security_schemes: HashMap<String, SecurityScheme>) -> Self {
378 self.security_schemes = security_schemes;
379 self
380 }
381
382 pub fn security(mut self, security: Vec<HashMap<String, Vec<String>>>) -> Self {
383 self.security_requirements = security
384 .into_iter()
385 .map(|req| {
386 let schemes = req
387 .into_iter()
388 .map(|(k, v)| {
389 (
390 k,
391 StringList {
392 list: v,
393 ..Default::default()
394 },
395 )
396 })
397 .collect();
398 SecurityRequirement {
399 schemes,
400 ..Default::default()
401 }
402 })
403 .collect();
404 self
405 }
406
407 pub fn default_input_modes(mut self, default_input_modes: Vec<String>) -> Self {
408 self.default_input_modes = default_input_modes;
409 self
410 }
411
412 pub fn default_output_modes(mut self, default_output_modes: Vec<String>) -> Self {
413 self.default_output_modes = default_output_modes;
414 self
415 }
416
417 pub fn skills(mut self, skills: Vec<AgentSkill>) -> Self {
418 self.skills = skills;
419 self
420 }
421
422 pub fn signatures(mut self, signatures: Vec<AgentCardSignature>) -> Self {
423 self.signatures = signatures;
424 self
425 }
426
427 pub fn supports_extended_agent_card(mut self, val: bool) -> Self {
428 let caps = self
429 .capabilities
430 .get_or_insert_with(AgentCapabilities::default);
431 caps.extended_agent_card = Some(val);
432 self
433 }
434
435 pub fn build(self) -> AgentCard {
436 let mut supported_interfaces = self.supported_interfaces;
437 if !self.url.is_empty() {
439 let primary = AgentInterface {
440 url: self.url,
441 protocol_binding: self
442 .preferred_transport
443 .unwrap_or_else(|| PROTOCOL_BINDING_JSONRPC.to_string()),
444 protocol_version: self.protocol_version.unwrap_or_else(|| "1.0".to_string()),
445 ..Default::default()
446 };
447 supported_interfaces.insert(0, primary);
448 }
449
450 AgentCard {
451 name: self.name,
452 description: self.description,
453 supported_interfaces,
454 provider: self.provider.into(),
455 version: self.version,
456 documentation_url: self.documentation_url,
457 capabilities: self.capabilities.unwrap_or_default().into(),
458 security_schemes: self.security_schemes,
459 security_requirements: self.security_requirements,
460 default_input_modes: self.default_input_modes,
461 default_output_modes: self.default_output_modes,
462 skills: self.skills,
463 signatures: self.signatures,
464 icon_url: self.icon_url,
465 ..Default::default()
466 }
467 }
468}
469
470#[cfg(test)]
471mod tests {
472 use super::*;
473
474 #[test]
475 fn test_security_scheme_api_key_serialization() {
476 let scheme = SecurityScheme::api_key(
477 "X-API-Key".to_string(),
478 "header".to_string(),
479 Some("API Key authentication".to_string()),
480 );
481
482 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
483 assert_eq!(json_value["apiKeySecurityScheme"]["location"], "header");
485 assert_eq!(json_value["apiKeySecurityScheme"]["name"], "X-API-Key");
486 }
487
488 #[test]
489 fn test_security_scheme_http_serialization() {
490 let scheme = SecurityScheme::http(
491 "bearer".to_string(),
492 Some("JWT".to_string()),
493 Some("Bearer token authentication".to_string()),
494 );
495
496 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
497 assert_eq!(json_value["httpAuthSecurityScheme"]["scheme"], "bearer");
498 assert_eq!(json_value["httpAuthSecurityScheme"]["bearerFormat"], "JWT");
499 }
500
501 #[test]
502 fn test_security_scheme_mtls_serialization() {
503 let scheme = SecurityScheme::mutual_tls(Some("Mutual TLS authentication".to_string()));
504
505 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
506 assert_eq!(
507 json_value["mtlsSecurityScheme"]["description"],
508 "Mutual TLS authentication"
509 );
510 }
511
512 #[test]
513 fn test_security_scheme_oauth2_with_metadata() {
514 let flows = OAuthFlows::authorization_code(AuthorizationCodeOAuthFlow {
515 authorization_url: "https://example.com/oauth/authorize".to_string(),
516 token_url: "https://example.com/oauth/token".to_string(),
517 refresh_url: String::new(),
518 scopes: HashMap::new(),
519 ..Default::default()
520 });
521
522 let scheme = SecurityScheme::oauth2(
523 flows,
524 Some("OAuth2 authentication".to_string()),
525 Some("https://example.com/.well-known/oauth-authorization-server".to_string()),
526 );
527
528 let json_value = serde_json::to_value(&scheme).expect("Failed to serialize SecurityScheme");
529 assert_eq!(
530 json_value["oauth2SecurityScheme"]["oauth2MetadataUrl"],
531 "https://example.com/.well-known/oauth-authorization-server"
532 );
533 }
534}