1use crate::constants::{defaults, instructions, llm_generation, project_doc};
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")]
58 pub todo_planning_mode: bool,
59
60 #[serde(default)]
62 pub ui_surface: UiSurfacePreference,
63
64 #[serde(default = "default_max_conversation_turns")]
66 pub max_conversation_turns: usize,
67
68 #[serde(default = "default_reasoning_effort")]
71 pub reasoning_effort: ReasoningEffortLevel,
72
73 #[serde(default = "default_verbosity")]
76 pub verbosity: VerbosityLevel,
77
78 #[serde(default = "default_temperature")]
83 pub temperature: f32,
84
85 #[serde(default = "default_refine_temperature")]
89 pub refine_temperature: f32,
90
91 #[serde(default = "default_enable_self_review")]
93 pub enable_self_review: bool,
94
95 #[serde(default = "default_max_review_passes")]
97 pub max_review_passes: usize,
98
99 #[serde(default = "default_refine_prompts_enabled")]
101 pub refine_prompts_enabled: bool,
102
103 #[serde(default = "default_refine_max_passes")]
105 pub refine_prompts_max_passes: usize,
106
107 #[serde(default)]
109 pub refine_prompts_model: String,
110
111 #[serde(default)]
115 pub small_model: AgentSmallModelConfig,
116
117 #[serde(default)]
119 pub onboarding: AgentOnboardingConfig,
120
121 #[serde(default = "default_project_doc_max_bytes")]
123 pub project_doc_max_bytes: usize,
124
125 #[serde(
127 default = "default_instruction_max_bytes",
128 alias = "rule_doc_max_bytes"
129 )]
130 pub instruction_max_bytes: usize,
131
132 #[serde(default, alias = "instruction_paths", alias = "instructions")]
134 pub instruction_files: Vec<String>,
135
136 #[serde(default, skip_serializing)]
142 pub custom_api_keys: BTreeMap<String, String>,
143
144 #[serde(default)]
151 pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
152
153 #[serde(default)]
155 pub checkpointing: AgentCheckpointingConfig,
156
157 #[serde(default)]
159 pub vibe_coding: AgentVibeCodingConfig,
160
161 #[serde(default = "default_max_task_retries")]
165 pub max_task_retries: u32,
166
167 #[serde(default)]
169 pub harness: AgentHarnessConfig,
170
171 #[serde(default = "default_include_temporal_context")]
174 pub include_temporal_context: bool,
175
176 #[serde(default)]
178 pub temporal_context_use_utc: bool,
179
180 #[serde(default = "default_include_working_directory")]
182 pub include_working_directory: bool,
183
184 #[serde(default)]
194 pub include_structured_reasoning_tags: Option<bool>,
195
196 #[serde(default)]
198 pub user_instructions: Option<String>,
199
200 #[serde(default)]
203 pub default_editing_mode: EditingMode,
204
205 #[serde(default = "default_require_plan_confirmation")]
209 pub require_plan_confirmation: bool,
210
211 #[serde(default = "default_autonomous_mode")]
214 pub autonomous_mode: bool,
215
216 #[serde(default)]
219 pub circuit_breaker: CircuitBreakerConfig,
220
221 #[serde(default)]
224 pub open_responses: OpenResponsesConfig,
225}
226
227#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
228#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
229#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
230#[serde(rename_all = "snake_case")]
231pub enum ContinuationPolicy {
232 Off,
233 #[default]
234 ExecOnly,
235 All,
236}
237
238impl ContinuationPolicy {
239 pub fn as_str(&self) -> &'static str {
240 match self {
241 Self::Off => "off",
242 Self::ExecOnly => "exec_only",
243 Self::All => "all",
244 }
245 }
246
247 pub fn parse(value: &str) -> Option<Self> {
248 let normalized = value.trim();
249 if normalized.eq_ignore_ascii_case("off") {
250 Some(Self::Off)
251 } else if normalized.eq_ignore_ascii_case("exec_only")
252 || normalized.eq_ignore_ascii_case("exec-only")
253 {
254 Some(Self::ExecOnly)
255 } else if normalized.eq_ignore_ascii_case("all") {
256 Some(Self::All)
257 } else {
258 None
259 }
260 }
261}
262
263impl<'de> Deserialize<'de> for ContinuationPolicy {
264 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
265 where
266 D: serde::Deserializer<'de>,
267 {
268 let raw = String::deserialize(deserializer)?;
269 Ok(Self::parse(&raw).unwrap_or_default())
270 }
271}
272
273#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
274#[derive(Debug, Clone, Deserialize, Serialize)]
275pub struct AgentHarnessConfig {
276 #[serde(default = "default_harness_max_tool_calls_per_turn")]
278 pub max_tool_calls_per_turn: usize,
279 #[serde(default = "default_harness_max_tool_wall_clock_secs")]
281 pub max_tool_wall_clock_secs: u64,
282 #[serde(default = "default_harness_max_tool_retries")]
284 pub max_tool_retries: u32,
285 #[serde(default = "default_harness_auto_compaction_enabled")]
289 pub auto_compaction_enabled: bool,
290 #[serde(default)]
294 pub auto_compaction_threshold_tokens: Option<u64>,
295 #[serde(default)]
297 pub continuation_policy: ContinuationPolicy,
298 #[serde(default)]
300 pub event_log_path: Option<String>,
301}
302
303impl Default for AgentHarnessConfig {
304 fn default() -> Self {
305 Self {
306 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
307 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
308 max_tool_retries: default_harness_max_tool_retries(),
309 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
310 auto_compaction_threshold_tokens: None,
311 continuation_policy: ContinuationPolicy::default(),
312 event_log_path: None,
313 }
314 }
315}
316
317#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
318#[derive(Debug, Clone, Deserialize, Serialize)]
319pub struct CircuitBreakerConfig {
320 #[serde(default = "default_circuit_breaker_enabled")]
322 pub enabled: bool,
323
324 #[serde(default = "default_failure_threshold")]
326 pub failure_threshold: u32,
327
328 #[serde(default = "default_pause_on_open")]
330 pub pause_on_open: bool,
331
332 #[serde(default = "default_max_open_circuits")]
334 pub max_open_circuits: usize,
335
336 #[serde(default = "default_recovery_cooldown")]
338 pub recovery_cooldown: u64,
339}
340
341impl Default for CircuitBreakerConfig {
342 fn default() -> Self {
343 Self {
344 enabled: default_circuit_breaker_enabled(),
345 failure_threshold: default_failure_threshold(),
346 pause_on_open: default_pause_on_open(),
347 max_open_circuits: default_max_open_circuits(),
348 recovery_cooldown: default_recovery_cooldown(),
349 }
350 }
351}
352
353#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
359#[derive(Debug, Clone, Deserialize, Serialize)]
360pub struct OpenResponsesConfig {
361 #[serde(default)]
365 pub enabled: bool,
366
367 #[serde(default = "default_open_responses_emit_events")]
371 pub emit_events: bool,
372
373 #[serde(default = "default_open_responses_include_extensions")]
376 pub include_extensions: bool,
377
378 #[serde(default = "default_open_responses_map_tool_calls")]
381 pub map_tool_calls: bool,
382
383 #[serde(default = "default_open_responses_include_reasoning")]
386 pub include_reasoning: bool,
387}
388
389impl Default for OpenResponsesConfig {
390 fn default() -> Self {
391 Self {
392 enabled: false, emit_events: default_open_responses_emit_events(),
394 include_extensions: default_open_responses_include_extensions(),
395 map_tool_calls: default_open_responses_map_tool_calls(),
396 include_reasoning: default_open_responses_include_reasoning(),
397 }
398 }
399}
400
401#[inline]
402const fn default_open_responses_emit_events() -> bool {
403 true }
405
406#[inline]
407const fn default_open_responses_include_extensions() -> bool {
408 true }
410
411#[inline]
412const fn default_open_responses_map_tool_calls() -> bool {
413 true }
415
416#[inline]
417const fn default_open_responses_include_reasoning() -> bool {
418 true }
420
421impl Default for AgentConfig {
422 fn default() -> Self {
423 Self {
424 provider: default_provider(),
425 api_key_env: default_api_key_env(),
426 default_model: default_model(),
427 theme: default_theme(),
428 system_prompt_mode: SystemPromptMode::default(),
429 tool_documentation_mode: ToolDocumentationMode::default(),
430 enable_split_tool_results: default_enable_split_tool_results(),
431 todo_planning_mode: default_todo_planning_mode(),
432 ui_surface: UiSurfacePreference::default(),
433 max_conversation_turns: default_max_conversation_turns(),
434 reasoning_effort: default_reasoning_effort(),
435 verbosity: default_verbosity(),
436 temperature: default_temperature(),
437 refine_temperature: default_refine_temperature(),
438 enable_self_review: default_enable_self_review(),
439 max_review_passes: default_max_review_passes(),
440 refine_prompts_enabled: default_refine_prompts_enabled(),
441 refine_prompts_max_passes: default_refine_max_passes(),
442 refine_prompts_model: String::new(),
443 small_model: AgentSmallModelConfig::default(),
444 onboarding: AgentOnboardingConfig::default(),
445 project_doc_max_bytes: default_project_doc_max_bytes(),
446 instruction_max_bytes: default_instruction_max_bytes(),
447 instruction_files: Vec::new(),
448 custom_api_keys: BTreeMap::new(),
449 credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
450 checkpointing: AgentCheckpointingConfig::default(),
451 vibe_coding: AgentVibeCodingConfig::default(),
452 max_task_retries: default_max_task_retries(),
453 harness: AgentHarnessConfig::default(),
454 include_temporal_context: default_include_temporal_context(),
455 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
457 include_structured_reasoning_tags: None,
458 user_instructions: None,
459 default_editing_mode: EditingMode::default(),
460 require_plan_confirmation: default_require_plan_confirmation(),
461 autonomous_mode: default_autonomous_mode(),
462 circuit_breaker: CircuitBreakerConfig::default(),
463 open_responses: OpenResponsesConfig::default(),
464 }
465 }
466}
467
468impl AgentConfig {
469 pub fn should_include_structured_reasoning_tags(&self) -> bool {
471 self.include_structured_reasoning_tags.unwrap_or(matches!(
472 self.system_prompt_mode,
473 SystemPromptMode::Default | SystemPromptMode::Specialized
474 ))
475 }
476
477 pub fn validate_llm_params(&self) -> Result<(), String> {
479 if !(0.0..=1.0).contains(&self.temperature) {
481 return Err(format!(
482 "temperature must be between 0.0 and 1.0, got {}",
483 self.temperature
484 ));
485 }
486
487 if !(0.0..=1.0).contains(&self.refine_temperature) {
488 return Err(format!(
489 "refine_temperature must be between 0.0 and 1.0, got {}",
490 self.refine_temperature
491 ));
492 }
493
494 Ok(())
495 }
496}
497
498#[inline]
500fn default_provider() -> String {
501 defaults::DEFAULT_PROVIDER.into()
502}
503
504#[inline]
505fn default_api_key_env() -> String {
506 defaults::DEFAULT_API_KEY_ENV.into()
507}
508
509#[inline]
510fn default_model() -> String {
511 defaults::DEFAULT_MODEL.into()
512}
513
514#[inline]
515fn default_theme() -> String {
516 defaults::DEFAULT_THEME.into()
517}
518
519#[inline]
520const fn default_todo_planning_mode() -> bool {
521 true
522}
523
524#[inline]
525const fn default_enable_split_tool_results() -> bool {
526 true }
528
529#[inline]
530const fn default_max_conversation_turns() -> usize {
531 defaults::DEFAULT_MAX_CONVERSATION_TURNS
532}
533
534#[inline]
535fn default_reasoning_effort() -> ReasoningEffortLevel {
536 ReasoningEffortLevel::None
537}
538
539#[inline]
540fn default_verbosity() -> VerbosityLevel {
541 VerbosityLevel::default()
542}
543
544#[inline]
545const fn default_temperature() -> f32 {
546 llm_generation::DEFAULT_TEMPERATURE
547}
548
549#[inline]
550const fn default_refine_temperature() -> f32 {
551 llm_generation::DEFAULT_REFINE_TEMPERATURE
552}
553
554#[inline]
555const fn default_enable_self_review() -> bool {
556 false
557}
558
559#[inline]
560const fn default_max_review_passes() -> usize {
561 1
562}
563
564#[inline]
565const fn default_refine_prompts_enabled() -> bool {
566 false
567}
568
569#[inline]
570const fn default_refine_max_passes() -> usize {
571 1
572}
573
574#[inline]
575const fn default_project_doc_max_bytes() -> usize {
576 project_doc::DEFAULT_MAX_BYTES
577}
578
579#[inline]
580const fn default_instruction_max_bytes() -> usize {
581 instructions::DEFAULT_MAX_BYTES
582}
583
584#[inline]
585const fn default_max_task_retries() -> u32 {
586 2 }
588
589#[inline]
590const fn default_harness_max_tool_calls_per_turn() -> usize {
591 defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
592}
593
594#[inline]
595const fn default_harness_max_tool_wall_clock_secs() -> u64 {
596 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
597}
598
599#[inline]
600const fn default_harness_max_tool_retries() -> u32 {
601 defaults::DEFAULT_MAX_TOOL_RETRIES
602}
603
604#[inline]
605const fn default_harness_auto_compaction_enabled() -> bool {
606 false
607}
608
609#[inline]
610const fn default_include_temporal_context() -> bool {
611 true }
613
614#[inline]
615const fn default_include_working_directory() -> bool {
616 true }
618
619#[inline]
620const fn default_require_plan_confirmation() -> bool {
621 true }
623
624#[inline]
625const fn default_autonomous_mode() -> bool {
626 false }
628
629#[inline]
630const fn default_circuit_breaker_enabled() -> bool {
631 true }
633
634#[inline]
635const fn default_failure_threshold() -> u32 {
636 5 }
638
639#[inline]
640const fn default_pause_on_open() -> bool {
641 true }
643
644#[inline]
645const fn default_max_open_circuits() -> usize {
646 3 }
648
649#[inline]
650const fn default_recovery_cooldown() -> u64 {
651 60 }
653
654#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
655#[derive(Debug, Clone, Deserialize, Serialize)]
656pub struct AgentCheckpointingConfig {
657 #[serde(default = "default_checkpointing_enabled")]
659 pub enabled: bool,
660
661 #[serde(default)]
663 pub storage_dir: Option<String>,
664
665 #[serde(default = "default_checkpointing_max_snapshots")]
667 pub max_snapshots: usize,
668
669 #[serde(default = "default_checkpointing_max_age_days")]
671 pub max_age_days: Option<u64>,
672}
673
674impl Default for AgentCheckpointingConfig {
675 fn default() -> Self {
676 Self {
677 enabled: default_checkpointing_enabled(),
678 storage_dir: None,
679 max_snapshots: default_checkpointing_max_snapshots(),
680 max_age_days: default_checkpointing_max_age_days(),
681 }
682 }
683}
684
685#[inline]
686const fn default_checkpointing_enabled() -> bool {
687 DEFAULT_CHECKPOINTS_ENABLED
688}
689
690#[inline]
691const fn default_checkpointing_max_snapshots() -> usize {
692 DEFAULT_MAX_SNAPSHOTS
693}
694
695#[inline]
696const fn default_checkpointing_max_age_days() -> Option<u64> {
697 Some(DEFAULT_MAX_AGE_DAYS)
698}
699
700#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
701#[derive(Debug, Clone, Deserialize, Serialize)]
702pub struct AgentOnboardingConfig {
703 #[serde(default = "default_onboarding_enabled")]
705 pub enabled: bool,
706
707 #[serde(default = "default_intro_text")]
709 pub intro_text: String,
710
711 #[serde(default = "default_show_project_overview")]
713 pub include_project_overview: bool,
714
715 #[serde(default = "default_show_language_summary")]
717 pub include_language_summary: bool,
718
719 #[serde(default = "default_show_guideline_highlights")]
721 pub include_guideline_highlights: bool,
722
723 #[serde(default = "default_show_usage_tips_in_welcome")]
725 pub include_usage_tips_in_welcome: bool,
726
727 #[serde(default = "default_show_recommended_actions_in_welcome")]
729 pub include_recommended_actions_in_welcome: bool,
730
731 #[serde(default = "default_guideline_highlight_limit")]
733 pub guideline_highlight_limit: usize,
734
735 #[serde(default = "default_usage_tips")]
737 pub usage_tips: Vec<String>,
738
739 #[serde(default = "default_recommended_actions")]
741 pub recommended_actions: Vec<String>,
742
743 #[serde(default)]
745 pub chat_placeholder: Option<String>,
746}
747
748impl Default for AgentOnboardingConfig {
749 fn default() -> Self {
750 Self {
751 enabled: default_onboarding_enabled(),
752 intro_text: default_intro_text(),
753 include_project_overview: default_show_project_overview(),
754 include_language_summary: default_show_language_summary(),
755 include_guideline_highlights: default_show_guideline_highlights(),
756 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
757 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
758 guideline_highlight_limit: default_guideline_highlight_limit(),
759 usage_tips: default_usage_tips(),
760 recommended_actions: default_recommended_actions(),
761 chat_placeholder: None,
762 }
763 }
764}
765
766#[inline]
767const fn default_onboarding_enabled() -> bool {
768 true
769}
770
771const DEFAULT_INTRO_TEXT: &str =
772 "Let's get oriented. I preloaded workspace context so we can move fast.";
773
774#[inline]
775fn default_intro_text() -> String {
776 DEFAULT_INTRO_TEXT.into()
777}
778
779#[inline]
780const fn default_show_project_overview() -> bool {
781 true
782}
783
784#[inline]
785const fn default_show_language_summary() -> bool {
786 false
787}
788
789#[inline]
790const fn default_show_guideline_highlights() -> bool {
791 true
792}
793
794#[inline]
795const fn default_show_usage_tips_in_welcome() -> bool {
796 false
797}
798
799#[inline]
800const fn default_show_recommended_actions_in_welcome() -> bool {
801 false
802}
803
804#[inline]
805const fn default_guideline_highlight_limit() -> usize {
806 3
807}
808
809const DEFAULT_USAGE_TIPS: &[&str] = &[
810 "Describe your current coding goal or ask for a quick status overview.",
811 "Reference AGENTS.md guidelines when proposing changes.",
812 "Prefer asking for targeted file reads or diffs before editing.",
813];
814
815const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
816 "Review the highlighted guidelines and share the task you want to tackle.",
817 "Ask for a workspace tour if you need more context.",
818];
819
820fn default_usage_tips() -> Vec<String> {
821 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
822}
823
824fn default_recommended_actions() -> Vec<String> {
825 DEFAULT_RECOMMENDED_ACTIONS
826 .iter()
827 .map(|s| (*s).into())
828 .collect()
829}
830
831#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
841#[derive(Debug, Clone, Deserialize, Serialize)]
842pub struct AgentSmallModelConfig {
843 #[serde(default = "default_small_model_enabled")]
845 pub enabled: bool,
846
847 #[serde(default)]
850 pub model: String,
851
852 #[serde(default = "default_small_model_temperature")]
854 pub temperature: f32,
855
856 #[serde(default = "default_small_model_for_large_reads")]
858 pub use_for_large_reads: bool,
859
860 #[serde(default = "default_small_model_for_web_summary")]
862 pub use_for_web_summary: bool,
863
864 #[serde(default = "default_small_model_for_git_history")]
866 pub use_for_git_history: bool,
867}
868
869impl Default for AgentSmallModelConfig {
870 fn default() -> Self {
871 Self {
872 enabled: default_small_model_enabled(),
873 model: String::new(),
874 temperature: default_small_model_temperature(),
875 use_for_large_reads: default_small_model_for_large_reads(),
876 use_for_web_summary: default_small_model_for_web_summary(),
877 use_for_git_history: default_small_model_for_git_history(),
878 }
879 }
880}
881
882#[inline]
883const fn default_small_model_enabled() -> bool {
884 true }
886
887#[inline]
888const fn default_small_model_temperature() -> f32 {
889 0.3 }
891
892#[inline]
893const fn default_small_model_for_large_reads() -> bool {
894 true
895}
896
897#[inline]
898const fn default_small_model_for_web_summary() -> bool {
899 true
900}
901
902#[inline]
903const fn default_small_model_for_git_history() -> bool {
904 true
905}
906
907#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
912#[derive(Debug, Clone, Deserialize, Serialize)]
913pub struct AgentVibeCodingConfig {
914 #[serde(default = "default_vibe_coding_enabled")]
916 pub enabled: bool,
917
918 #[serde(default = "default_vibe_min_prompt_length")]
920 pub min_prompt_length: usize,
921
922 #[serde(default = "default_vibe_min_prompt_words")]
924 pub min_prompt_words: usize,
925
926 #[serde(default = "default_vibe_entity_resolution")]
928 pub enable_entity_resolution: bool,
929
930 #[serde(default = "default_vibe_entity_cache")]
932 pub entity_index_cache: String,
933
934 #[serde(default = "default_vibe_max_entity_matches")]
936 pub max_entity_matches: usize,
937
938 #[serde(default = "default_vibe_track_workspace")]
940 pub track_workspace_state: bool,
941
942 #[serde(default = "default_vibe_max_recent_files")]
944 pub max_recent_files: usize,
945
946 #[serde(default = "default_vibe_track_values")]
948 pub track_value_history: bool,
949
950 #[serde(default = "default_vibe_conversation_memory")]
952 pub enable_conversation_memory: bool,
953
954 #[serde(default = "default_vibe_max_memory_turns")]
956 pub max_memory_turns: usize,
957
958 #[serde(default = "default_vibe_pronoun_resolution")]
960 pub enable_pronoun_resolution: bool,
961
962 #[serde(default = "default_vibe_proactive_context")]
964 pub enable_proactive_context: bool,
965
966 #[serde(default = "default_vibe_max_context_files")]
968 pub max_context_files: usize,
969
970 #[serde(default = "default_vibe_max_snippets_per_file")]
972 pub max_context_snippets_per_file: usize,
973
974 #[serde(default = "default_vibe_max_search_results")]
976 pub max_search_results: usize,
977
978 #[serde(default = "default_vibe_value_inference")]
980 pub enable_relative_value_inference: bool,
981}
982
983impl Default for AgentVibeCodingConfig {
984 fn default() -> Self {
985 Self {
986 enabled: default_vibe_coding_enabled(),
987 min_prompt_length: default_vibe_min_prompt_length(),
988 min_prompt_words: default_vibe_min_prompt_words(),
989 enable_entity_resolution: default_vibe_entity_resolution(),
990 entity_index_cache: default_vibe_entity_cache(),
991 max_entity_matches: default_vibe_max_entity_matches(),
992 track_workspace_state: default_vibe_track_workspace(),
993 max_recent_files: default_vibe_max_recent_files(),
994 track_value_history: default_vibe_track_values(),
995 enable_conversation_memory: default_vibe_conversation_memory(),
996 max_memory_turns: default_vibe_max_memory_turns(),
997 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
998 enable_proactive_context: default_vibe_proactive_context(),
999 max_context_files: default_vibe_max_context_files(),
1000 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1001 max_search_results: default_vibe_max_search_results(),
1002 enable_relative_value_inference: default_vibe_value_inference(),
1003 }
1004 }
1005}
1006
1007#[inline]
1009const fn default_vibe_coding_enabled() -> bool {
1010 false }
1012
1013#[inline]
1014const fn default_vibe_min_prompt_length() -> usize {
1015 5
1016}
1017
1018#[inline]
1019const fn default_vibe_min_prompt_words() -> usize {
1020 2
1021}
1022
1023#[inline]
1024const fn default_vibe_entity_resolution() -> bool {
1025 true
1026}
1027
1028#[inline]
1029fn default_vibe_entity_cache() -> String {
1030 ".vtcode/entity_index.json".into()
1031}
1032
1033#[inline]
1034const fn default_vibe_max_entity_matches() -> usize {
1035 5
1036}
1037
1038#[inline]
1039const fn default_vibe_track_workspace() -> bool {
1040 true
1041}
1042
1043#[inline]
1044const fn default_vibe_max_recent_files() -> usize {
1045 20
1046}
1047
1048#[inline]
1049const fn default_vibe_track_values() -> bool {
1050 true
1051}
1052
1053#[inline]
1054const fn default_vibe_conversation_memory() -> bool {
1055 true
1056}
1057
1058#[inline]
1059const fn default_vibe_max_memory_turns() -> usize {
1060 50
1061}
1062
1063#[inline]
1064const fn default_vibe_pronoun_resolution() -> bool {
1065 true
1066}
1067
1068#[inline]
1069const fn default_vibe_proactive_context() -> bool {
1070 true
1071}
1072
1073#[inline]
1074const fn default_vibe_max_context_files() -> usize {
1075 3
1076}
1077
1078#[inline]
1079const fn default_vibe_max_snippets_per_file() -> usize {
1080 20
1081}
1082
1083#[inline]
1084const fn default_vibe_max_search_results() -> usize {
1085 5
1086}
1087
1088#[inline]
1089const fn default_vibe_value_inference() -> bool {
1090 true
1091}
1092
1093#[cfg(test)]
1094mod tests {
1095 use super::*;
1096
1097 #[test]
1098 fn test_continuation_policy_defaults_and_parses() {
1099 assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::ExecOnly);
1100 assert_eq!(
1101 ContinuationPolicy::parse("off"),
1102 Some(ContinuationPolicy::Off)
1103 );
1104 assert_eq!(
1105 ContinuationPolicy::parse("exec-only"),
1106 Some(ContinuationPolicy::ExecOnly)
1107 );
1108 assert_eq!(
1109 ContinuationPolicy::parse("all"),
1110 Some(ContinuationPolicy::All)
1111 );
1112 assert_eq!(ContinuationPolicy::parse("invalid"), None);
1113 }
1114
1115 #[test]
1116 fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1117 let parsed: AgentHarnessConfig =
1118 toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1119 assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1120
1121 let fallback: AgentHarnessConfig =
1122 toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1123 assert_eq!(fallback.continuation_policy, ContinuationPolicy::ExecOnly);
1124 }
1125
1126 #[test]
1127 fn test_editing_mode_config_default() {
1128 let config = AgentConfig::default();
1129 assert_eq!(config.default_editing_mode, EditingMode::Edit);
1130 assert!(config.require_plan_confirmation);
1131 assert!(!config.autonomous_mode);
1132 }
1133
1134 #[test]
1135 fn test_structured_reasoning_defaults_follow_prompt_mode() {
1136 let default_mode = AgentConfig {
1137 system_prompt_mode: SystemPromptMode::Default,
1138 ..Default::default()
1139 };
1140 assert!(default_mode.should_include_structured_reasoning_tags());
1141
1142 let specialized_mode = AgentConfig {
1143 system_prompt_mode: SystemPromptMode::Specialized,
1144 ..Default::default()
1145 };
1146 assert!(specialized_mode.should_include_structured_reasoning_tags());
1147
1148 let minimal_mode = AgentConfig {
1149 system_prompt_mode: SystemPromptMode::Minimal,
1150 ..Default::default()
1151 };
1152 assert!(!minimal_mode.should_include_structured_reasoning_tags());
1153
1154 let lightweight_mode = AgentConfig {
1155 system_prompt_mode: SystemPromptMode::Lightweight,
1156 ..Default::default()
1157 };
1158 assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1159 }
1160
1161 #[test]
1162 fn test_structured_reasoning_explicit_override() {
1163 let mut config = AgentConfig {
1164 system_prompt_mode: SystemPromptMode::Minimal,
1165 include_structured_reasoning_tags: Some(true),
1166 ..AgentConfig::default()
1167 };
1168 assert!(config.should_include_structured_reasoning_tags());
1169
1170 config.include_structured_reasoning_tags = Some(false);
1171 assert!(!config.should_include_structured_reasoning_tags());
1172 }
1173}