1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use url::Url;
8use uuid::Uuid;
9
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
12#[serde(rename_all = "camelCase")]
13pub struct AgentScope {
14 pub scope_type: String,
15 pub scope_id: Uuid,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
23pub struct AgentCard {
24 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub id: Option<Uuid>,
27
28 #[serde(
30 rename = "organizationId",
31 default,
32 skip_serializing_if = "Option::is_none"
33 )]
34 pub organization_id: Option<Uuid>,
35
36 pub name: String,
38
39 pub description: String,
41
42 pub capabilities: AgentCapabilities,
44
45 #[serde(skip_serializing_if = "Option::is_none")]
47 pub authentication: Option<Vec<SecurityScheme>>,
48
49 pub endpoints: HashMap<String, EndpointConfig>,
51
52 #[serde(skip_serializing_if = "Option::is_none")]
54 pub version: Option<String>,
55
56 #[serde(rename = "documentationUrl", skip_serializing_if = "Option::is_none")]
58 pub documentation_url: Option<String>,
59
60 #[serde(default, skip_serializing_if = "Option::is_none")]
62 pub scopes: Option<Vec<AgentScope>>,
63}
64
65impl AgentCard {
66 pub fn new(
68 name: impl Into<String>,
69 description: impl Into<String>,
70 capabilities: AgentCapabilities,
71 ) -> Self {
72 Self {
73 id: None,
74 organization_id: None,
75 name: name.into(),
76 description: description.into(),
77 capabilities,
78 authentication: None,
79 endpoints: HashMap::new(),
80 version: None,
81 documentation_url: None,
82 scopes: None,
83 }
84 }
85
86 pub fn with_endpoint(mut self, name: impl Into<String>, config: EndpointConfig) -> Self {
88 self.endpoints.insert(name.into(), config);
89 self
90 }
91
92 pub fn with_authentication(mut self, schemes: Vec<SecurityScheme>) -> Self {
94 self.authentication = Some(schemes);
95 self
96 }
97
98 pub fn with_version(mut self, version: impl Into<String>) -> Self {
100 self.version = Some(version.into());
101 self
102 }
103}
104
105#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
107pub struct AgentCapabilities {
108 #[serde(default)]
110 pub streaming: bool,
111
112 #[serde(rename = "pushNotifications", default)]
114 pub push_notifications: bool,
115
116 #[serde(rename = "taskManagement", default)]
118 pub task_management: bool,
119
120 #[serde(rename = "multiTurn", default)]
122 pub multi_turn: bool,
123
124 #[serde(rename = "supportedPartTypes", skip_serializing_if = "Option::is_none")]
126 pub supported_part_types: Option<Vec<String>>,
127}
128
129impl AgentCapabilities {
130 pub fn new() -> Self {
132 Self {
133 streaming: false,
134 push_notifications: false,
135 task_management: false,
136 multi_turn: false,
137 supported_part_types: None,
138 }
139 }
140
141 pub fn with_streaming(mut self) -> Self {
143 self.streaming = true;
144 self
145 }
146
147 pub fn with_push_notifications(mut self) -> Self {
149 self.push_notifications = true;
150 self
151 }
152
153 pub fn with_task_management(mut self) -> Self {
155 self.task_management = true;
156 self
157 }
158
159 pub fn with_multi_turn(mut self) -> Self {
161 self.multi_turn = true;
162 self
163 }
164}
165
166impl Default for AgentCapabilities {
167 fn default() -> Self {
168 Self::new()
169 }
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
174#[serde(rename_all = "camelCase")]
175pub struct ApiKeySecurityScheme {
176 pub description: Option<String>,
177 #[serde(rename = "in")]
178 pub location: String,
179 pub name: String,
180}
181
182#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184#[serde(rename_all = "camelCase")]
185pub struct HttpAuthSecurityScheme {
186 pub description: Option<String>,
187 pub scheme: String,
188 pub bearer_format: Option<String>,
189}
190
191#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
193#[serde(rename_all = "camelCase")]
194pub struct OAuthFlow {
195 pub authorization_url: Option<Url>,
196 pub token_url: Option<Url>,
197 pub refresh_url: Option<Url>,
198 pub scopes: HashMap<String, String>,
199}
200
201#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
203#[serde(rename_all = "camelCase")]
204pub struct OAuthFlows {
205 pub authorization_code: Option<OAuthFlow>,
206 pub client_credentials: Option<OAuthFlow>,
207 pub implicit: Option<OAuthFlow>,
208 pub password: Option<OAuthFlow>,
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
213#[serde(rename_all = "camelCase")]
214pub struct OAuth2SecurityScheme {
215 pub description: Option<String>,
216 pub flows: OAuthFlows,
217 pub oauth2_metadata_url: Option<Url>,
218}
219
220#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
222#[serde(rename_all = "camelCase")]
223pub struct OpenIdConnectSecurityScheme {
224 pub description: Option<String>,
225 pub open_id_connect_url: Url,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
230pub enum SecurityScheme {
231 #[serde(rename = "apiKeySecurityScheme")]
232 ApiKey(ApiKeySecurityScheme),
233 #[serde(rename = "httpAuthSecurityScheme")]
234 HttpAuth(HttpAuthSecurityScheme),
235 #[serde(rename = "oauth2SecurityScheme")]
236 OAuth2(Box<OAuth2SecurityScheme>),
237 #[serde(rename = "openIdConnectSecurityScheme")]
238 OpenIdConnect(OpenIdConnectSecurityScheme),
239}
240
241#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
243pub struct EndpointConfig {
244 pub url: String,
246
247 #[serde(rename = "type")]
249 pub endpoint_type: String,
250
251 #[serde(default)]
253 pub preferred: bool,
254}
255
256impl EndpointConfig {
257 pub fn new(url: impl Into<String>, endpoint_type: impl Into<String>) -> Self {
259 Self {
260 url: url.into(),
261 endpoint_type: endpoint_type.into(),
262 preferred: false,
263 }
264 }
265
266 pub fn preferred(mut self) -> Self {
268 self.preferred = true;
269 self
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 #[test]
278 fn test_agent_card_creation() {
279 let capabilities = AgentCapabilities::new()
280 .with_streaming()
281 .with_task_management();
282
283 let card = AgentCard::new("Test Agent", "A test agent", capabilities)
284 .with_version("1.0.0")
285 .with_endpoint(
286 "http",
287 EndpointConfig::new("https://example.com", "http+json").preferred(),
288 );
289
290 assert_eq!(card.name, "Test Agent");
291 assert!(card.capabilities.streaming);
292 assert!(card.capabilities.task_management);
293 assert_eq!(card.version, Some("1.0.0".to_string()));
294 assert_eq!(card.endpoints.len(), 1);
295 }
296
297 #[test]
298 fn test_agent_capabilities() {
299 let mut caps = AgentCapabilities::default();
300 assert!(!caps.streaming);
301 assert!(!caps.task_management);
302
303 caps = caps.with_streaming().with_multi_turn();
304 assert!(caps.streaming);
305 assert!(caps.multi_turn);
306 }
307
308 #[test]
309 fn test_security_schemes() {
310 let http_auth = SecurityScheme::HttpAuth(HttpAuthSecurityScheme {
311 description: None,
312 scheme: "bearer".to_string(),
313 bearer_format: None,
314 });
315 let api_key = SecurityScheme::ApiKey(ApiKeySecurityScheme {
316 description: None,
317 location: "header".to_string(),
318 name: "X-API-Key".to_string(),
319 });
320
321 assert!(matches!(http_auth, SecurityScheme::HttpAuth(_)));
322 assert!(matches!(api_key, SecurityScheme::ApiKey(_)));
323 }
324
325 #[test]
326 fn test_agent_card_serialization() {
327 let capabilities = AgentCapabilities::new().with_streaming();
328 let card = AgentCard::new("Test", "Description", capabilities);
329
330 let json = serde_json::to_string(&card).unwrap();
331 assert!(json.contains("\"name\":\"Test\""));
332
333 let deserialized: AgentCard = serde_json::from_str(&json).unwrap();
334 assert_eq!(card, deserialized);
335 }
336}