modular_agent_core/
spec.rs1use std::ops::Not;
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5
6use crate::FnvIndexMap;
7use crate::config::AgentConfigs;
8use crate::definition::AgentConfigSpecs;
9use crate::error::AgentError;
10
11pub type PresetSpecs = FnvIndexMap<String, PresetSpec>;
13
14#[derive(Clone, Debug, Default, Deserialize, Serialize)]
20pub struct PresetSpec {
21 pub agents: Vec<AgentSpec>,
23
24 pub connections: Vec<ConnectionSpec>,
26
27 #[serde(flatten)]
31 pub extensions: FnvIndexMap<String, Value>,
32}
33
34impl PresetSpec {
35 pub fn add_agent(&mut self, agent: AgentSpec) {
37 self.agents.push(agent);
38 }
39
40 pub fn remove_agent(&mut self, agent_id: &str) {
42 self.agents.retain(|agent| agent.id != agent_id);
43 }
44
45 pub fn add_connection(&mut self, connection: ConnectionSpec) {
47 self.connections.push(connection);
48 }
49
50 pub fn remove_connection(&mut self, connection: &ConnectionSpec) -> Option<ConnectionSpec> {
55 let Some(index) = self.connections.iter().position(|c| c == connection) else {
56 return None;
57 };
58 Some(self.connections.remove(index))
59 }
60
61 pub fn to_json(&self) -> Result<String, AgentError> {
63 let json = serde_json::to_string_pretty(self)
64 .map_err(|e| AgentError::SerializationError(e.to_string()))?;
65 Ok(json)
66 }
67
68 pub fn from_json(json_str: &str) -> Result<Self, AgentError> {
70 let preset: PresetSpec = serde_json::from_str(json_str)
71 .map_err(|e| AgentError::SerializationError(e.to_string()))?;
72 Ok(preset)
73 }
74}
75
76#[derive(Debug, Clone, Default, Serialize, Deserialize)]
81pub struct AgentSpec {
82 #[serde(skip_serializing_if = "String::is_empty", default)]
84 pub id: String,
85
86 #[serde(skip_serializing_if = "String::is_empty", default)]
88 pub def_name: String,
89
90 #[serde(skip_serializing_if = "Option::is_none", default)]
92 pub inputs: Option<Vec<String>>,
93
94 #[serde(skip_serializing_if = "Option::is_none", default)]
96 pub outputs: Option<Vec<String>>,
97
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub configs: Option<AgentConfigs>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub config_specs: Option<AgentConfigSpecs>,
105
106 #[serde(default, skip_serializing_if = "<&bool>::not")]
108 pub disabled: bool,
109
110 #[serde(flatten)]
112 pub extensions: FnvIndexMap<String, serde_json::Value>,
113}
114
115impl AgentSpec {
116 pub fn update(&mut self, value: &Value) -> Result<(), AgentError> {
121 let update_map = value
122 .as_object()
123 .ok_or_else(|| AgentError::SerializationError("Expected JSON object".to_string()))?;
124
125 for (k, v) in update_map {
126 match k.as_str() {
127 "id" => {
128 if let Some(id_str) = v.as_str() {
129 self.id = id_str.to_string();
130 }
131 }
132 "def_name" => {
133 if let Some(def_name_str) = v.as_str() {
134 self.def_name = def_name_str.to_string();
135 }
136 }
137 "inputs" => {
138 if let Some(inputs_array) = v.as_array() {
139 self.inputs = Some(
140 inputs_array
141 .iter()
142 .filter_map(|v| v.as_str().map(|s| s.to_string()))
143 .collect(),
144 );
145 }
146 }
147 "outputs" => {
148 if let Some(outputs_array) = v.as_array() {
149 self.outputs = Some(
150 outputs_array
151 .iter()
152 .filter_map(|v| v.as_str().map(|s| s.to_string()))
153 .collect(),
154 );
155 }
156 }
157 "configs" => {
158 let configs: AgentConfigs = serde_json::from_value(v.clone())
159 .map_err(|e| AgentError::SerializationError(e.to_string()))?;
160 self.configs = Some(configs);
161 }
162 "disabled" => {
163 if let Some(disabled_bool) = v.as_bool() {
164 self.disabled = disabled_bool;
165 }
166 }
167 _ => {
168 if v.is_null() {
170 self.extensions.shift_remove(k);
171 } else {
172 self.extensions.insert(k.clone(), v.clone());
173 }
174 }
175 }
176 }
177
178 Ok(())
179 }
180}
181
182#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize)]
187pub struct ConnectionSpec {
188 pub source: String,
190
191 pub source_handle: String,
193
194 pub target: String,
196
197 pub target_handle: String,
199}