vex_api/a2a/
agent_card.rs1use serde::{Deserialize, Serialize};
13
14#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
16pub struct AgentCard {
17 pub name: String,
19 pub description: String,
21 pub version: String,
23 pub skills: Vec<Skill>,
25 pub authentication: AuthConfig,
27 pub provider: ProviderInfo,
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub docs_url: Option<String>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
36pub struct Skill {
37 pub id: String,
39 pub name: String,
41 pub description: String,
43 #[serde(skip_serializing_if = "Option::is_none")]
45 pub input_schema: Option<serde_json::Value>,
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub output_schema: Option<serde_json::Value>,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
53pub struct AuthConfig {
54 pub schemes: Vec<String>,
56 #[serde(skip_serializing_if = "Option::is_none")]
58 pub token_endpoint: Option<String>,
59 #[serde(skip_serializing_if = "Option::is_none")]
61 pub oidc_discovery: Option<String>,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
66pub struct ProviderInfo {
67 pub organization: String,
69 #[serde(skip_serializing_if = "Option::is_none")]
71 pub contact: Option<String>,
72}
73
74impl AgentCard {
75 pub fn new(name: impl Into<String>) -> Self {
77 Self {
78 name: name.into(),
79 description: String::new(),
80 version: "1.0".to_string(),
81 skills: Vec::new(),
82 authentication: AuthConfig::default(),
83 provider: ProviderInfo {
84 organization: "VEX".to_string(),
85 contact: None,
86 },
87 docs_url: None,
88 }
89 }
90
91 pub fn with_description(mut self, desc: impl Into<String>) -> Self {
93 self.description = desc.into();
94 self
95 }
96
97 pub fn with_skill(mut self, id: impl Into<String>, description: impl Into<String>) -> Self {
99 let id_str = id.into();
100 self.skills.push(Skill {
101 id: id_str.clone(),
102 name: id_str,
103 description: description.into(),
104 input_schema: None,
105 output_schema: None,
106 });
107 self
108 }
109
110 pub fn with_skill_full(mut self, skill: Skill) -> Self {
112 self.skills.push(skill);
113 self
114 }
115
116 pub fn with_docs(mut self, url: impl Into<String>) -> Self {
118 self.docs_url = Some(url.into());
119 self
120 }
121
122 pub fn with_auth(mut self, auth: AuthConfig) -> Self {
124 self.authentication = auth;
125 self
126 }
127
128 pub fn vex_default() -> Self {
130 Self::new("vex-agent")
131 .with_description(
132 "VEX Protocol agent with adversarial verification and cryptographic proofs",
133 )
134 .with_skill("verify", "Verify a claim using adversarial red/blue debate")
135 .with_skill("hash", "Compute SHA-256 hash of content")
136 .with_skill("merkle_root", "Get current Merkle root for audit chain")
137 .with_docs("https://provnai.dev/docs")
138 .with_auth(AuthConfig {
139 schemes: vec!["bearer".to_string(), "api_key".to_string()],
140 token_endpoint: None,
141 oidc_discovery: None,
142 })
143 }
144}
145
146impl Default for AuthConfig {
147 fn default() -> Self {
148 Self {
149 schemes: vec!["bearer".to_string()],
150 token_endpoint: None,
151 oidc_discovery: None,
152 }
153 }
154}
155
156impl Skill {
157 pub fn new(
159 id: impl Into<String>,
160 name: impl Into<String>,
161 description: impl Into<String>,
162 ) -> Self {
163 Self {
164 id: id.into(),
165 name: name.into(),
166 description: description.into(),
167 input_schema: None,
168 output_schema: None,
169 }
170 }
171
172 pub fn with_input_schema(mut self, schema: serde_json::Value) -> Self {
174 self.input_schema = Some(schema);
175 self
176 }
177
178 pub fn with_output_schema(mut self, schema: serde_json::Value) -> Self {
180 self.output_schema = Some(schema);
181 self
182 }
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_agent_card_new() {
191 let card = AgentCard::new("test-agent");
192 assert_eq!(card.name, "test-agent");
193 assert_eq!(card.version, "1.0");
194 assert!(card.skills.is_empty());
195 }
196
197 #[test]
198 fn test_agent_card_builder() {
199 let card = AgentCard::new("vex")
200 .with_description("VEX verifier")
201 .with_skill("verify", "Verify claims");
202
203 assert_eq!(card.description, "VEX verifier");
204 assert_eq!(card.skills.len(), 1);
205 assert_eq!(card.skills[0].id, "verify");
206 }
207
208 #[test]
209 fn test_vex_default() {
210 let card = AgentCard::vex_default();
211 assert_eq!(card.name, "vex-agent");
212 assert!(card.skills.len() >= 3);
213 assert!(card.docs_url.is_some());
214 }
215
216 #[test]
217 fn test_serialization() {
218 let card = AgentCard::vex_default();
219 let json = serde_json::to_string_pretty(&card).unwrap();
220 assert!(json.contains("vex-agent"));
221 assert!(json.contains("verify"));
222 }
223}