Skip to main content

systemprompt_agent/models/web/
create_agent.rs

1use crate::models::a2a::{AgentCapabilities, AgentCard, AgentInterface, TransportProtocol};
2use serde::{Deserialize, Serialize};
3
4use super::card_input::AgentCardInput;
5use super::validation::{is_valid_version, list_available_mcp_servers};
6
7#[derive(Debug, Clone, Deserialize)]
8pub struct CreateAgentRequestRaw {
9    pub card: AgentCardInput,
10    pub is_active: Option<bool>,
11    pub system_prompt: Option<String>,
12    pub mcp_servers: Option<Vec<String>>,
13}
14
15#[derive(Debug, Clone, Serialize)]
16pub struct CreateAgentRequest {
17    pub card: AgentCard,
18    pub is_active: Option<bool>,
19    pub system_prompt: Option<String>,
20    pub mcp_servers: Option<Vec<String>>,
21}
22
23impl<'de> Deserialize<'de> for CreateAgentRequest {
24    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
25    where
26        D: serde::Deserializer<'de>,
27    {
28        let raw = CreateAgentRequestRaw::deserialize(deserializer)?;
29
30        let url = raw
31            .card
32            .url
33            .unwrap_or_else(|| format!("http://placeholder/api/v1/agents/{}", raw.card.name));
34
35        let card = AgentCard {
36            name: raw.card.name,
37            description: raw.card.description,
38            supported_interfaces: vec![AgentInterface {
39                url,
40                protocol_binding: raw
41                    .card
42                    .preferred_transport
43                    .unwrap_or(TransportProtocol::JsonRpc),
44                protocol_version: raw.card.protocol_version,
45            }],
46            version: raw.card.version,
47            icon_url: None,
48            provider: None,
49            documentation_url: None,
50            capabilities: raw.card.capabilities.normalize(),
51            security_schemes: raw.card.security_schemes,
52            security: raw.card.security,
53            default_input_modes: if raw.card.default_input_modes.is_empty() {
54                vec!["text/plain".to_string()]
55            } else {
56                raw.card.default_input_modes
57            },
58            default_output_modes: if raw.card.default_output_modes.is_empty() {
59                vec!["text/plain".to_string()]
60            } else {
61                raw.card.default_output_modes
62            },
63            skills: raw.card.skills,
64            supports_authenticated_extended_card: None,
65            signatures: None,
66        };
67
68        Ok(Self {
69            card,
70            is_active: raw.is_active,
71            system_prompt: raw.system_prompt,
72            mcp_servers: raw.mcp_servers,
73        })
74    }
75}
76
77impl CreateAgentRequest {
78    pub fn from_raw(raw: CreateAgentRequestRaw, api_server_url: &str) -> Self {
79        let url = raw
80            .card
81            .url
82            .unwrap_or_else(|| format!("{}/api/v1/agents/{}", api_server_url, raw.card.name));
83
84        let card = AgentCard {
85            name: raw.card.name,
86            description: raw.card.description,
87            supported_interfaces: vec![AgentInterface {
88                url,
89                protocol_binding: raw
90                    .card
91                    .preferred_transport
92                    .unwrap_or(TransportProtocol::JsonRpc),
93                protocol_version: raw.card.protocol_version,
94            }],
95            version: raw.card.version,
96            icon_url: None,
97            provider: None,
98            documentation_url: None,
99            capabilities: raw.card.capabilities.normalize(),
100            security_schemes: raw.card.security_schemes,
101            security: raw.card.security,
102            default_input_modes: if raw.card.default_input_modes.is_empty() {
103                vec!["text/plain".to_string()]
104            } else {
105                raw.card.default_input_modes
106            },
107            default_output_modes: if raw.card.default_output_modes.is_empty() {
108                vec!["text/plain".to_string()]
109            } else {
110                raw.card.default_output_modes
111            },
112            skills: raw.card.skills,
113            supports_authenticated_extended_card: None,
114            signatures: None,
115        };
116
117        Self {
118            card,
119            is_active: raw.is_active,
120            system_prompt: raw.system_prompt,
121            mcp_servers: raw.mcp_servers,
122        }
123    }
124
125    pub async fn validate(&self) -> Result<(), String> {
126        if self.card.name.trim().is_empty() {
127            return Err("Name is required".to_string());
128        }
129
130        let card_url = self.card.url().unwrap_or("");
131        if !card_url.starts_with("http://") && !card_url.starts_with("https://") {
132            return Err("URL must be a valid HTTP or HTTPS URL".to_string());
133        }
134
135        if !is_valid_version(&self.card.version) {
136            return Err("Version must be in semantic version format (e.g., 1.0.0)".to_string());
137        }
138
139        if let Some(ref mcp_servers) = self.mcp_servers {
140            if !mcp_servers.is_empty() {
141                let available_servers = list_available_mcp_servers().await?;
142                let mut invalid_servers = Vec::new();
143
144                for server in mcp_servers {
145                    if !available_servers.contains(server) {
146                        invalid_servers.push(server.clone());
147                    }
148                }
149
150                if !invalid_servers.is_empty() {
151                    return Err(format!(
152                        "Invalid MCP server(s): {}. Available servers: {}",
153                        invalid_servers.join(", "),
154                        if available_servers.is_empty() {
155                            "(none)".to_string()
156                        } else {
157                            available_servers.join(", ")
158                        }
159                    ));
160                }
161            }
162        }
163
164        Ok(())
165    }
166
167    pub fn get_version(&self) -> String {
168        self.card.version.clone()
169    }
170
171    pub fn is_active(&self) -> bool {
172        self.is_active.unwrap_or(true)
173    }
174
175    pub fn extract_port(&self) -> u16 {
176        self.card
177            .url()
178            .and_then(super::validation::extract_port_from_url)
179            .unwrap_or(80)
180    }
181
182    pub const fn get_capabilities(&self) -> &AgentCapabilities {
183        &self.card.capabilities
184    }
185}