1use crate::constants::{defaults, instructions, llm_generation, project_doc, prompts};
2use crate::types::{
3 EditingMode, ReasoningEffortLevel, SystemPromptMode, ToolDocumentationMode,
4 UiSurfacePreference, VerbosityLevel,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
10const DEFAULT_MAX_SNAPSHOTS: usize = 50;
11const DEFAULT_MAX_AGE_DAYS: u64 = 30;
12
13#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct AgentConfig {
17 #[serde(default = "default_provider")]
19 pub provider: String,
20
21 #[serde(default = "default_api_key_env")]
23 pub api_key_env: String,
24
25 #[serde(default = "default_model")]
27 pub default_model: String,
28
29 #[serde(default = "default_theme")]
31 pub theme: String,
32
33 #[serde(default)]
37 pub system_prompt_mode: SystemPromptMode,
38
39 #[serde(default)]
45 pub tool_documentation_mode: ToolDocumentationMode,
46
47 #[serde(default = "default_enable_split_tool_results")]
54 pub enable_split_tool_results: bool,
55
56 #[serde(default = "default_todo_planning_mode")]
57 pub todo_planning_mode: bool,
58
59 #[serde(default)]
61 pub ui_surface: UiSurfacePreference,
62
63 #[serde(default = "default_max_conversation_turns")]
65 pub max_conversation_turns: usize,
66
67 #[serde(default = "default_reasoning_effort")]
70 pub reasoning_effort: ReasoningEffortLevel,
71
72 #[serde(default = "default_verbosity")]
75 pub verbosity: VerbosityLevel,
76
77 #[serde(default = "default_temperature")]
82 pub temperature: f32,
83
84 #[serde(default = "default_refine_temperature")]
88 pub refine_temperature: f32,
89
90 #[serde(default = "default_enable_self_review")]
92 pub enable_self_review: bool,
93
94 #[serde(default = "default_max_review_passes")]
96 pub max_review_passes: usize,
97
98 #[serde(default = "default_refine_prompts_enabled")]
100 pub refine_prompts_enabled: bool,
101
102 #[serde(default = "default_refine_max_passes")]
104 pub refine_prompts_max_passes: usize,
105
106 #[serde(default)]
108 pub refine_prompts_model: String,
109
110 #[serde(default)]
114 pub small_model: AgentSmallModelConfig,
115
116 #[serde(default)]
118 pub onboarding: AgentOnboardingConfig,
119
120 #[serde(default = "default_project_doc_max_bytes")]
122 pub project_doc_max_bytes: usize,
123
124 #[serde(
126 default = "default_instruction_max_bytes",
127 alias = "rule_doc_max_bytes"
128 )]
129 pub instruction_max_bytes: usize,
130
131 #[serde(default, alias = "instruction_paths", alias = "instructions")]
133 pub instruction_files: Vec<String>,
134
135 #[serde(default)]
137 pub custom_prompts: AgentCustomPromptsConfig,
138
139 #[serde(default)]
141 pub custom_slash_commands: AgentCustomSlashCommandsConfig,
142
143 #[serde(default)]
145 pub custom_api_keys: BTreeMap<String, String>,
146
147 #[serde(default)]
149 pub checkpointing: AgentCheckpointingConfig,
150
151 #[serde(default)]
153 pub vibe_coding: AgentVibeCodingConfig,
154
155 #[serde(default = "default_max_task_retries")]
159 pub max_task_retries: u32,
160
161 #[serde(default = "default_include_temporal_context")]
164 pub include_temporal_context: bool,
165
166 #[serde(default)]
168 pub temporal_context_use_utc: bool,
169
170 #[serde(default = "default_include_working_directory")]
172 pub include_working_directory: bool,
173
174 #[serde(default)]
176 pub user_instructions: Option<String>,
177
178 #[serde(default)]
184 pub default_editing_mode: EditingMode,
185
186 #[serde(default = "default_require_plan_confirmation")]
191 pub require_plan_confirmation: bool,
192}
193
194impl Default for AgentConfig {
195 fn default() -> Self {
196 Self {
197 provider: default_provider(),
198 api_key_env: default_api_key_env(),
199 default_model: default_model(),
200 theme: default_theme(),
201 system_prompt_mode: SystemPromptMode::default(),
202 tool_documentation_mode: ToolDocumentationMode::default(),
203 enable_split_tool_results: default_enable_split_tool_results(),
204 todo_planning_mode: default_todo_planning_mode(),
205 ui_surface: UiSurfacePreference::default(),
206 max_conversation_turns: default_max_conversation_turns(),
207 reasoning_effort: default_reasoning_effort(),
208 verbosity: default_verbosity(),
209 temperature: default_temperature(),
210 refine_temperature: default_refine_temperature(),
211 enable_self_review: default_enable_self_review(),
212 max_review_passes: default_max_review_passes(),
213 refine_prompts_enabled: default_refine_prompts_enabled(),
214 refine_prompts_max_passes: default_refine_max_passes(),
215 refine_prompts_model: String::new(),
216 small_model: AgentSmallModelConfig::default(),
217 onboarding: AgentOnboardingConfig::default(),
218 project_doc_max_bytes: default_project_doc_max_bytes(),
219 instruction_max_bytes: default_instruction_max_bytes(),
220 instruction_files: Vec::new(),
221 custom_prompts: AgentCustomPromptsConfig::default(),
222 custom_slash_commands: AgentCustomSlashCommandsConfig::default(),
223 custom_api_keys: BTreeMap::new(),
224 checkpointing: AgentCheckpointingConfig::default(),
225 vibe_coding: AgentVibeCodingConfig::default(),
226 max_task_retries: default_max_task_retries(),
227 include_temporal_context: default_include_temporal_context(),
228 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
230 user_instructions: None,
231 default_editing_mode: EditingMode::default(),
232 require_plan_confirmation: default_require_plan_confirmation(),
233 }
234 }
235}
236
237impl AgentConfig {
238 pub fn validate_llm_params(&self) -> Result<(), String> {
240 if !(0.0..=1.0).contains(&self.temperature) {
242 return Err(format!(
243 "temperature must be between 0.0 and 1.0, got {}",
244 self.temperature
245 ));
246 }
247
248 if !(0.0..=1.0).contains(&self.refine_temperature) {
249 return Err(format!(
250 "refine_temperature must be between 0.0 and 1.0, got {}",
251 self.refine_temperature
252 ));
253 }
254
255 Ok(())
256 }
257}
258
259#[inline]
261fn default_provider() -> String {
262 defaults::DEFAULT_PROVIDER.into()
263}
264
265#[inline]
266fn default_api_key_env() -> String {
267 defaults::DEFAULT_API_KEY_ENV.into()
268}
269
270#[inline]
271fn default_model() -> String {
272 defaults::DEFAULT_MODEL.into()
273}
274
275#[inline]
276fn default_theme() -> String {
277 defaults::DEFAULT_THEME.into()
278}
279
280#[inline]
281const fn default_todo_planning_mode() -> bool {
282 true
283}
284
285#[inline]
286const fn default_enable_split_tool_results() -> bool {
287 true }
289
290#[inline]
291const fn default_max_conversation_turns() -> usize {
292 150
293}
294
295#[inline]
296fn default_reasoning_effort() -> ReasoningEffortLevel {
297 ReasoningEffortLevel::default()
298}
299
300#[inline]
301fn default_verbosity() -> VerbosityLevel {
302 VerbosityLevel::default()
303}
304
305#[inline]
306const fn default_temperature() -> f32 {
307 llm_generation::DEFAULT_TEMPERATURE
308}
309
310#[inline]
311const fn default_refine_temperature() -> f32 {
312 llm_generation::DEFAULT_REFINE_TEMPERATURE
313}
314
315#[inline]
316const fn default_enable_self_review() -> bool {
317 false
318}
319
320#[inline]
321const fn default_max_review_passes() -> usize {
322 1
323}
324
325#[inline]
326const fn default_refine_prompts_enabled() -> bool {
327 false
328}
329
330#[inline]
331const fn default_refine_max_passes() -> usize {
332 1
333}
334
335#[inline]
336const fn default_project_doc_max_bytes() -> usize {
337 project_doc::DEFAULT_MAX_BYTES
338}
339
340#[inline]
341const fn default_instruction_max_bytes() -> usize {
342 instructions::DEFAULT_MAX_BYTES
343}
344
345#[inline]
346const fn default_max_task_retries() -> u32 {
347 2 }
349
350#[inline]
351const fn default_include_temporal_context() -> bool {
352 true }
354
355#[inline]
356const fn default_include_working_directory() -> bool {
357 true }
359
360#[inline]
361const fn default_require_plan_confirmation() -> bool {
362 true }
364
365#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
366#[derive(Debug, Clone, Deserialize, Serialize)]
367pub struct AgentCustomPromptsConfig {
368 #[serde(default = "default_custom_prompts_enabled")]
370 pub enabled: bool,
371
372 #[serde(default = "default_custom_prompts_directory")]
374 pub directory: String,
375
376 #[serde(default)]
378 pub extra_directories: Vec<String>,
379
380 #[serde(default = "default_custom_prompts_max_file_size_kb")]
382 pub max_file_size_kb: usize,
383}
384
385impl Default for AgentCustomPromptsConfig {
386 fn default() -> Self {
387 Self {
388 enabled: default_custom_prompts_enabled(),
389 directory: default_custom_prompts_directory(),
390 extra_directories: Vec::new(),
391 max_file_size_kb: default_custom_prompts_max_file_size_kb(),
392 }
393 }
394}
395
396#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
398#[derive(Debug, Clone, Deserialize, Serialize, Default)]
399pub struct AgentCustomSlashCommandsConfig {
400 #[serde(default = "default_custom_slash_commands_enabled")]
402 pub enabled: bool,
403
404 #[serde(default = "default_custom_slash_commands_directory")]
406 pub directory: String,
407
408 #[serde(default)]
410 pub extra_directories: Vec<String>,
411
412 #[serde(default = "default_custom_slash_commands_max_file_size_kb")]
414 pub max_file_size_kb: usize,
415}
416
417#[inline]
418const fn default_custom_slash_commands_enabled() -> bool {
419 true
420}
421
422fn default_custom_slash_commands_directory() -> String {
423 crate::constants::prompts::DEFAULT_CUSTOM_SLASH_COMMANDS_DIR.into()
424}
425
426const fn default_custom_slash_commands_max_file_size_kb() -> usize {
427 64 }
429
430#[inline]
431const fn default_custom_prompts_enabled() -> bool {
432 true
433}
434
435#[inline]
436fn default_custom_prompts_directory() -> String {
437 prompts::DEFAULT_CUSTOM_PROMPTS_DIR.into()
438}
439
440#[inline]
441const fn default_custom_prompts_max_file_size_kb() -> usize {
442 prompts::DEFAULT_CUSTOM_PROMPT_MAX_FILE_SIZE_KB
443}
444
445#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
446#[derive(Debug, Clone, Deserialize, Serialize)]
447pub struct AgentCheckpointingConfig {
448 #[serde(default = "default_checkpointing_enabled")]
450 pub enabled: bool,
451
452 #[serde(default)]
454 pub storage_dir: Option<String>,
455
456 #[serde(default = "default_checkpointing_max_snapshots")]
458 pub max_snapshots: usize,
459
460 #[serde(default = "default_checkpointing_max_age_days")]
462 pub max_age_days: Option<u64>,
463}
464
465impl Default for AgentCheckpointingConfig {
466 fn default() -> Self {
467 Self {
468 enabled: default_checkpointing_enabled(),
469 storage_dir: None,
470 max_snapshots: default_checkpointing_max_snapshots(),
471 max_age_days: default_checkpointing_max_age_days(),
472 }
473 }
474}
475
476#[inline]
477const fn default_checkpointing_enabled() -> bool {
478 DEFAULT_CHECKPOINTS_ENABLED
479}
480
481#[inline]
482const fn default_checkpointing_max_snapshots() -> usize {
483 DEFAULT_MAX_SNAPSHOTS
484}
485
486#[inline]
487const fn default_checkpointing_max_age_days() -> Option<u64> {
488 Some(DEFAULT_MAX_AGE_DAYS)
489}
490
491#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
492#[derive(Debug, Clone, Deserialize, Serialize)]
493pub struct AgentOnboardingConfig {
494 #[serde(default = "default_onboarding_enabled")]
496 pub enabled: bool,
497
498 #[serde(default = "default_intro_text")]
500 pub intro_text: String,
501
502 #[serde(default = "default_show_project_overview")]
504 pub include_project_overview: bool,
505
506 #[serde(default = "default_show_language_summary")]
508 pub include_language_summary: bool,
509
510 #[serde(default = "default_show_guideline_highlights")]
512 pub include_guideline_highlights: bool,
513
514 #[serde(default = "default_show_usage_tips_in_welcome")]
516 pub include_usage_tips_in_welcome: bool,
517
518 #[serde(default = "default_show_recommended_actions_in_welcome")]
520 pub include_recommended_actions_in_welcome: bool,
521
522 #[serde(default = "default_guideline_highlight_limit")]
524 pub guideline_highlight_limit: usize,
525
526 #[serde(default = "default_usage_tips")]
528 pub usage_tips: Vec<String>,
529
530 #[serde(default = "default_recommended_actions")]
532 pub recommended_actions: Vec<String>,
533
534 #[serde(default)]
536 pub chat_placeholder: Option<String>,
537}
538
539impl Default for AgentOnboardingConfig {
540 fn default() -> Self {
541 Self {
542 enabled: default_onboarding_enabled(),
543 intro_text: default_intro_text(),
544 include_project_overview: default_show_project_overview(),
545 include_language_summary: default_show_language_summary(),
546 include_guideline_highlights: default_show_guideline_highlights(),
547 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
548 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
549 guideline_highlight_limit: default_guideline_highlight_limit(),
550 usage_tips: default_usage_tips(),
551 recommended_actions: default_recommended_actions(),
552 chat_placeholder: None,
553 }
554 }
555}
556
557#[inline]
558const fn default_onboarding_enabled() -> bool {
559 true
560}
561
562const DEFAULT_INTRO_TEXT: &str =
563 "Let's get oriented. I preloaded workspace context so we can move fast.";
564
565#[inline]
566fn default_intro_text() -> String {
567 DEFAULT_INTRO_TEXT.into()
568}
569
570#[inline]
571const fn default_show_project_overview() -> bool {
572 true
573}
574
575#[inline]
576const fn default_show_language_summary() -> bool {
577 false
578}
579
580#[inline]
581const fn default_show_guideline_highlights() -> bool {
582 true
583}
584
585#[inline]
586const fn default_show_usage_tips_in_welcome() -> bool {
587 false
588}
589
590#[inline]
591const fn default_show_recommended_actions_in_welcome() -> bool {
592 false
593}
594
595#[inline]
596const fn default_guideline_highlight_limit() -> usize {
597 3
598}
599
600const DEFAULT_USAGE_TIPS: &[&str] = &[
601 "Describe your current coding goal or ask for a quick status overview.",
602 "Reference AGENTS.md guidelines when proposing changes.",
603 "Prefer asking for targeted file reads or diffs before editing.",
604];
605
606const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
607 "Review the highlighted guidelines and share the task you want to tackle.",
608 "Ask for a workspace tour if you need more context.",
609];
610
611fn default_usage_tips() -> Vec<String> {
612 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
613}
614
615fn default_recommended_actions() -> Vec<String> {
616 DEFAULT_RECOMMENDED_ACTIONS
617 .iter()
618 .map(|s| (*s).into())
619 .collect()
620}
621
622#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
632#[derive(Debug, Clone, Deserialize, Serialize)]
633pub struct AgentSmallModelConfig {
634 #[serde(default = "default_small_model_enabled")]
636 pub enabled: bool,
637
638 #[serde(default)]
641 pub model: String,
642
643 #[serde(default = "default_small_model_temperature")]
645 pub temperature: f32,
646
647 #[serde(default = "default_small_model_for_large_reads")]
649 pub use_for_large_reads: bool,
650
651 #[serde(default = "default_small_model_for_web_summary")]
653 pub use_for_web_summary: bool,
654
655 #[serde(default = "default_small_model_for_git_history")]
657 pub use_for_git_history: bool,
658}
659
660impl Default for AgentSmallModelConfig {
661 fn default() -> Self {
662 Self {
663 enabled: default_small_model_enabled(),
664 model: String::new(),
665 temperature: default_small_model_temperature(),
666 use_for_large_reads: default_small_model_for_large_reads(),
667 use_for_web_summary: default_small_model_for_web_summary(),
668 use_for_git_history: default_small_model_for_git_history(),
669 }
670 }
671}
672
673#[inline]
674const fn default_small_model_enabled() -> bool {
675 true }
677
678#[inline]
679const fn default_small_model_temperature() -> f32 {
680 0.3 }
682
683#[inline]
684const fn default_small_model_for_large_reads() -> bool {
685 true
686}
687
688#[inline]
689const fn default_small_model_for_web_summary() -> bool {
690 true
691}
692
693#[inline]
694const fn default_small_model_for_git_history() -> bool {
695 true
696}
697
698#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
703#[derive(Debug, Clone, Deserialize, Serialize)]
704pub struct AgentVibeCodingConfig {
705 #[serde(default = "default_vibe_coding_enabled")]
707 pub enabled: bool,
708
709 #[serde(default = "default_vibe_min_prompt_length")]
711 pub min_prompt_length: usize,
712
713 #[serde(default = "default_vibe_min_prompt_words")]
715 pub min_prompt_words: usize,
716
717 #[serde(default = "default_vibe_entity_resolution")]
719 pub enable_entity_resolution: bool,
720
721 #[serde(default = "default_vibe_entity_cache")]
723 pub entity_index_cache: String,
724
725 #[serde(default = "default_vibe_max_entity_matches")]
727 pub max_entity_matches: usize,
728
729 #[serde(default = "default_vibe_track_workspace")]
731 pub track_workspace_state: bool,
732
733 #[serde(default = "default_vibe_max_recent_files")]
735 pub max_recent_files: usize,
736
737 #[serde(default = "default_vibe_track_values")]
739 pub track_value_history: bool,
740
741 #[serde(default = "default_vibe_conversation_memory")]
743 pub enable_conversation_memory: bool,
744
745 #[serde(default = "default_vibe_max_memory_turns")]
747 pub max_memory_turns: usize,
748
749 #[serde(default = "default_vibe_pronoun_resolution")]
751 pub enable_pronoun_resolution: bool,
752
753 #[serde(default = "default_vibe_proactive_context")]
755 pub enable_proactive_context: bool,
756
757 #[serde(default = "default_vibe_max_context_files")]
759 pub max_context_files: usize,
760
761 #[serde(default = "default_vibe_max_snippets_per_file")]
763 pub max_context_snippets_per_file: usize,
764
765 #[serde(default = "default_vibe_max_search_results")]
767 pub max_search_results: usize,
768
769 #[serde(default = "default_vibe_value_inference")]
771 pub enable_relative_value_inference: bool,
772}
773
774impl Default for AgentVibeCodingConfig {
775 fn default() -> Self {
776 Self {
777 enabled: default_vibe_coding_enabled(),
778 min_prompt_length: default_vibe_min_prompt_length(),
779 min_prompt_words: default_vibe_min_prompt_words(),
780 enable_entity_resolution: default_vibe_entity_resolution(),
781 entity_index_cache: default_vibe_entity_cache(),
782 max_entity_matches: default_vibe_max_entity_matches(),
783 track_workspace_state: default_vibe_track_workspace(),
784 max_recent_files: default_vibe_max_recent_files(),
785 track_value_history: default_vibe_track_values(),
786 enable_conversation_memory: default_vibe_conversation_memory(),
787 max_memory_turns: default_vibe_max_memory_turns(),
788 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
789 enable_proactive_context: default_vibe_proactive_context(),
790 max_context_files: default_vibe_max_context_files(),
791 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
792 max_search_results: default_vibe_max_search_results(),
793 enable_relative_value_inference: default_vibe_value_inference(),
794 }
795 }
796}
797
798#[inline]
800const fn default_vibe_coding_enabled() -> bool {
801 false }
803
804#[inline]
805const fn default_vibe_min_prompt_length() -> usize {
806 5
807}
808
809#[inline]
810const fn default_vibe_min_prompt_words() -> usize {
811 2
812}
813
814#[inline]
815const fn default_vibe_entity_resolution() -> bool {
816 true
817}
818
819#[inline]
820fn default_vibe_entity_cache() -> String {
821 ".vtcode/entity_index.json".into()
822}
823
824#[inline]
825const fn default_vibe_max_entity_matches() -> usize {
826 5
827}
828
829#[inline]
830const fn default_vibe_track_workspace() -> bool {
831 true
832}
833
834#[inline]
835const fn default_vibe_max_recent_files() -> usize {
836 20
837}
838
839#[inline]
840const fn default_vibe_track_values() -> bool {
841 true
842}
843
844#[inline]
845const fn default_vibe_conversation_memory() -> bool {
846 true
847}
848
849#[inline]
850const fn default_vibe_max_memory_turns() -> usize {
851 50
852}
853
854#[inline]
855const fn default_vibe_pronoun_resolution() -> bool {
856 true
857}
858
859#[inline]
860const fn default_vibe_proactive_context() -> bool {
861 true
862}
863
864#[inline]
865const fn default_vibe_max_context_files() -> usize {
866 3
867}
868
869#[inline]
870const fn default_vibe_max_snippets_per_file() -> usize {
871 20
872}
873
874#[inline]
875const fn default_vibe_max_search_results() -> usize {
876 5
877}
878
879#[inline]
880const fn default_vibe_value_inference() -> bool {
881 true
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887
888 #[test]
889 fn test_editing_mode_config_default() {
890 let config = AgentConfig::default();
891 assert_eq!(config.default_editing_mode, EditingMode::Edit);
892 assert!(config.require_plan_confirmation);
893 }
894}