1use std::collections::HashMap;
6use std::path::PathBuf;
7use std::time::Duration;
8
9use crate::output_style::OutputStyle;
10use crate::permissions::PermissionPolicy;
11use crate::tools::ToolAccess;
12
13#[derive(Debug, Clone)]
15pub struct AgentModelConfig {
16 pub primary: String,
18 pub small: String,
20 pub max_tokens: u32,
22}
23
24impl Default for AgentModelConfig {
25 fn default() -> Self {
26 Self {
27 primary: crate::client::DEFAULT_MODEL.to_string(),
28 small: crate::client::DEFAULT_SMALL_MODEL.to_string(),
29 max_tokens: 8192,
30 }
31 }
32}
33
34impl AgentModelConfig {
35 pub fn new(primary: impl Into<String>) -> Self {
36 Self {
37 primary: primary.into(),
38 ..Default::default()
39 }
40 }
41
42 pub fn with_small(mut self, small: impl Into<String>) -> Self {
43 self.small = small.into();
44 self
45 }
46
47 pub fn with_max_tokens(mut self, tokens: u32) -> Self {
48 self.max_tokens = tokens;
49 self
50 }
51}
52
53#[derive(Debug, Clone)]
55pub struct ExecutionConfig {
56 pub max_iterations: usize,
58 pub timeout: Option<Duration>,
60 pub chunk_timeout: Duration,
62 pub auto_compact: bool,
64 pub compact_threshold: f32,
66 pub compact_keep_messages: usize,
68}
69
70impl Default for ExecutionConfig {
71 fn default() -> Self {
72 Self {
73 max_iterations: 100,
74 timeout: Some(Duration::from_secs(300)),
75 chunk_timeout: Duration::from_secs(60),
76 auto_compact: true,
77 compact_threshold: crate::types::DEFAULT_COMPACT_THRESHOLD,
78 compact_keep_messages: 4,
79 }
80 }
81}
82
83impl ExecutionConfig {
84 pub fn with_max_iterations(mut self, max: usize) -> Self {
85 self.max_iterations = max;
86 self
87 }
88
89 pub fn with_timeout(mut self, timeout: Duration) -> Self {
90 self.timeout = Some(timeout);
91 self
92 }
93
94 pub fn without_timeout(mut self) -> Self {
95 self.timeout = None;
96 self
97 }
98
99 pub fn with_chunk_timeout(mut self, timeout: Duration) -> Self {
100 self.chunk_timeout = timeout;
101 self
102 }
103
104 pub fn with_auto_compact(mut self, enabled: bool) -> Self {
105 self.auto_compact = enabled;
106 self
107 }
108
109 pub fn with_compact_threshold(mut self, threshold: f32) -> Self {
110 self.compact_threshold = threshold.clamp(0.0, 1.0);
111 self
112 }
113
114 pub fn with_compact_keep_messages(mut self, count: usize) -> Self {
115 self.compact_keep_messages = count;
116 self
117 }
118}
119
120#[derive(Debug, Clone, Default)]
122pub struct SecurityConfig {
123 pub permission_policy: PermissionPolicy,
125 pub tool_access: ToolAccess,
127 pub env: HashMap<String, String>,
129}
130
131impl SecurityConfig {
132 pub fn permissive() -> Self {
133 Self {
134 permission_policy: PermissionPolicy::permissive(),
135 tool_access: ToolAccess::All,
136 ..Default::default()
137 }
138 }
139
140 pub fn read_only() -> Self {
141 Self {
142 permission_policy: PermissionPolicy::read_only(),
143 tool_access: ToolAccess::only(["Read", "Glob", "Grep", "Task", "TaskOutput"]),
144 ..Default::default()
145 }
146 }
147
148 pub fn with_permission_policy(mut self, policy: PermissionPolicy) -> Self {
149 self.permission_policy = policy;
150 self
151 }
152
153 pub fn with_tool_access(mut self, access: ToolAccess) -> Self {
154 self.tool_access = access;
155 self
156 }
157
158 pub fn with_env(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
159 self.env.insert(key.into(), value.into());
160 self
161 }
162
163 pub fn with_envs(
164 mut self,
165 vars: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>,
166 ) -> Self {
167 for (k, v) in vars {
168 self.env.insert(k.into(), v.into());
169 }
170 self
171 }
172}
173
174#[derive(Debug, Clone, Default)]
176pub struct BudgetConfig {
177 pub max_cost_usd: Option<f64>,
179 pub tenant_id: Option<String>,
181 pub fallback_model: Option<String>,
183}
184
185impl BudgetConfig {
186 pub fn unlimited() -> Self {
187 Self::default()
188 }
189
190 pub fn with_max_cost(mut self, usd: f64) -> Self {
191 self.max_cost_usd = Some(usd);
192 self
193 }
194
195 pub fn with_tenant(mut self, tenant_id: impl Into<String>) -> Self {
196 self.tenant_id = Some(tenant_id.into());
197 self
198 }
199
200 pub fn with_fallback(mut self, model: impl Into<String>) -> Self {
201 self.fallback_model = Some(model.into());
202 self
203 }
204}
205
206#[derive(Debug, Clone, Default)]
208pub struct PromptConfig {
209 pub system_prompt: Option<String>,
211 pub system_prompt_mode: SystemPromptMode,
213 pub output_style: Option<OutputStyle>,
215 pub output_schema: Option<serde_json::Value>,
217}
218
219#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
220pub enum SystemPromptMode {
221 #[default]
223 Replace,
224 Append,
226}
227
228impl PromptConfig {
229 pub fn with_system_prompt(mut self, prompt: impl Into<String>) -> Self {
230 self.system_prompt = Some(prompt.into());
231 self
232 }
233
234 pub fn with_append_mode(mut self) -> Self {
235 self.system_prompt_mode = SystemPromptMode::Append;
236 self
237 }
238
239 pub fn with_output_style(mut self, style: OutputStyle) -> Self {
240 self.output_style = Some(style);
241 self
242 }
243
244 pub fn with_output_schema(mut self, schema: serde_json::Value) -> Self {
245 self.output_schema = Some(schema);
246 self
247 }
248
249 pub fn with_structured_output<T: schemars::JsonSchema>(mut self) -> Self {
250 let schema = schemars::schema_for!(T);
251 self.output_schema = serde_json::to_value(schema).ok();
252 self
253 }
254}
255
256#[derive(Debug, Clone)]
258pub struct CacheConfig {
259 pub enabled: bool,
260 pub system_prompt_cache: bool,
261 pub message_cache: bool,
262}
263
264impl Default for CacheConfig {
265 fn default() -> Self {
266 Self {
267 enabled: true,
268 system_prompt_cache: true,
269 message_cache: true,
270 }
271 }
272}
273
274impl CacheConfig {
275 pub fn disabled() -> Self {
276 Self {
277 enabled: false,
278 system_prompt_cache: false,
279 message_cache: false,
280 }
281 }
282
283 pub fn system_only() -> Self {
284 Self {
285 enabled: true,
286 system_prompt_cache: true,
287 message_cache: false,
288 }
289 }
290
291 pub fn with_system_cache(mut self, enabled: bool) -> Self {
292 self.system_prompt_cache = enabled;
293 self
294 }
295
296 pub fn with_message_cache(mut self, enabled: bool) -> Self {
297 self.message_cache = enabled;
298 self
299 }
300}
301
302#[derive(Debug, Clone, Default)]
307pub struct ServerToolsConfig {
308 pub web_search: Option<crate::types::WebSearchTool>,
309 pub web_fetch: Option<crate::types::WebFetchTool>,
310}
311
312impl ServerToolsConfig {
313 pub fn web_search() -> Self {
314 Self {
315 web_search: Some(crate::types::WebSearchTool::default()),
316 web_fetch: None,
317 }
318 }
319
320 pub fn web_fetch() -> Self {
321 Self {
322 web_search: None,
323 web_fetch: Some(crate::types::WebFetchTool::default()),
324 }
325 }
326
327 pub fn all() -> Self {
328 Self {
329 web_search: Some(crate::types::WebSearchTool::default()),
330 web_fetch: Some(crate::types::WebFetchTool::default()),
331 }
332 }
333
334 pub fn with_web_search(mut self, config: crate::types::WebSearchTool) -> Self {
335 self.web_search = Some(config);
336 self
337 }
338
339 pub fn with_web_fetch(mut self, config: crate::types::WebFetchTool) -> Self {
340 self.web_fetch = Some(config);
341 self
342 }
343}
344
345#[derive(Debug, Clone, Default)]
347pub struct AgentConfig {
348 pub model: AgentModelConfig,
349 pub execution: ExecutionConfig,
350 pub security: SecurityConfig,
351 pub budget: BudgetConfig,
352 pub prompt: PromptConfig,
353 pub cache: CacheConfig,
354 pub working_dir: Option<PathBuf>,
355 pub server_tools: ServerToolsConfig,
356 pub coding_mode: bool,
357}
358
359impl AgentConfig {
360 pub fn new() -> Self {
361 Self::default()
362 }
363
364 pub fn with_model(mut self, config: AgentModelConfig) -> Self {
365 self.model = config;
366 self
367 }
368
369 pub fn with_execution(mut self, config: ExecutionConfig) -> Self {
370 self.execution = config;
371 self
372 }
373
374 pub fn with_security(mut self, config: SecurityConfig) -> Self {
375 self.security = config;
376 self
377 }
378
379 pub fn with_budget(mut self, config: BudgetConfig) -> Self {
380 self.budget = config;
381 self
382 }
383
384 pub fn with_prompt(mut self, config: PromptConfig) -> Self {
385 self.prompt = config;
386 self
387 }
388
389 pub fn with_cache(mut self, config: CacheConfig) -> Self {
390 self.cache = config;
391 self
392 }
393
394 pub fn with_working_dir(mut self, dir: impl Into<PathBuf>) -> Self {
395 self.working_dir = Some(dir.into());
396 self
397 }
398
399 pub fn with_server_tools(mut self, config: ServerToolsConfig) -> Self {
400 self.server_tools = config;
401 self
402 }
403
404 pub fn with_coding_mode(mut self, enabled: bool) -> Self {
405 self.coding_mode = enabled;
406 self
407 }
408}
409
410#[cfg(test)]
411mod tests {
412 use super::*;
413
414 #[test]
415 fn test_model_config() {
416 let config = AgentModelConfig::new("claude-opus-4")
417 .with_small("claude-haiku")
418 .with_max_tokens(4096);
419
420 assert_eq!(config.primary, "claude-opus-4");
421 assert_eq!(config.small, "claude-haiku");
422 assert_eq!(config.max_tokens, 4096);
423 }
424
425 #[test]
426 fn test_execution_config() {
427 let config = ExecutionConfig::default()
428 .with_max_iterations(50)
429 .with_timeout(Duration::from_secs(600))
430 .with_auto_compact(false);
431
432 assert_eq!(config.max_iterations, 50);
433 assert_eq!(config.timeout, Some(Duration::from_secs(600)));
434 assert!(!config.auto_compact);
435 }
436
437 #[test]
438 fn test_security_config() {
439 let config = SecurityConfig::permissive().with_env("API_KEY", "secret");
440
441 assert_eq!(config.env.get("API_KEY"), Some(&"secret".to_string()));
442 }
443
444 #[test]
445 fn test_budget_config() {
446 let config = BudgetConfig::unlimited()
447 .with_max_cost(10.0)
448 .with_tenant("org-123")
449 .with_fallback("claude-haiku");
450
451 assert_eq!(config.max_cost_usd, Some(10.0));
452 assert_eq!(config.tenant_id, Some("org-123".to_string()));
453 assert_eq!(config.fallback_model, Some("claude-haiku".to_string()));
454 }
455
456 #[test]
457 fn test_agent_config() {
458 let config = AgentConfig::new()
459 .with_model(AgentModelConfig::new("claude-opus-4"))
460 .with_budget(BudgetConfig::unlimited().with_max_cost(5.0))
461 .with_working_dir("/project");
462
463 assert_eq!(config.model.primary, "claude-opus-4");
464 assert_eq!(config.budget.max_cost_usd, Some(5.0));
465 assert_eq!(config.working_dir, Some(PathBuf::from("/project")));
466 }
467}