systemprompt_agent/models/web/
create_agent.rs1use crate::models::a2a::{AgentCapabilities, AgentCard, 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 protocol_version: raw.card.protocol_version,
37 name: raw.card.name,
38 description: raw.card.description,
39 url,
40 version: raw.card.version,
41 preferred_transport: raw.card.preferred_transport,
42 additional_interfaces: None,
43 icon_url: None,
44 provider: None,
45 documentation_url: None,
46 capabilities: raw.card.capabilities.normalize(),
47 security_schemes: raw.card.security_schemes,
48 security: raw.card.security,
49 default_input_modes: if raw.card.default_input_modes.is_empty() {
50 vec!["text/plain".to_string()]
51 } else {
52 raw.card.default_input_modes
53 },
54 default_output_modes: if raw.card.default_output_modes.is_empty() {
55 vec!["text/plain".to_string()]
56 } else {
57 raw.card.default_output_modes
58 },
59 skills: raw.card.skills,
60 supports_authenticated_extended_card: None,
61 signatures: None,
62 };
63
64 Ok(Self {
65 card,
66 is_active: raw.is_active,
67 system_prompt: raw.system_prompt,
68 mcp_servers: raw.mcp_servers,
69 })
70 }
71}
72
73impl CreateAgentRequest {
74 pub fn from_raw(raw: CreateAgentRequestRaw, api_server_url: &str) -> Self {
75 let url = raw
76 .card
77 .url
78 .unwrap_or_else(|| format!("{}/api/v1/agents/{}", api_server_url, raw.card.name));
79
80 let card = AgentCard {
81 protocol_version: raw.card.protocol_version,
82 name: raw.card.name,
83 description: raw.card.description,
84 url,
85 version: raw.card.version,
86 preferred_transport: raw.card.preferred_transport,
87 additional_interfaces: None,
88 icon_url: None,
89 provider: None,
90 documentation_url: None,
91 capabilities: raw.card.capabilities.normalize(),
92 security_schemes: raw.card.security_schemes,
93 security: raw.card.security,
94 default_input_modes: if raw.card.default_input_modes.is_empty() {
95 vec!["text/plain".to_string()]
96 } else {
97 raw.card.default_input_modes
98 },
99 default_output_modes: if raw.card.default_output_modes.is_empty() {
100 vec!["text/plain".to_string()]
101 } else {
102 raw.card.default_output_modes
103 },
104 skills: raw.card.skills,
105 supports_authenticated_extended_card: None,
106 signatures: None,
107 };
108
109 Self {
110 card,
111 is_active: raw.is_active,
112 system_prompt: raw.system_prompt,
113 mcp_servers: raw.mcp_servers,
114 }
115 }
116
117 pub async fn validate(&self) -> Result<(), String> {
118 if self.card.name.trim().is_empty() {
119 return Err("Name is required".to_string());
120 }
121
122 if !self.card.url.starts_with("http://") && !self.card.url.starts_with("https://") {
123 return Err("URL must be a valid HTTP or HTTPS URL".to_string());
124 }
125
126 if !is_valid_version(&self.card.version) {
127 return Err("Version must be in semantic version format (e.g., 1.0.0)".to_string());
128 }
129
130 if let Some(ref mcp_servers) = self.mcp_servers {
131 if !mcp_servers.is_empty() {
132 let available_servers = list_available_mcp_servers().await?;
133 let mut invalid_servers = Vec::new();
134
135 for server in mcp_servers {
136 if !available_servers.contains(server) {
137 invalid_servers.push(server.clone());
138 }
139 }
140
141 if !invalid_servers.is_empty() {
142 return Err(format!(
143 "Invalid MCP server(s): {}. Available servers: {}",
144 invalid_servers.join(", "),
145 if available_servers.is_empty() {
146 "(none)".to_string()
147 } else {
148 available_servers.join(", ")
149 }
150 ));
151 }
152 }
153 }
154
155 Ok(())
156 }
157
158 pub fn get_version(&self) -> String {
159 self.card.version.clone()
160 }
161
162 pub fn is_active(&self) -> bool {
163 self.is_active.unwrap_or(true)
164 }
165
166 pub fn extract_port(&self) -> u16 {
167 super::validation::extract_port_from_url(&self.card.url).unwrap_or(80)
168 }
169
170 pub const fn get_capabilities(&self) -> &AgentCapabilities {
171 &self.card.capabilities
172 }
173
174 pub const fn get_transport_protocols(&self) -> Option<&TransportProtocol> {
175 self.card.preferred_transport.as_ref()
176 }
177}