Skip to main content

modular_agent_kit/
preset.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3
4use crate::error::AgentError;
5use crate::id::{new_id, update_ids};
6use crate::mak::MAK;
7use crate::spec::PresetSpec;
8use crate::{AgentSpec, ConnectionSpec};
9
10pub struct Preset {
11    id: String,
12
13    name: Option<String>,
14
15    running: bool,
16
17    spec: PresetSpec,
18
19    #[cfg(feature = "file")]
20    dir: Option<String>,
21}
22
23impl Preset {
24    /// Create a new preset with the given spec.
25    ///
26    /// The ids of the given spec, including agents and connections, are changed to new unique ids.
27    pub fn new(mut spec: PresetSpec) -> Self {
28        let (agents, connections) = update_ids(&spec.agents, &spec.connections);
29        spec.agents = agents;
30        spec.connections = connections;
31
32        Self {
33            id: new_id(),
34            name: None,
35            running: false,
36            spec,
37            #[cfg(feature = "file")]
38            dir: None,
39        }
40    }
41
42    pub fn id(&self) -> &str {
43        &self.id
44    }
45
46    pub fn spec(&self) -> &PresetSpec {
47        &self.spec
48    }
49
50    pub fn update_spec(&mut self, value: &Value) -> Result<(), AgentError> {
51        let update_map = value
52            .as_object()
53            .ok_or_else(|| AgentError::SerializationError("Expected JSON object".to_string()))?;
54
55        for (k, v) in update_map {
56            match k.as_str() {
57                "agents" => {
58                    // just ignore
59                }
60                "connections" => {
61                    // just ignore
62                }
63                _ => {
64                    // Update extensions
65                    self.spec.extensions.insert(k.clone(), v.clone());
66                }
67            }
68        }
69        Ok(())
70    }
71
72    pub fn running(&self) -> bool {
73        self.running
74    }
75
76    pub fn name(&self) -> Option<&str> {
77        self.name.as_deref()
78    }
79
80    pub fn set_name(&mut self, name: String) {
81        self.name = Some(name);
82    }
83
84    pub fn clear_name(&mut self) {
85        self.name = None;
86    }
87
88    #[cfg(feature = "file")]
89    pub fn dir(&self) -> Option<&str> {
90        self.dir.as_deref()
91    }
92
93    #[cfg(feature = "file")]
94    pub fn set_dir(&mut self, dir: String) {
95        self.dir = Some(dir);
96    }
97
98    #[cfg(feature = "file")]
99    pub fn clear_dir(&mut self) {
100        self.dir = None;
101    }
102
103    pub fn add_agent(&mut self, agent: AgentSpec) {
104        self.spec.add_agent(agent);
105    }
106
107    pub fn remove_agent(&mut self, agent_id: &str) {
108        self.spec.remove_agent(agent_id);
109    }
110
111    pub fn add_connection(&mut self, connection: ConnectionSpec) {
112        self.spec.add_connection(connection);
113    }
114
115    pub fn remove_connection(&mut self, connection: &ConnectionSpec) -> Option<ConnectionSpec> {
116        self.spec.remove_connection(connection)
117    }
118
119    pub async fn start(&mut self, mak: &MAK) -> Result<(), AgentError> {
120        if self.running {
121            // Already running
122            return Ok(());
123        }
124        self.running = true;
125
126        for agent in self.spec.agents.iter() {
127            if agent.disabled {
128                continue;
129            }
130            mak.start_agent(&agent.id).await.unwrap_or_else(|e| {
131                log::error!("Failed to start agent {}: {}", agent.id, e);
132            });
133        }
134
135        Ok(())
136    }
137
138    pub async fn stop(&mut self, mak: &MAK) -> Result<(), AgentError> {
139        for agent in self.spec.agents.iter() {
140            mak.stop_agent(&agent.id).await.unwrap_or_else(|e| {
141                log::error!("Failed to stop agent {}: {}", agent.id, e);
142            });
143        }
144        self.running = false;
145        Ok(())
146    }
147}
148
149#[derive(Clone, Debug, Serialize, Deserialize)]
150pub struct PresetInfo {
151    pub id: String,
152    pub name: Option<String>,
153    pub running: bool,
154    #[cfg(feature = "file")]
155    pub dir: Option<String>,
156}
157
158impl From<&Preset> for PresetInfo {
159    fn from(preset: &Preset) -> Self {
160        Self {
161            id: preset.id.clone(),
162            name: preset.name.clone(),
163            running: preset.running,
164            #[cfg(feature = "file")]
165            dir: preset.dir.clone(),
166        }
167    }
168}