1use crate::constants::{defaults, instructions, llm_generation, project_doc, prompts};
2use crate::types::{ReasoningEffortLevel, UiSurfacePreference, VerbosityLevel};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
7const DEFAULT_MAX_SNAPSHOTS: usize = 50;
8const DEFAULT_MAX_AGE_DAYS: u64 = 30;
9
10#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct AgentConfig {
14 #[serde(default = "default_provider")]
16 pub provider: String,
17
18 #[serde(default = "default_api_key_env")]
20 pub api_key_env: String,
21
22 #[serde(default = "default_model")]
24 pub default_model: String,
25
26 #[serde(default = "default_theme")]
28 pub theme: String,
29
30 #[serde(default = "default_todo_planning_mode")]
32 pub todo_planning_mode: bool,
33
34 #[serde(default)]
36 pub ui_surface: UiSurfacePreference,
37
38 #[serde(default = "default_max_conversation_turns")]
40 pub max_conversation_turns: usize,
41
42 #[serde(default = "default_reasoning_effort")]
45 pub reasoning_effort: ReasoningEffortLevel,
46
47 #[serde(default = "default_verbosity")]
50 pub verbosity: VerbosityLevel,
51
52 #[serde(default = "default_temperature")]
57 pub temperature: f32,
58
59 #[serde(default = "default_max_tokens")]
65 pub max_tokens: u32,
66
67 #[serde(default = "default_refine_temperature")]
71 pub refine_temperature: f32,
72
73 #[serde(default = "default_refine_max_tokens")]
76 pub refine_max_tokens: u32,
77
78 #[serde(default = "default_enable_self_review")]
80 pub enable_self_review: bool,
81
82 #[serde(default = "default_max_review_passes")]
84 pub max_review_passes: usize,
85
86 #[serde(default = "default_refine_prompts_enabled")]
88 pub refine_prompts_enabled: bool,
89
90 #[serde(default = "default_refine_max_passes")]
92 pub refine_prompts_max_passes: usize,
93
94 #[serde(default)]
96 pub refine_prompts_model: String,
97
98 #[serde(default)]
102 pub small_model: AgentSmallModelConfig,
103
104 #[serde(default)]
106 pub onboarding: AgentOnboardingConfig,
107
108 #[serde(default = "default_project_doc_max_bytes")]
110 pub project_doc_max_bytes: usize,
111
112 #[serde(
114 default = "default_instruction_max_bytes",
115 alias = "rule_doc_max_bytes"
116 )]
117 pub instruction_max_bytes: usize,
118
119 #[serde(default, alias = "instruction_paths", alias = "instructions")]
121 pub instruction_files: Vec<String>,
122
123 #[serde(default)]
125 pub custom_prompts: AgentCustomPromptsConfig,
126
127 #[serde(default)]
129 pub custom_api_keys: BTreeMap<String, String>,
130
131 #[serde(default)]
133 pub checkpointing: AgentCheckpointingConfig,
134}
135
136impl Default for AgentConfig {
137 fn default() -> Self {
138 Self {
139 provider: default_provider(),
140 api_key_env: default_api_key_env(),
141 default_model: default_model(),
142 theme: default_theme(),
143 todo_planning_mode: default_todo_planning_mode(),
144 ui_surface: UiSurfacePreference::default(),
145 max_conversation_turns: default_max_conversation_turns(),
146 reasoning_effort: default_reasoning_effort(),
147 verbosity: default_verbosity(),
148 temperature: default_temperature(),
149 max_tokens: default_max_tokens(),
150 refine_temperature: default_refine_temperature(),
151 refine_max_tokens: default_refine_max_tokens(),
152 enable_self_review: default_enable_self_review(),
153 max_review_passes: default_max_review_passes(),
154 refine_prompts_enabled: default_refine_prompts_enabled(),
155 refine_prompts_max_passes: default_refine_max_passes(),
156 refine_prompts_model: String::new(),
157 small_model: AgentSmallModelConfig::default(),
158 onboarding: AgentOnboardingConfig::default(),
159 project_doc_max_bytes: default_project_doc_max_bytes(),
160 instruction_max_bytes: default_instruction_max_bytes(),
161 instruction_files: Vec::new(),
162 custom_prompts: AgentCustomPromptsConfig::default(),
163 custom_api_keys: BTreeMap::new(),
164 checkpointing: AgentCheckpointingConfig::default(),
165 }
166 }
167}
168
169impl AgentConfig {
170 pub fn validate_llm_params(&self) -> Result<(), String> {
172 if !(0.0..=1.0).contains(&self.temperature) {
173 return Err(format!(
174 "temperature must be between 0.0 and 1.0, got {}",
175 self.temperature
176 ));
177 }
178
179 if !(0.0..=1.0).contains(&self.refine_temperature) {
180 return Err(format!(
181 "refine_temperature must be between 0.0 and 1.0, got {}",
182 self.refine_temperature
183 ));
184 }
185
186 if self.max_tokens == 0 {
187 return Err("max_tokens must be greater than 0".to_string());
188 }
189
190 if self.refine_max_tokens == 0 {
191 return Err("refine_max_tokens must be greater than 0".to_string());
192 }
193
194 Ok(())
195 }
196}
197
198fn default_provider() -> String {
199 defaults::DEFAULT_PROVIDER.to_string()
200}
201
202fn default_api_key_env() -> String {
203 defaults::DEFAULT_API_KEY_ENV.to_string()
204}
205fn default_model() -> String {
206 defaults::DEFAULT_MODEL.to_string()
207}
208fn default_theme() -> String {
209 defaults::DEFAULT_THEME.to_string()
210}
211
212fn default_todo_planning_mode() -> bool {
213 true
214}
215fn default_max_conversation_turns() -> usize {
216 150
217}
218fn default_reasoning_effort() -> ReasoningEffortLevel {
219 ReasoningEffortLevel::default()
220}
221
222fn default_verbosity() -> VerbosityLevel {
223 VerbosityLevel::default()
224}
225
226fn default_temperature() -> f32 {
227 llm_generation::DEFAULT_TEMPERATURE
228}
229
230fn default_max_tokens() -> u32 {
231 llm_generation::DEFAULT_MAX_TOKENS
232}
233
234fn default_refine_temperature() -> f32 {
235 llm_generation::DEFAULT_REFINE_TEMPERATURE
236}
237
238fn default_refine_max_tokens() -> u32 {
239 llm_generation::DEFAULT_REFINE_MAX_TOKENS
240}
241
242fn default_enable_self_review() -> bool {
243 false
244}
245
246fn default_max_review_passes() -> usize {
247 1
248}
249
250fn default_refine_prompts_enabled() -> bool {
251 false
252}
253
254fn default_refine_max_passes() -> usize {
255 1
256}
257
258fn default_project_doc_max_bytes() -> usize {
259 project_doc::DEFAULT_MAX_BYTES
260}
261
262fn default_instruction_max_bytes() -> usize {
263 instructions::DEFAULT_MAX_BYTES
264}
265
266#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
267#[derive(Debug, Clone, Deserialize, Serialize)]
268pub struct AgentCustomPromptsConfig {
269 #[serde(default = "default_custom_prompts_enabled")]
271 pub enabled: bool,
272
273 #[serde(default = "default_custom_prompts_directory")]
275 pub directory: String,
276
277 #[serde(default)]
279 pub extra_directories: Vec<String>,
280
281 #[serde(default = "default_custom_prompts_max_file_size_kb")]
283 pub max_file_size_kb: usize,
284}
285
286impl Default for AgentCustomPromptsConfig {
287 fn default() -> Self {
288 Self {
289 enabled: default_custom_prompts_enabled(),
290 directory: default_custom_prompts_directory(),
291 extra_directories: Vec::new(),
292 max_file_size_kb: default_custom_prompts_max_file_size_kb(),
293 }
294 }
295}
296
297fn default_custom_prompts_enabled() -> bool {
298 true
299}
300
301fn default_custom_prompts_directory() -> String {
302 prompts::DEFAULT_CUSTOM_PROMPTS_DIR.to_string()
303}
304
305fn default_custom_prompts_max_file_size_kb() -> usize {
306 prompts::DEFAULT_CUSTOM_PROMPT_MAX_FILE_SIZE_KB
307}
308
309#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
310#[derive(Debug, Clone, Deserialize, Serialize)]
311pub struct AgentCheckpointingConfig {
312 #[serde(default = "default_checkpointing_enabled")]
314 pub enabled: bool,
315
316 #[serde(default)]
318 pub storage_dir: Option<String>,
319
320 #[serde(default = "default_checkpointing_max_snapshots")]
322 pub max_snapshots: usize,
323
324 #[serde(default = "default_checkpointing_max_age_days")]
326 pub max_age_days: Option<u64>,
327}
328
329impl Default for AgentCheckpointingConfig {
330 fn default() -> Self {
331 Self {
332 enabled: default_checkpointing_enabled(),
333 storage_dir: None,
334 max_snapshots: default_checkpointing_max_snapshots(),
335 max_age_days: default_checkpointing_max_age_days(),
336 }
337 }
338}
339
340fn default_checkpointing_enabled() -> bool {
341 DEFAULT_CHECKPOINTS_ENABLED
342}
343
344fn default_checkpointing_max_snapshots() -> usize {
345 DEFAULT_MAX_SNAPSHOTS
346}
347
348fn default_checkpointing_max_age_days() -> Option<u64> {
349 Some(DEFAULT_MAX_AGE_DAYS)
350}
351
352#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
353#[derive(Debug, Clone, Deserialize, Serialize)]
354pub struct AgentOnboardingConfig {
355 #[serde(default = "default_onboarding_enabled")]
357 pub enabled: bool,
358
359 #[serde(default = "default_intro_text")]
361 pub intro_text: String,
362
363 #[serde(default = "default_show_project_overview")]
365 pub include_project_overview: bool,
366
367 #[serde(default = "default_show_language_summary")]
369 pub include_language_summary: bool,
370
371 #[serde(default = "default_show_guideline_highlights")]
373 pub include_guideline_highlights: bool,
374
375 #[serde(default = "default_show_usage_tips_in_welcome")]
377 pub include_usage_tips_in_welcome: bool,
378
379 #[serde(default = "default_show_recommended_actions_in_welcome")]
381 pub include_recommended_actions_in_welcome: bool,
382
383 #[serde(default = "default_guideline_highlight_limit")]
385 pub guideline_highlight_limit: usize,
386
387 #[serde(default = "default_usage_tips")]
389 pub usage_tips: Vec<String>,
390
391 #[serde(default = "default_recommended_actions")]
393 pub recommended_actions: Vec<String>,
394
395 #[serde(default)]
397 pub chat_placeholder: Option<String>,
398}
399
400impl Default for AgentOnboardingConfig {
401 fn default() -> Self {
402 Self {
403 enabled: default_onboarding_enabled(),
404 intro_text: default_intro_text(),
405 include_project_overview: default_show_project_overview(),
406 include_language_summary: default_show_language_summary(),
407 include_guideline_highlights: default_show_guideline_highlights(),
408 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
409 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
410 guideline_highlight_limit: default_guideline_highlight_limit(),
411 usage_tips: default_usage_tips(),
412 recommended_actions: default_recommended_actions(),
413 chat_placeholder: None,
414 }
415 }
416}
417
418fn default_onboarding_enabled() -> bool {
419 true
420}
421
422fn default_intro_text() -> String {
423 "Let's get oriented. I preloaded workspace context so we can move fast.".to_string()
424}
425
426fn default_show_project_overview() -> bool {
427 true
428}
429
430fn default_show_language_summary() -> bool {
431 false
432}
433
434fn default_show_guideline_highlights() -> bool {
435 true
436}
437
438fn default_show_usage_tips_in_welcome() -> bool {
439 false
440}
441
442fn default_show_recommended_actions_in_welcome() -> bool {
443 false
444}
445
446fn default_guideline_highlight_limit() -> usize {
447 3
448}
449
450fn default_usage_tips() -> Vec<String> {
451 vec![
452 "Describe your current coding goal or ask for a quick status overview.".to_string(),
453 "Reference AGENTS.md guidelines when proposing changes.".to_string(),
454 "Draft or refresh your TODO list with update_plan before coding.".to_string(),
455 "Prefer asking for targeted file reads or diffs before editing.".to_string(),
456 ]
457}
458
459fn default_recommended_actions() -> Vec<String> {
460 vec![
461 "Start the session by outlining a 3–6 step TODO plan via update_plan.".to_string(),
462 "Review the highlighted guidelines and share the task you want to tackle.".to_string(),
463 "Ask for a workspace tour if you need more context.".to_string(),
464 ]
465}
466
467#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
477#[derive(Debug, Clone, Deserialize, Serialize)]
478pub struct AgentSmallModelConfig {
479 #[serde(default = "default_small_model_enabled")]
481 pub enabled: bool,
482
483 #[serde(default)]
486 pub model: String,
487
488 #[serde(default = "default_small_model_max_tokens")]
490 pub max_tokens: u32,
491
492 #[serde(default = "default_small_model_temperature")]
494 pub temperature: f32,
495
496 #[serde(default = "default_small_model_for_large_reads")]
498 pub use_for_large_reads: bool,
499
500 #[serde(default = "default_small_model_for_web_summary")]
502 pub use_for_web_summary: bool,
503
504 #[serde(default = "default_small_model_for_git_history")]
506 pub use_for_git_history: bool,
507}
508
509impl Default for AgentSmallModelConfig {
510 fn default() -> Self {
511 Self {
512 enabled: default_small_model_enabled(),
513 model: String::new(),
514 max_tokens: default_small_model_max_tokens(),
515 temperature: default_small_model_temperature(),
516 use_for_large_reads: default_small_model_for_large_reads(),
517 use_for_web_summary: default_small_model_for_web_summary(),
518 use_for_git_history: default_small_model_for_git_history(),
519 }
520 }
521}
522
523fn default_small_model_enabled() -> bool {
524 true }
526
527fn default_small_model_max_tokens() -> u32 {
528 1000 }
530
531fn default_small_model_temperature() -> f32 {
532 0.3 }
534
535fn default_small_model_for_large_reads() -> bool {
536 true
537}
538
539fn default_small_model_for_web_summary() -> bool {
540 true
541}
542
543fn default_small_model_for_git_history() -> bool {
544 true
545}