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