1use crate::config::Config;
6use anyhow::Result;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct Profile {
13 pub name: String,
14 pub description: String,
15 pub config_overrides: ConfigOverrides,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, Default)]
20pub struct ConfigOverrides {
21 pub max_tokens: Option<usize>,
22 pub temperature: Option<f32>,
23 pub max_iterations: Option<usize>,
24 pub step_timeout_secs: Option<u64>,
25 pub concurrency: Option<ConcurrencyOverrides>,
26 pub agent: Option<AgentOverrides>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct ConcurrencyOverrides {
31 pub max_parallel_requests: Option<usize>,
32 pub timeout_secs: Option<u64>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct AgentOverrides {
37 pub streaming: Option<bool>,
38 pub native_function_calling: Option<bool>,
39 pub enable_thinking: Option<bool>,
40}
41
42pub struct ProfileManager {
44 profiles: HashMap<String, Profile>,
45}
46
47impl ProfileManager {
48 pub fn new() -> Self {
50 let mut profiles = HashMap::new();
51
52 profiles.insert(
54 "architect".to_string(),
55 Profile {
56 name: "Architect".to_string(),
57 description: "Deep thinking, single agent for complex design tasks".to_string(),
58 config_overrides: ConfigOverrides {
59 max_tokens: Some(8192),
60 temperature: Some(0.7),
61 max_iterations: Some(100),
62 step_timeout_secs: Some(900),
63 concurrency: Some(ConcurrencyOverrides {
64 max_parallel_requests: Some(4),
65 timeout_secs: Some(120),
66 }),
67 agent: Some(AgentOverrides {
68 streaming: Some(false),
69 native_function_calling: Some(true),
70 enable_thinking: Some(true),
71 }),
72 },
73 },
74 );
75
76 profiles.insert(
78 "swarm-8".to_string(),
79 Profile {
80 name: "Swarm-8".to_string(),
81 description: "8 concurrent agents for collaborative tasks".to_string(),
82 config_overrides: ConfigOverrides {
83 max_tokens: Some(4096),
84 temperature: Some(0.6),
85 max_iterations: Some(50),
86 step_timeout_secs: Some(600),
87 concurrency: Some(ConcurrencyOverrides {
88 max_parallel_requests: Some(16),
89 timeout_secs: Some(300),
90 }),
91 agent: Some(AgentOverrides {
92 streaming: Some(false),
93 native_function_calling: Some(true),
94 enable_thinking: Some(false),
95 }),
96 },
97 },
98 );
99
100 profiles.insert(
102 "batch-16".to_string(),
103 Profile {
104 name: "Batch-16".to_string(),
105 description: "16 concurrent agents - optimal for 2x RTX 4090".to_string(),
106 config_overrides: ConfigOverrides {
107 max_tokens: Some(4096),
108 temperature: Some(0.6),
109 max_iterations: Some(30),
110 step_timeout_secs: Some(900),
111 concurrency: Some(ConcurrencyOverrides {
112 max_parallel_requests: Some(24),
113 timeout_secs: Some(600),
114 }),
115 agent: Some(AgentOverrides {
116 streaming: Some(false),
117 native_function_calling: Some(true),
118 enable_thinking: Some(false),
119 }),
120 },
121 },
122 );
123
124 profiles.insert(
126 "batch-32".to_string(),
127 Profile {
128 name: "Batch-32".to_string(),
129 description: "Maximum throughput for simple tasks".to_string(),
130 config_overrides: ConfigOverrides {
131 max_tokens: Some(2048),
132 temperature: Some(0.5),
133 max_iterations: Some(10),
134 step_timeout_secs: Some(300),
135 concurrency: Some(ConcurrencyOverrides {
136 max_parallel_requests: Some(32),
137 timeout_secs: Some(120),
138 }),
139 agent: Some(AgentOverrides {
140 streaming: Some(false),
141 native_function_calling: Some(false),
142 enable_thinking: Some(false),
143 }),
144 },
145 },
146 );
147
148 profiles.insert(
150 "visual".to_string(),
151 Profile {
152 name: "Visual".to_string(),
153 description: "Website building with visual validation".to_string(),
154 config_overrides: ConfigOverrides {
155 max_tokens: Some(4096),
156 temperature: Some(0.6),
157 max_iterations: Some(50),
158 step_timeout_secs: Some(600),
159 concurrency: Some(ConcurrencyOverrides {
160 max_parallel_requests: Some(8),
161 timeout_secs: Some(300),
162 }),
163 agent: Some(AgentOverrides {
164 streaming: Some(false),
165 native_function_calling: Some(true),
166 enable_thinking: Some(false),
167 }),
168 },
169 },
170 );
171
172 profiles.insert(
174 "quick".to_string(),
175 Profile {
176 name: "Quick".to_string(),
177 description: "Fast responses, minimal verification".to_string(),
178 config_overrides: ConfigOverrides {
179 max_tokens: Some(2048),
180 temperature: Some(0.5),
181 max_iterations: Some(10),
182 step_timeout_secs: Some(120),
183 concurrency: Some(ConcurrencyOverrides {
184 max_parallel_requests: Some(32),
185 timeout_secs: Some(60),
186 }),
187 agent: Some(AgentOverrides {
188 streaming: Some(false),
189 native_function_calling: Some(false),
190 enable_thinking: Some(false),
191 }),
192 },
193 },
194 );
195
196 Self { profiles }
197 }
198
199 pub fn get(&self, name: &str) -> Option<&Profile> {
201 self.profiles.get(name)
202 }
203
204 pub fn list(&self) -> Vec<&Profile> {
206 self.profiles.values().collect()
207 }
208
209 pub fn apply_profile(&self, config: &mut Config, profile_name: &str) -> Result<()> {
211 let profile = self
212 .get(profile_name)
213 .ok_or_else(|| anyhow::anyhow!("Profile '{}' not found", profile_name))?;
214
215 let o = &profile.config_overrides;
219
220 if let Some(max_tokens) = o.max_tokens {
221 config.max_tokens = max_tokens;
222 }
223 if let Some(temperature) = o.temperature {
224 config.temperature = temperature;
225 }
226 if let Some(max_iterations) = o.max_iterations {
227 config.agent.max_iterations = max_iterations;
228 }
229 if let Some(step_timeout_secs) = o.step_timeout_secs {
230 config.agent.step_timeout_secs = step_timeout_secs;
231 }
232 if let Some(agent) = &o.agent {
233 if let Some(streaming) = agent.streaming {
234 config.agent.streaming = streaming;
235 }
236 if let Some(native_fc) = agent.native_function_calling {
237 config.agent.native_function_calling = native_fc;
238 }
239 }
244 if let Some(concurrency) = &o.concurrency {
245 if let Some(max_parallel) = concurrency.max_parallel_requests {
246 config.concurrency.max_streams = max_parallel;
247 }
248 }
250
251 Ok(())
252 }
253
254 pub fn describe(&self, name: &str) -> Option<String> {
256 self.get(name)
257 .map(|p| format!("{}: {}", p.name, p.description))
258 }
259}
260
261impl Default for ProfileManager {
262 fn default() -> Self {
263 Self::new()
264 }
265}
266
267#[cfg(test)]
268#[path = "../../tests/unit/profiles/mod_test.rs"]
269mod tests;