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