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