use crate::models::a2a::{AgentCapabilities, AgentCard, AgentInterface, TransportProtocol};
use serde::{Deserialize, Serialize};
use super::card_input::AgentCardInput;
use super::validation::{is_valid_version, list_available_mcp_servers};
#[derive(Debug, Clone, Deserialize)]
pub struct CreateAgentRequestRaw {
pub card: AgentCardInput,
pub is_active: Option<bool>,
pub system_prompt: Option<String>,
pub mcp_servers: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CreateAgentRequest {
pub card: AgentCard,
pub is_active: Option<bool>,
pub system_prompt: Option<String>,
pub mcp_servers: Option<Vec<String>>,
}
impl<'de> Deserialize<'de> for CreateAgentRequest {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let raw = CreateAgentRequestRaw::deserialize(deserializer)?;
let url = raw
.card
.url
.unwrap_or_else(|| format!("http://placeholder/api/v1/agents/{}", raw.card.name));
let card = AgentCard {
name: raw.card.name,
description: raw.card.description,
supported_interfaces: vec![AgentInterface {
url,
protocol_binding: raw
.card
.preferred_transport
.unwrap_or(TransportProtocol::JsonRpc),
protocol_version: raw.card.protocol_version,
}],
version: raw.card.version,
icon_url: None,
provider: None,
documentation_url: None,
capabilities: raw.card.capabilities.normalize(),
security_schemes: raw.card.security_schemes,
security: raw.card.security,
default_input_modes: if raw.card.default_input_modes.is_empty() {
vec!["text/plain".to_string()]
} else {
raw.card.default_input_modes
},
default_output_modes: if raw.card.default_output_modes.is_empty() {
vec!["text/plain".to_string()]
} else {
raw.card.default_output_modes
},
skills: raw.card.skills,
supports_authenticated_extended_card: None,
signatures: None,
};
Ok(Self {
card,
is_active: raw.is_active,
system_prompt: raw.system_prompt,
mcp_servers: raw.mcp_servers,
})
}
}
impl CreateAgentRequest {
pub fn from_raw(raw: CreateAgentRequestRaw, api_server_url: &str) -> Self {
let url = raw
.card
.url
.unwrap_or_else(|| format!("{}/api/v1/agents/{}", api_server_url, raw.card.name));
let card = AgentCard {
name: raw.card.name,
description: raw.card.description,
supported_interfaces: vec![AgentInterface {
url,
protocol_binding: raw
.card
.preferred_transport
.unwrap_or(TransportProtocol::JsonRpc),
protocol_version: raw.card.protocol_version,
}],
version: raw.card.version,
icon_url: None,
provider: None,
documentation_url: None,
capabilities: raw.card.capabilities.normalize(),
security_schemes: raw.card.security_schemes,
security: raw.card.security,
default_input_modes: if raw.card.default_input_modes.is_empty() {
vec!["text/plain".to_string()]
} else {
raw.card.default_input_modes
},
default_output_modes: if raw.card.default_output_modes.is_empty() {
vec!["text/plain".to_string()]
} else {
raw.card.default_output_modes
},
skills: raw.card.skills,
supports_authenticated_extended_card: None,
signatures: None,
};
Self {
card,
is_active: raw.is_active,
system_prompt: raw.system_prompt,
mcp_servers: raw.mcp_servers,
}
}
pub async fn validate(&self) -> Result<(), String> {
if self.card.name.trim().is_empty() {
return Err("Name is required".to_string());
}
let card_url = self.card.url().unwrap_or("");
if !card_url.starts_with("http://") && !card_url.starts_with("https://") {
return Err("URL must be a valid HTTP or HTTPS URL".to_string());
}
if !is_valid_version(&self.card.version) {
return Err("Version must be in semantic version format (e.g., 1.0.0)".to_string());
}
if let Some(ref mcp_servers) = self.mcp_servers {
if !mcp_servers.is_empty() {
let available_servers = list_available_mcp_servers().await?;
let mut invalid_servers = Vec::new();
for server in mcp_servers {
if !available_servers.contains(server) {
invalid_servers.push(server.clone());
}
}
if !invalid_servers.is_empty() {
return Err(format!(
"Invalid MCP server(s): {}. Available servers: {}",
invalid_servers.join(", "),
if available_servers.is_empty() {
"(none)".to_string()
} else {
available_servers.join(", ")
}
));
}
}
}
Ok(())
}
pub fn get_version(&self) -> String {
self.card.version.clone()
}
pub fn is_active(&self) -> bool {
self.is_active.unwrap_or(true)
}
pub fn extract_port(&self) -> u16 {
self.card
.url()
.and_then(super::validation::extract_port_from_url)
.unwrap_or(80)
}
pub const fn get_capabilities(&self) -> &AgentCapabilities {
&self.card.capabilities
}
}