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 ExecOnly,
234 #[default]
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)]
301 pub event_log_path: Option<String>,
302}
303
304impl Default for AgentHarnessConfig {
305 fn default() -> Self {
306 Self {
307 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
308 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
309 max_tool_retries: default_harness_max_tool_retries(),
310 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
311 auto_compaction_threshold_tokens: None,
312 continuation_policy: ContinuationPolicy::default(),
313 event_log_path: None,
314 }
315 }
316}
317
318#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
319#[derive(Debug, Clone, Deserialize, Serialize)]
320pub struct CircuitBreakerConfig {
321 #[serde(default = "default_circuit_breaker_enabled")]
323 pub enabled: bool,
324
325 #[serde(default = "default_failure_threshold")]
327 pub failure_threshold: u32,
328
329 #[serde(default = "default_pause_on_open")]
331 pub pause_on_open: bool,
332
333 #[serde(default = "default_max_open_circuits")]
335 pub max_open_circuits: usize,
336
337 #[serde(default = "default_recovery_cooldown")]
339 pub recovery_cooldown: u64,
340}
341
342impl Default for CircuitBreakerConfig {
343 fn default() -> Self {
344 Self {
345 enabled: default_circuit_breaker_enabled(),
346 failure_threshold: default_failure_threshold(),
347 pause_on_open: default_pause_on_open(),
348 max_open_circuits: default_max_open_circuits(),
349 recovery_cooldown: default_recovery_cooldown(),
350 }
351 }
352}
353
354#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
360#[derive(Debug, Clone, Deserialize, Serialize)]
361pub struct OpenResponsesConfig {
362 #[serde(default)]
366 pub enabled: bool,
367
368 #[serde(default = "default_open_responses_emit_events")]
372 pub emit_events: bool,
373
374 #[serde(default = "default_open_responses_include_extensions")]
377 pub include_extensions: bool,
378
379 #[serde(default = "default_open_responses_map_tool_calls")]
382 pub map_tool_calls: bool,
383
384 #[serde(default = "default_open_responses_include_reasoning")]
387 pub include_reasoning: bool,
388}
389
390impl Default for OpenResponsesConfig {
391 fn default() -> Self {
392 Self {
393 enabled: false, emit_events: default_open_responses_emit_events(),
395 include_extensions: default_open_responses_include_extensions(),
396 map_tool_calls: default_open_responses_map_tool_calls(),
397 include_reasoning: default_open_responses_include_reasoning(),
398 }
399 }
400}
401
402#[inline]
403const fn default_open_responses_emit_events() -> bool {
404 true }
406
407#[inline]
408const fn default_open_responses_include_extensions() -> bool {
409 true }
411
412#[inline]
413const fn default_open_responses_map_tool_calls() -> bool {
414 true }
416
417#[inline]
418const fn default_open_responses_include_reasoning() -> bool {
419 true }
421
422impl Default for AgentConfig {
423 fn default() -> Self {
424 Self {
425 provider: default_provider(),
426 api_key_env: default_api_key_env(),
427 default_model: default_model(),
428 theme: default_theme(),
429 system_prompt_mode: SystemPromptMode::default(),
430 tool_documentation_mode: ToolDocumentationMode::default(),
431 enable_split_tool_results: default_enable_split_tool_results(),
432 todo_planning_mode: default_todo_planning_mode(),
433 ui_surface: UiSurfacePreference::default(),
434 max_conversation_turns: default_max_conversation_turns(),
435 reasoning_effort: default_reasoning_effort(),
436 verbosity: default_verbosity(),
437 temperature: default_temperature(),
438 refine_temperature: default_refine_temperature(),
439 enable_self_review: default_enable_self_review(),
440 max_review_passes: default_max_review_passes(),
441 refine_prompts_enabled: default_refine_prompts_enabled(),
442 refine_prompts_max_passes: default_refine_max_passes(),
443 refine_prompts_model: String::new(),
444 small_model: AgentSmallModelConfig::default(),
445 onboarding: AgentOnboardingConfig::default(),
446 project_doc_max_bytes: default_project_doc_max_bytes(),
447 instruction_max_bytes: default_instruction_max_bytes(),
448 instruction_files: Vec::new(),
449 custom_api_keys: BTreeMap::new(),
450 credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
451 checkpointing: AgentCheckpointingConfig::default(),
452 vibe_coding: AgentVibeCodingConfig::default(),
453 max_task_retries: default_max_task_retries(),
454 harness: AgentHarnessConfig::default(),
455 include_temporal_context: default_include_temporal_context(),
456 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
458 include_structured_reasoning_tags: None,
459 user_instructions: None,
460 default_editing_mode: EditingMode::default(),
461 require_plan_confirmation: default_require_plan_confirmation(),
462 autonomous_mode: default_autonomous_mode(),
463 circuit_breaker: CircuitBreakerConfig::default(),
464 open_responses: OpenResponsesConfig::default(),
465 }
466 }
467}
468
469impl AgentConfig {
470 pub fn should_include_structured_reasoning_tags(&self) -> bool {
472 self.include_structured_reasoning_tags.unwrap_or(matches!(
473 self.system_prompt_mode,
474 SystemPromptMode::Default | SystemPromptMode::Specialized
475 ))
476 }
477
478 pub fn validate_llm_params(&self) -> Result<(), String> {
480 if !(0.0..=1.0).contains(&self.temperature) {
482 return Err(format!(
483 "temperature must be between 0.0 and 1.0, got {}",
484 self.temperature
485 ));
486 }
487
488 if !(0.0..=1.0).contains(&self.refine_temperature) {
489 return Err(format!(
490 "refine_temperature must be between 0.0 and 1.0, got {}",
491 self.refine_temperature
492 ));
493 }
494
495 Ok(())
496 }
497}
498
499#[inline]
501fn default_provider() -> String {
502 defaults::DEFAULT_PROVIDER.into()
503}
504
505#[inline]
506fn default_api_key_env() -> String {
507 defaults::DEFAULT_API_KEY_ENV.into()
508}
509
510#[inline]
511fn default_model() -> String {
512 defaults::DEFAULT_MODEL.into()
513}
514
515#[inline]
516fn default_theme() -> String {
517 defaults::DEFAULT_THEME.into()
518}
519
520#[inline]
521const fn default_todo_planning_mode() -> bool {
522 true
523}
524
525#[inline]
526const fn default_enable_split_tool_results() -> bool {
527 true }
529
530#[inline]
531const fn default_max_conversation_turns() -> usize {
532 defaults::DEFAULT_MAX_CONVERSATION_TURNS
533}
534
535#[inline]
536fn default_reasoning_effort() -> ReasoningEffortLevel {
537 ReasoningEffortLevel::None
538}
539
540#[inline]
541fn default_verbosity() -> VerbosityLevel {
542 VerbosityLevel::default()
543}
544
545#[inline]
546const fn default_temperature() -> f32 {
547 llm_generation::DEFAULT_TEMPERATURE
548}
549
550#[inline]
551const fn default_refine_temperature() -> f32 {
552 llm_generation::DEFAULT_REFINE_TEMPERATURE
553}
554
555#[inline]
556const fn default_enable_self_review() -> bool {
557 false
558}
559
560#[inline]
561const fn default_max_review_passes() -> usize {
562 1
563}
564
565#[inline]
566const fn default_refine_prompts_enabled() -> bool {
567 false
568}
569
570#[inline]
571const fn default_refine_max_passes() -> usize {
572 1
573}
574
575#[inline]
576const fn default_project_doc_max_bytes() -> usize {
577 project_doc::DEFAULT_MAX_BYTES
578}
579
580#[inline]
581const fn default_instruction_max_bytes() -> usize {
582 instructions::DEFAULT_MAX_BYTES
583}
584
585#[inline]
586const fn default_max_task_retries() -> u32 {
587 2 }
589
590#[inline]
591const fn default_harness_max_tool_calls_per_turn() -> usize {
592 defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
593}
594
595#[inline]
596const fn default_harness_max_tool_wall_clock_secs() -> u64 {
597 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
598}
599
600#[inline]
601const fn default_harness_max_tool_retries() -> u32 {
602 defaults::DEFAULT_MAX_TOOL_RETRIES
603}
604
605#[inline]
606const fn default_harness_auto_compaction_enabled() -> bool {
607 false
608}
609
610#[inline]
611const fn default_include_temporal_context() -> bool {
612 true }
614
615#[inline]
616const fn default_include_working_directory() -> bool {
617 true }
619
620#[inline]
621const fn default_require_plan_confirmation() -> bool {
622 true }
624
625#[inline]
626const fn default_autonomous_mode() -> bool {
627 false }
629
630#[inline]
631const fn default_circuit_breaker_enabled() -> bool {
632 true }
634
635#[inline]
636const fn default_failure_threshold() -> u32 {
637 5 }
639
640#[inline]
641const fn default_pause_on_open() -> bool {
642 true }
644
645#[inline]
646const fn default_max_open_circuits() -> usize {
647 3 }
649
650#[inline]
651const fn default_recovery_cooldown() -> u64 {
652 60 }
654
655#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
656#[derive(Debug, Clone, Deserialize, Serialize)]
657pub struct AgentCheckpointingConfig {
658 #[serde(default = "default_checkpointing_enabled")]
660 pub enabled: bool,
661
662 #[serde(default)]
664 pub storage_dir: Option<String>,
665
666 #[serde(default = "default_checkpointing_max_snapshots")]
668 pub max_snapshots: usize,
669
670 #[serde(default = "default_checkpointing_max_age_days")]
672 pub max_age_days: Option<u64>,
673}
674
675impl Default for AgentCheckpointingConfig {
676 fn default() -> Self {
677 Self {
678 enabled: default_checkpointing_enabled(),
679 storage_dir: None,
680 max_snapshots: default_checkpointing_max_snapshots(),
681 max_age_days: default_checkpointing_max_age_days(),
682 }
683 }
684}
685
686#[inline]
687const fn default_checkpointing_enabled() -> bool {
688 DEFAULT_CHECKPOINTS_ENABLED
689}
690
691#[inline]
692const fn default_checkpointing_max_snapshots() -> usize {
693 DEFAULT_MAX_SNAPSHOTS
694}
695
696#[inline]
697const fn default_checkpointing_max_age_days() -> Option<u64> {
698 Some(DEFAULT_MAX_AGE_DAYS)
699}
700
701#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
702#[derive(Debug, Clone, Deserialize, Serialize)]
703pub struct AgentOnboardingConfig {
704 #[serde(default = "default_onboarding_enabled")]
706 pub enabled: bool,
707
708 #[serde(default = "default_intro_text")]
710 pub intro_text: String,
711
712 #[serde(default = "default_show_project_overview")]
714 pub include_project_overview: bool,
715
716 #[serde(default = "default_show_language_summary")]
718 pub include_language_summary: bool,
719
720 #[serde(default = "default_show_guideline_highlights")]
722 pub include_guideline_highlights: bool,
723
724 #[serde(default = "default_show_usage_tips_in_welcome")]
726 pub include_usage_tips_in_welcome: bool,
727
728 #[serde(default = "default_show_recommended_actions_in_welcome")]
730 pub include_recommended_actions_in_welcome: bool,
731
732 #[serde(default = "default_guideline_highlight_limit")]
734 pub guideline_highlight_limit: usize,
735
736 #[serde(default = "default_usage_tips")]
738 pub usage_tips: Vec<String>,
739
740 #[serde(default = "default_recommended_actions")]
742 pub recommended_actions: Vec<String>,
743
744 #[serde(default)]
746 pub chat_placeholder: Option<String>,
747}
748
749impl Default for AgentOnboardingConfig {
750 fn default() -> Self {
751 Self {
752 enabled: default_onboarding_enabled(),
753 intro_text: default_intro_text(),
754 include_project_overview: default_show_project_overview(),
755 include_language_summary: default_show_language_summary(),
756 include_guideline_highlights: default_show_guideline_highlights(),
757 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
758 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
759 guideline_highlight_limit: default_guideline_highlight_limit(),
760 usage_tips: default_usage_tips(),
761 recommended_actions: default_recommended_actions(),
762 chat_placeholder: None,
763 }
764 }
765}
766
767#[inline]
768const fn default_onboarding_enabled() -> bool {
769 true
770}
771
772const DEFAULT_INTRO_TEXT: &str =
773 "Let's get oriented. I preloaded workspace context so we can move fast.";
774
775#[inline]
776fn default_intro_text() -> String {
777 DEFAULT_INTRO_TEXT.into()
778}
779
780#[inline]
781const fn default_show_project_overview() -> bool {
782 true
783}
784
785#[inline]
786const fn default_show_language_summary() -> bool {
787 false
788}
789
790#[inline]
791const fn default_show_guideline_highlights() -> bool {
792 true
793}
794
795#[inline]
796const fn default_show_usage_tips_in_welcome() -> bool {
797 false
798}
799
800#[inline]
801const fn default_show_recommended_actions_in_welcome() -> bool {
802 false
803}
804
805#[inline]
806const fn default_guideline_highlight_limit() -> usize {
807 3
808}
809
810const DEFAULT_USAGE_TIPS: &[&str] = &[
811 "Describe your current coding goal or ask for a quick status overview.",
812 "Reference AGENTS.md guidelines when proposing changes.",
813 "Prefer asking for targeted file reads or diffs before editing.",
814];
815
816const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
817 "Review the highlighted guidelines and share the task you want to tackle.",
818 "Ask for a workspace tour if you need more context.",
819];
820
821fn default_usage_tips() -> Vec<String> {
822 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
823}
824
825fn default_recommended_actions() -> Vec<String> {
826 DEFAULT_RECOMMENDED_ACTIONS
827 .iter()
828 .map(|s| (*s).into())
829 .collect()
830}
831
832#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
842#[derive(Debug, Clone, Deserialize, Serialize)]
843pub struct AgentSmallModelConfig {
844 #[serde(default = "default_small_model_enabled")]
846 pub enabled: bool,
847
848 #[serde(default)]
851 pub model: String,
852
853 #[serde(default = "default_small_model_temperature")]
855 pub temperature: f32,
856
857 #[serde(default = "default_small_model_for_large_reads")]
859 pub use_for_large_reads: bool,
860
861 #[serde(default = "default_small_model_for_web_summary")]
863 pub use_for_web_summary: bool,
864
865 #[serde(default = "default_small_model_for_git_history")]
867 pub use_for_git_history: bool,
868}
869
870impl Default for AgentSmallModelConfig {
871 fn default() -> Self {
872 Self {
873 enabled: default_small_model_enabled(),
874 model: String::new(),
875 temperature: default_small_model_temperature(),
876 use_for_large_reads: default_small_model_for_large_reads(),
877 use_for_web_summary: default_small_model_for_web_summary(),
878 use_for_git_history: default_small_model_for_git_history(),
879 }
880 }
881}
882
883#[inline]
884const fn default_small_model_enabled() -> bool {
885 true }
887
888#[inline]
889const fn default_small_model_temperature() -> f32 {
890 0.3 }
892
893#[inline]
894const fn default_small_model_for_large_reads() -> bool {
895 true
896}
897
898#[inline]
899const fn default_small_model_for_web_summary() -> bool {
900 true
901}
902
903#[inline]
904const fn default_small_model_for_git_history() -> bool {
905 true
906}
907
908#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
913#[derive(Debug, Clone, Deserialize, Serialize)]
914pub struct AgentVibeCodingConfig {
915 #[serde(default = "default_vibe_coding_enabled")]
917 pub enabled: bool,
918
919 #[serde(default = "default_vibe_min_prompt_length")]
921 pub min_prompt_length: usize,
922
923 #[serde(default = "default_vibe_min_prompt_words")]
925 pub min_prompt_words: usize,
926
927 #[serde(default = "default_vibe_entity_resolution")]
929 pub enable_entity_resolution: bool,
930
931 #[serde(default = "default_vibe_entity_cache")]
933 pub entity_index_cache: String,
934
935 #[serde(default = "default_vibe_max_entity_matches")]
937 pub max_entity_matches: usize,
938
939 #[serde(default = "default_vibe_track_workspace")]
941 pub track_workspace_state: bool,
942
943 #[serde(default = "default_vibe_max_recent_files")]
945 pub max_recent_files: usize,
946
947 #[serde(default = "default_vibe_track_values")]
949 pub track_value_history: bool,
950
951 #[serde(default = "default_vibe_conversation_memory")]
953 pub enable_conversation_memory: bool,
954
955 #[serde(default = "default_vibe_max_memory_turns")]
957 pub max_memory_turns: usize,
958
959 #[serde(default = "default_vibe_pronoun_resolution")]
961 pub enable_pronoun_resolution: bool,
962
963 #[serde(default = "default_vibe_proactive_context")]
965 pub enable_proactive_context: bool,
966
967 #[serde(default = "default_vibe_max_context_files")]
969 pub max_context_files: usize,
970
971 #[serde(default = "default_vibe_max_snippets_per_file")]
973 pub max_context_snippets_per_file: usize,
974
975 #[serde(default = "default_vibe_max_search_results")]
977 pub max_search_results: usize,
978
979 #[serde(default = "default_vibe_value_inference")]
981 pub enable_relative_value_inference: bool,
982}
983
984impl Default for AgentVibeCodingConfig {
985 fn default() -> Self {
986 Self {
987 enabled: default_vibe_coding_enabled(),
988 min_prompt_length: default_vibe_min_prompt_length(),
989 min_prompt_words: default_vibe_min_prompt_words(),
990 enable_entity_resolution: default_vibe_entity_resolution(),
991 entity_index_cache: default_vibe_entity_cache(),
992 max_entity_matches: default_vibe_max_entity_matches(),
993 track_workspace_state: default_vibe_track_workspace(),
994 max_recent_files: default_vibe_max_recent_files(),
995 track_value_history: default_vibe_track_values(),
996 enable_conversation_memory: default_vibe_conversation_memory(),
997 max_memory_turns: default_vibe_max_memory_turns(),
998 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
999 enable_proactive_context: default_vibe_proactive_context(),
1000 max_context_files: default_vibe_max_context_files(),
1001 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1002 max_search_results: default_vibe_max_search_results(),
1003 enable_relative_value_inference: default_vibe_value_inference(),
1004 }
1005 }
1006}
1007
1008#[inline]
1010const fn default_vibe_coding_enabled() -> bool {
1011 false }
1013
1014#[inline]
1015const fn default_vibe_min_prompt_length() -> usize {
1016 5
1017}
1018
1019#[inline]
1020const fn default_vibe_min_prompt_words() -> usize {
1021 2
1022}
1023
1024#[inline]
1025const fn default_vibe_entity_resolution() -> bool {
1026 true
1027}
1028
1029#[inline]
1030fn default_vibe_entity_cache() -> String {
1031 ".vtcode/entity_index.json".into()
1032}
1033
1034#[inline]
1035const fn default_vibe_max_entity_matches() -> usize {
1036 5
1037}
1038
1039#[inline]
1040const fn default_vibe_track_workspace() -> bool {
1041 true
1042}
1043
1044#[inline]
1045const fn default_vibe_max_recent_files() -> usize {
1046 20
1047}
1048
1049#[inline]
1050const fn default_vibe_track_values() -> bool {
1051 true
1052}
1053
1054#[inline]
1055const fn default_vibe_conversation_memory() -> bool {
1056 true
1057}
1058
1059#[inline]
1060const fn default_vibe_max_memory_turns() -> usize {
1061 50
1062}
1063
1064#[inline]
1065const fn default_vibe_pronoun_resolution() -> bool {
1066 true
1067}
1068
1069#[inline]
1070const fn default_vibe_proactive_context() -> bool {
1071 true
1072}
1073
1074#[inline]
1075const fn default_vibe_max_context_files() -> usize {
1076 3
1077}
1078
1079#[inline]
1080const fn default_vibe_max_snippets_per_file() -> usize {
1081 20
1082}
1083
1084#[inline]
1085const fn default_vibe_max_search_results() -> usize {
1086 5
1087}
1088
1089#[inline]
1090const fn default_vibe_value_inference() -> bool {
1091 true
1092}
1093
1094#[cfg(test)]
1095mod tests {
1096 use super::*;
1097
1098 #[test]
1099 fn test_continuation_policy_defaults_and_parses() {
1100 assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
1101 assert_eq!(
1102 ContinuationPolicy::parse("off"),
1103 Some(ContinuationPolicy::Off)
1104 );
1105 assert_eq!(
1106 ContinuationPolicy::parse("exec-only"),
1107 Some(ContinuationPolicy::ExecOnly)
1108 );
1109 assert_eq!(
1110 ContinuationPolicy::parse("all"),
1111 Some(ContinuationPolicy::All)
1112 );
1113 assert_eq!(ContinuationPolicy::parse("invalid"), None);
1114 }
1115
1116 #[test]
1117 fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1118 let parsed: AgentHarnessConfig =
1119 toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1120 assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1121
1122 let fallback: AgentHarnessConfig =
1123 toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1124 assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
1125 }
1126
1127 #[test]
1128 fn test_editing_mode_config_default() {
1129 let config = AgentConfig::default();
1130 assert_eq!(config.default_editing_mode, EditingMode::Edit);
1131 assert!(config.require_plan_confirmation);
1132 assert!(!config.autonomous_mode);
1133 }
1134
1135 #[test]
1136 fn test_structured_reasoning_defaults_follow_prompt_mode() {
1137 let default_mode = AgentConfig {
1138 system_prompt_mode: SystemPromptMode::Default,
1139 ..Default::default()
1140 };
1141 assert!(default_mode.should_include_structured_reasoning_tags());
1142
1143 let specialized_mode = AgentConfig {
1144 system_prompt_mode: SystemPromptMode::Specialized,
1145 ..Default::default()
1146 };
1147 assert!(specialized_mode.should_include_structured_reasoning_tags());
1148
1149 let minimal_mode = AgentConfig {
1150 system_prompt_mode: SystemPromptMode::Minimal,
1151 ..Default::default()
1152 };
1153 assert!(!minimal_mode.should_include_structured_reasoning_tags());
1154
1155 let lightweight_mode = AgentConfig {
1156 system_prompt_mode: SystemPromptMode::Lightweight,
1157 ..Default::default()
1158 };
1159 assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1160 }
1161
1162 #[test]
1163 fn test_structured_reasoning_explicit_override() {
1164 let mut config = AgentConfig {
1165 system_prompt_mode: SystemPromptMode::Minimal,
1166 include_structured_reasoning_tags: Some(true),
1167 ..AgentConfig::default()
1168 };
1169 assert!(config.should_include_structured_reasoning_tags());
1170
1171 config.include_structured_reasoning_tags = Some(false);
1172 assert!(!config.should_include_structured_reasoning_tags());
1173 }
1174}