Skip to main content

systemprompt_agent/models/web/
update_agent.rs

1use crate::models::a2a::AgentCard;
2use serde::{Deserialize, Serialize};
3
4use super::card_input::AgentCardInput;
5use super::validation::{extract_port_from_url, is_valid_version, list_available_mcp_servers};
6
7#[derive(Debug, Clone, Deserialize)]
8pub struct UpdateAgentRequestRaw {
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 UpdateAgentRequest {
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 UpdateAgentRequest {
24    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
25    where
26        D: serde::Deserializer<'de>,
27    {
28        let raw = UpdateAgentRequestRaw::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 UpdateAgentRequest {
74    pub fn from_raw(raw: UpdateAgentRequestRaw, 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.trim().is_empty() {
123            return Err("Endpoint is required".to_string());
124        }
125
126        if !self.card.url.starts_with("http://") && !self.card.url.starts_with("https://") {
127            return Err("Endpoint must be a valid HTTP or HTTPS URL".to_string());
128        }
129
130        if !is_valid_version(&self.card.version) {
131            return Err("Version must be in semantic version format (e.g., 1.0.0)".to_string());
132        }
133
134        if let Some(ref mcp_servers) = self.mcp_servers {
135            if !mcp_servers.is_empty() {
136                let available_servers = list_available_mcp_servers().await?;
137                let mut invalid_servers = Vec::new();
138
139                for server in mcp_servers {
140                    if !available_servers.contains(server) {
141                        invalid_servers.push(server.clone());
142                    }
143                }
144
145                if !invalid_servers.is_empty() {
146                    return Err(format!(
147                        "Invalid MCP server(s): {}. Available servers: {}",
148                        invalid_servers.join(", "),
149                        if available_servers.is_empty() {
150                            "(none)".to_string()
151                        } else {
152                            available_servers.join(", ")
153                        }
154                    ));
155                }
156            }
157        }
158
159        Ok(())
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        extract_port_from_url(&self.card.url).unwrap_or(80)
168    }
169}