use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AgentCard {
pub name: String,
pub description: String,
pub version: String,
pub skills: Vec<Skill>,
pub authentication: AuthConfig,
pub provider: ProviderInfo,
#[serde(skip_serializing_if = "Option::is_none")]
pub docs_url: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct Skill {
pub id: String,
pub name: String,
pub description: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub input_schema: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub output_schema: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct AuthConfig {
pub schemes: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub token_endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub oidc_discovery: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ProviderInfo {
pub organization: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub contact: Option<String>,
}
impl AgentCard {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
description: String::new(),
version: "1.0".to_string(),
skills: Vec::new(),
authentication: AuthConfig::default(),
provider: ProviderInfo {
organization: "VEX".to_string(),
contact: None,
},
docs_url: None,
}
}
pub fn with_description(mut self, desc: impl Into<String>) -> Self {
self.description = desc.into();
self
}
pub fn with_skill(mut self, id: impl Into<String>, description: impl Into<String>) -> Self {
let id_str = id.into();
self.skills.push(Skill {
id: id_str.clone(),
name: id_str,
description: description.into(),
input_schema: None,
output_schema: None,
});
self
}
pub fn with_skill_full(mut self, skill: Skill) -> Self {
self.skills.push(skill);
self
}
pub fn with_docs(mut self, url: impl Into<String>) -> Self {
self.docs_url = Some(url.into());
self
}
pub fn with_auth(mut self, auth: AuthConfig) -> Self {
self.authentication = auth;
self
}
pub fn vex_default() -> Self {
Self::new("vex-agent")
.with_description(
"VEX Protocol agent with adversarial verification and cryptographic proofs",
)
.with_skill("verify", "Verify a claim using adversarial red/blue debate")
.with_skill("hash", "Compute SHA-256 hash of content")
.with_skill("merkle_root", "Get current Merkle root for audit chain")
.with_docs("https://provnai.dev/docs")
.with_auth(AuthConfig {
schemes: vec!["bearer".to_string(), "api_key".to_string()],
token_endpoint: None,
oidc_discovery: None,
})
}
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
schemes: vec!["bearer".to_string()],
token_endpoint: None,
oidc_discovery: None,
}
}
}
impl Skill {
pub fn new(
id: impl Into<String>,
name: impl Into<String>,
description: impl Into<String>,
) -> Self {
Self {
id: id.into(),
name: name.into(),
description: description.into(),
input_schema: None,
output_schema: None,
}
}
pub fn with_input_schema(mut self, schema: serde_json::Value) -> Self {
self.input_schema = Some(schema);
self
}
pub fn with_output_schema(mut self, schema: serde_json::Value) -> Self {
self.output_schema = Some(schema);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_agent_card_new() {
let card = AgentCard::new("test-agent");
assert_eq!(card.name, "test-agent");
assert_eq!(card.version, "1.0");
assert!(card.skills.is_empty());
}
#[test]
fn test_agent_card_builder() {
let card = AgentCard::new("vex")
.with_description("VEX verifier")
.with_skill("verify", "Verify claims");
assert_eq!(card.description, "VEX verifier");
assert_eq!(card.skills.len(), 1);
assert_eq!(card.skills[0].id, "verify");
}
#[test]
fn test_vex_default() {
let card = AgentCard::vex_default();
assert_eq!(card.name, "vex-agent");
assert!(card.skills.len() >= 3);
assert!(card.docs_url.is_some());
}
#[test]
fn test_serialization() {
let card = AgentCard::vex_default();
let json = serde_json::to_string_pretty(&card).unwrap();
assert!(json.contains("vex-agent"));
assert!(json.contains("verify"));
}
}