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) {
174 return Err(format!(
175 "temperature must be between 0.0 and 1.0, got {}",
176 self.temperature
177 ));
178 }
179
180 if !(0.0..=1.0).contains(&self.refine_temperature) {
181 return Err(format!(
182 "refine_temperature must be between 0.0 and 1.0, got {}",
183 self.refine_temperature
184 ));
185 }
186
187 if self.max_tokens == 0 {
189 return Err("max_tokens must be greater than 0".into());
190 }
191
192 if self.refine_max_tokens == 0 {
193 return Err("refine_max_tokens must be greater than 0".into());
194 }
195
196 Ok(())
197 }
198}
199
200#[inline]
202fn default_provider() -> String {
203 defaults::DEFAULT_PROVIDER.into()
204}
205
206#[inline]
207fn default_api_key_env() -> String {
208 defaults::DEFAULT_API_KEY_ENV.into()
209}
210
211#[inline]
212fn default_model() -> String {
213 defaults::DEFAULT_MODEL.into()
214}
215
216#[inline]
217fn default_theme() -> String {
218 defaults::DEFAULT_THEME.into()
219}
220
221#[inline]
222const fn default_todo_planning_mode() -> bool {
223 true
224}
225
226#[inline]
227const fn default_max_conversation_turns() -> usize {
228 150
229}
230
231#[inline]
232fn default_reasoning_effort() -> ReasoningEffortLevel {
233 ReasoningEffortLevel::default()
234}
235
236#[inline]
237fn default_verbosity() -> VerbosityLevel {
238 VerbosityLevel::default()
239}
240
241#[inline]
242const fn default_temperature() -> f32 {
243 llm_generation::DEFAULT_TEMPERATURE
244}
245
246#[inline]
247const fn default_max_tokens() -> u32 {
248 llm_generation::DEFAULT_MAX_TOKENS
249}
250
251#[inline]
252const fn default_refine_temperature() -> f32 {
253 llm_generation::DEFAULT_REFINE_TEMPERATURE
254}
255
256#[inline]
257const fn default_refine_max_tokens() -> u32 {
258 llm_generation::DEFAULT_REFINE_MAX_TOKENS
259}
260
261#[inline]
262const fn default_enable_self_review() -> bool {
263 false
264}
265
266#[inline]
267const fn default_max_review_passes() -> usize {
268 1
269}
270
271#[inline]
272const fn default_refine_prompts_enabled() -> bool {
273 false
274}
275
276#[inline]
277const fn default_refine_max_passes() -> usize {
278 1
279}
280
281#[inline]
282const fn default_project_doc_max_bytes() -> usize {
283 project_doc::DEFAULT_MAX_BYTES
284}
285
286#[inline]
287const fn default_instruction_max_bytes() -> usize {
288 instructions::DEFAULT_MAX_BYTES
289}
290
291#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
292#[derive(Debug, Clone, Deserialize, Serialize)]
293pub struct AgentCustomPromptsConfig {
294 #[serde(default = "default_custom_prompts_enabled")]
296 pub enabled: bool,
297
298 #[serde(default = "default_custom_prompts_directory")]
300 pub directory: String,
301
302 #[serde(default)]
304 pub extra_directories: Vec<String>,
305
306 #[serde(default = "default_custom_prompts_max_file_size_kb")]
308 pub max_file_size_kb: usize,
309}
310
311impl Default for AgentCustomPromptsConfig {
312 fn default() -> Self {
313 Self {
314 enabled: default_custom_prompts_enabled(),
315 directory: default_custom_prompts_directory(),
316 extra_directories: Vec::new(),
317 max_file_size_kb: default_custom_prompts_max_file_size_kb(),
318 }
319 }
320}
321
322#[inline]
323const fn default_custom_prompts_enabled() -> bool {
324 true
325}
326
327#[inline]
328fn default_custom_prompts_directory() -> String {
329 prompts::DEFAULT_CUSTOM_PROMPTS_DIR.into()
330}
331
332#[inline]
333const fn default_custom_prompts_max_file_size_kb() -> usize {
334 prompts::DEFAULT_CUSTOM_PROMPT_MAX_FILE_SIZE_KB
335}
336
337#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
338#[derive(Debug, Clone, Deserialize, Serialize)]
339pub struct AgentCheckpointingConfig {
340 #[serde(default = "default_checkpointing_enabled")]
342 pub enabled: bool,
343
344 #[serde(default)]
346 pub storage_dir: Option<String>,
347
348 #[serde(default = "default_checkpointing_max_snapshots")]
350 pub max_snapshots: usize,
351
352 #[serde(default = "default_checkpointing_max_age_days")]
354 pub max_age_days: Option<u64>,
355}
356
357impl Default for AgentCheckpointingConfig {
358 fn default() -> Self {
359 Self {
360 enabled: default_checkpointing_enabled(),
361 storage_dir: None,
362 max_snapshots: default_checkpointing_max_snapshots(),
363 max_age_days: default_checkpointing_max_age_days(),
364 }
365 }
366}
367
368#[inline]
369const fn default_checkpointing_enabled() -> bool {
370 DEFAULT_CHECKPOINTS_ENABLED
371}
372
373#[inline]
374const fn default_checkpointing_max_snapshots() -> usize {
375 DEFAULT_MAX_SNAPSHOTS
376}
377
378#[inline]
379const fn default_checkpointing_max_age_days() -> Option<u64> {
380 Some(DEFAULT_MAX_AGE_DAYS)
381}
382
383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384#[derive(Debug, Clone, Deserialize, Serialize)]
385pub struct AgentOnboardingConfig {
386 #[serde(default = "default_onboarding_enabled")]
388 pub enabled: bool,
389
390 #[serde(default = "default_intro_text")]
392 pub intro_text: String,
393
394 #[serde(default = "default_show_project_overview")]
396 pub include_project_overview: bool,
397
398 #[serde(default = "default_show_language_summary")]
400 pub include_language_summary: bool,
401
402 #[serde(default = "default_show_guideline_highlights")]
404 pub include_guideline_highlights: bool,
405
406 #[serde(default = "default_show_usage_tips_in_welcome")]
408 pub include_usage_tips_in_welcome: bool,
409
410 #[serde(default = "default_show_recommended_actions_in_welcome")]
412 pub include_recommended_actions_in_welcome: bool,
413
414 #[serde(default = "default_guideline_highlight_limit")]
416 pub guideline_highlight_limit: usize,
417
418 #[serde(default = "default_usage_tips")]
420 pub usage_tips: Vec<String>,
421
422 #[serde(default = "default_recommended_actions")]
424 pub recommended_actions: Vec<String>,
425
426 #[serde(default)]
428 pub chat_placeholder: Option<String>,
429}
430
431impl Default for AgentOnboardingConfig {
432 fn default() -> Self {
433 Self {
434 enabled: default_onboarding_enabled(),
435 intro_text: default_intro_text(),
436 include_project_overview: default_show_project_overview(),
437 include_language_summary: default_show_language_summary(),
438 include_guideline_highlights: default_show_guideline_highlights(),
439 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
440 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
441 guideline_highlight_limit: default_guideline_highlight_limit(),
442 usage_tips: default_usage_tips(),
443 recommended_actions: default_recommended_actions(),
444 chat_placeholder: None,
445 }
446 }
447}
448
449#[inline]
450const fn default_onboarding_enabled() -> bool {
451 true
452}
453
454const DEFAULT_INTRO_TEXT: &str =
455 "Let's get oriented. I preloaded workspace context so we can move fast.";
456
457#[inline]
458fn default_intro_text() -> String {
459 DEFAULT_INTRO_TEXT.into()
460}
461
462#[inline]
463const fn default_show_project_overview() -> bool {
464 true
465}
466
467#[inline]
468const fn default_show_language_summary() -> bool {
469 false
470}
471
472#[inline]
473const fn default_show_guideline_highlights() -> bool {
474 true
475}
476
477#[inline]
478const fn default_show_usage_tips_in_welcome() -> bool {
479 false
480}
481
482#[inline]
483const fn default_show_recommended_actions_in_welcome() -> bool {
484 false
485}
486
487#[inline]
488const fn default_guideline_highlight_limit() -> usize {
489 3
490}
491
492const DEFAULT_USAGE_TIPS: &[&str] = &[
493 "Describe your current coding goal or ask for a quick status overview.",
494 "Reference AGENTS.md guidelines when proposing changes.",
495 "Draft or refresh your TODO list with update_plan before coding.",
496 "Prefer asking for targeted file reads or diffs before editing.",
497];
498
499const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
500 "Start the session by outlining a 3–6 step TODO plan via update_plan.",
501 "Review the highlighted guidelines and share the task you want to tackle.",
502 "Ask for a workspace tour if you need more context.",
503];
504
505fn default_usage_tips() -> Vec<String> {
506 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
507}
508
509fn default_recommended_actions() -> Vec<String> {
510 DEFAULT_RECOMMENDED_ACTIONS
511 .iter()
512 .map(|s| (*s).into())
513 .collect()
514}
515
516#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
526#[derive(Debug, Clone, Deserialize, Serialize)]
527pub struct AgentSmallModelConfig {
528 #[serde(default = "default_small_model_enabled")]
530 pub enabled: bool,
531
532 #[serde(default)]
535 pub model: String,
536
537 #[serde(default = "default_small_model_max_tokens")]
539 pub max_tokens: u32,
540
541 #[serde(default = "default_small_model_temperature")]
543 pub temperature: f32,
544
545 #[serde(default = "default_small_model_for_large_reads")]
547 pub use_for_large_reads: bool,
548
549 #[serde(default = "default_small_model_for_web_summary")]
551 pub use_for_web_summary: bool,
552
553 #[serde(default = "default_small_model_for_git_history")]
555 pub use_for_git_history: bool,
556}
557
558impl Default for AgentSmallModelConfig {
559 fn default() -> Self {
560 Self {
561 enabled: default_small_model_enabled(),
562 model: String::new(),
563 max_tokens: default_small_model_max_tokens(),
564 temperature: default_small_model_temperature(),
565 use_for_large_reads: default_small_model_for_large_reads(),
566 use_for_web_summary: default_small_model_for_web_summary(),
567 use_for_git_history: default_small_model_for_git_history(),
568 }
569 }
570}
571
572#[inline]
573const fn default_small_model_enabled() -> bool {
574 true }
576
577#[inline]
578const fn default_small_model_max_tokens() -> u32 {
579 1000 }
581
582#[inline]
583const fn default_small_model_temperature() -> f32 {
584 0.3 }
586
587#[inline]
588const fn default_small_model_for_large_reads() -> bool {
589 true
590}
591
592#[inline]
593const fn default_small_model_for_web_summary() -> bool {
594 true
595}
596
597#[inline]
598const fn default_small_model_for_git_history() -> bool {
599 true
600}