orchestrator_config/config/
agent.rs1use crate::cli_types::AgentEnvEntry;
2use crate::selection::{SelectionStrategy, SelectionWeights};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
10pub struct HealthPolicyConfig {
11 #[serde(default = "default_disease_duration_hours")]
14 pub disease_duration_hours: u64,
15
16 #[serde(default = "default_disease_threshold")]
18 pub disease_threshold: u32,
19
20 #[serde(default = "default_capability_success_threshold")]
22 pub capability_success_threshold: f64,
23}
24
25fn default_disease_duration_hours() -> u64 {
26 5
27}
28
29fn default_disease_threshold() -> u32 {
30 2
31}
32
33fn default_capability_success_threshold() -> f64 {
34 0.5
35}
36
37impl Default for HealthPolicyConfig {
38 fn default() -> Self {
39 Self {
40 disease_duration_hours: default_disease_duration_hours(),
41 disease_threshold: default_disease_threshold(),
42 capability_success_threshold: default_capability_success_threshold(),
43 }
44 }
45}
46
47impl HealthPolicyConfig {
48 pub fn is_default(&self) -> bool {
50 *self == Self::default()
51 }
52}
53
54#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
61#[serde(rename_all = "snake_case")]
62pub enum PromptDelivery {
63 Stdin,
65 File,
67 Env,
69 #[default]
71 Arg,
72}
73
74impl PromptDelivery {
75 pub fn is_default(&self) -> bool {
77 *self == Self::Arg
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize, Default)]
83pub struct AgentMetadata {
84 pub name: String,
86 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub description: Option<String>,
89 pub version: Option<String>,
91 pub cost: Option<u8>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct AgentConfig {
98 #[serde(default)]
99 pub metadata: AgentMetadata,
101 #[serde(default = "default_true")]
104 pub enabled: bool,
105 #[serde(default)]
106 pub capabilities: Vec<String>,
108 #[serde(default)]
110 pub command: String,
111 #[serde(default)]
112 pub selection: AgentSelectionConfig,
114 #[serde(default, skip_serializing_if = "Option::is_none")]
117 pub env: Option<Vec<AgentEnvEntry>>,
118 #[serde(default, skip_serializing_if = "PromptDelivery::is_default")]
120 pub prompt_delivery: PromptDelivery,
121 #[serde(default, skip_serializing_if = "HealthPolicyConfig::is_default")]
123 pub health_policy: HealthPolicyConfig,
124}
125
126fn default_true() -> bool {
127 true
128}
129
130impl AgentConfig {
131 pub fn new() -> Self {
133 Self {
134 metadata: AgentMetadata::default(),
135 enabled: true,
136 capabilities: Vec::new(),
137 command: String::new(),
138 selection: AgentSelectionConfig::default(),
139 env: None,
140 prompt_delivery: PromptDelivery::default(),
141 health_policy: HealthPolicyConfig::default(),
142 }
143 }
144
145 pub fn supports_capability(&self, capability: &str) -> bool {
147 self.capabilities.contains(&capability.to_string())
148 }
149}
150
151impl Default for AgentConfig {
152 fn default() -> Self {
153 Self::new()
154 }
155}
156
157#[derive(Debug, Clone, Serialize, Deserialize, Default)]
159pub struct AgentSelectionConfig {
160 #[serde(default = "default_selection_strategy")]
162 pub strategy: SelectionStrategy,
163 #[serde(default)]
165 pub weights: Option<SelectionWeights>,
166}
167
168fn default_selection_strategy() -> SelectionStrategy {
169 SelectionStrategy::CapabilityAware
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_agent_config_default_and_new() {
178 let cfg = AgentConfig::default();
179 assert!(cfg.capabilities.is_empty());
180 assert!(cfg.command.is_empty());
181 assert_eq!(cfg.metadata.name, "");
182 assert!(cfg.metadata.description.is_none());
183 assert!(cfg.metadata.version.is_none());
184 assert!(cfg.metadata.cost.is_none());
185 assert_eq!(cfg.prompt_delivery, PromptDelivery::Arg);
186
187 let cfg2 = AgentConfig::new();
188 assert!(cfg2.capabilities.is_empty());
189 assert_eq!(cfg2.prompt_delivery, PromptDelivery::Arg);
190 }
191
192 #[test]
193 fn prompt_delivery_default_is_arg() {
194 assert_eq!(PromptDelivery::default(), PromptDelivery::Arg);
195 assert!(PromptDelivery::Arg.is_default());
196 assert!(!PromptDelivery::Stdin.is_default());
197 assert!(!PromptDelivery::File.is_default());
198 assert!(!PromptDelivery::Env.is_default());
199 }
200
201 #[test]
202 fn prompt_delivery_serde_roundtrip() {
203 for (variant, expected_str) in [
204 (PromptDelivery::Stdin, "\"stdin\""),
205 (PromptDelivery::File, "\"file\""),
206 (PromptDelivery::Env, "\"env\""),
207 (PromptDelivery::Arg, "\"arg\""),
208 ] {
209 let json = serde_json::to_string(&variant).unwrap();
210 assert_eq!(json, expected_str);
211 let deserialized: PromptDelivery = serde_json::from_str(&json).unwrap();
212 assert_eq!(deserialized, variant);
213 }
214 }
215
216 #[test]
217 fn prompt_delivery_skip_serializing_default() {
218 let cfg = AgentConfig::new();
219 let json = serde_json::to_string(&cfg).unwrap();
220 assert!(
221 !json.contains("prompt_delivery"),
222 "default Arg should be omitted"
223 );
224
225 let mut cfg2 = AgentConfig::new();
226 cfg2.prompt_delivery = PromptDelivery::Stdin;
227 let json2 = serde_json::to_string(&cfg2).unwrap();
228 assert!(
229 json2.contains("prompt_delivery"),
230 "non-default should be present"
231 );
232 }
233
234 #[test]
235 fn test_agent_supports_capability() {
236 let mut agent = AgentConfig::new();
237 agent.capabilities = vec!["plan".to_string(), "qa".to_string()];
238 assert!(agent.supports_capability("plan"));
239 assert!(agent.supports_capability("qa"));
240 assert!(!agent.supports_capability("fix"));
241 }
242
243 #[test]
244 fn test_agent_command_field() {
245 let mut agent = AgentConfig::new();
246 agent.command = "glmcode -p \"{prompt}\"".to_string();
247 assert!(agent.command.contains("{prompt}"));
248 }
249
250 #[test]
251 fn test_agent_selection_config_default() {
252 let cfg = AgentSelectionConfig::default();
253 assert!(cfg.weights.is_none());
254 }
255}