1use crate::constants::{defaults, llm_generation, prompt_budget};
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 prompt_suggestions: AgentPromptSuggestionsConfig,
120
121 #[serde(default)]
123 pub onboarding: AgentOnboardingConfig,
124
125 #[serde(default = "default_project_doc_max_bytes")]
127 pub project_doc_max_bytes: usize,
128
129 #[serde(default)]
131 pub project_doc_fallback_filenames: Vec<String>,
132
133 #[serde(
135 default = "default_instruction_max_bytes",
136 alias = "rule_doc_max_bytes"
137 )]
138 pub instruction_max_bytes: usize,
139
140 #[serde(default, alias = "instruction_paths", alias = "instructions")]
142 pub instruction_files: Vec<String>,
143
144 #[serde(default, skip_serializing)]
150 pub custom_api_keys: BTreeMap<String, String>,
151
152 #[serde(default)]
159 pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
160
161 #[serde(default)]
163 pub checkpointing: AgentCheckpointingConfig,
164
165 #[serde(default)]
167 pub vibe_coding: AgentVibeCodingConfig,
168
169 #[serde(default = "default_max_task_retries")]
173 pub max_task_retries: u32,
174
175 #[serde(default)]
177 pub harness: AgentHarnessConfig,
178
179 #[serde(default = "default_include_temporal_context")]
182 pub include_temporal_context: bool,
183
184 #[serde(default)]
186 pub temporal_context_use_utc: bool,
187
188 #[serde(default = "default_include_working_directory")]
190 pub include_working_directory: bool,
191
192 #[serde(default)]
202 pub include_structured_reasoning_tags: Option<bool>,
203
204 #[serde(default)]
206 pub user_instructions: Option<String>,
207
208 #[serde(default)]
211 pub default_editing_mode: EditingMode,
212
213 #[serde(default = "default_require_plan_confirmation")]
217 pub require_plan_confirmation: bool,
218
219 #[serde(default = "default_autonomous_mode")]
223 pub autonomous_mode: bool,
224
225 #[serde(default)]
228 pub circuit_breaker: CircuitBreakerConfig,
229
230 #[serde(default)]
233 pub open_responses: OpenResponsesConfig,
234}
235
236#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
237#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
238#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
239#[serde(rename_all = "snake_case")]
240pub enum ContinuationPolicy {
241 Off,
242 ExecOnly,
243 #[default]
244 All,
245}
246
247impl ContinuationPolicy {
248 pub fn as_str(&self) -> &'static str {
249 match self {
250 Self::Off => "off",
251 Self::ExecOnly => "exec_only",
252 Self::All => "all",
253 }
254 }
255
256 pub fn parse(value: &str) -> Option<Self> {
257 let normalized = value.trim();
258 if normalized.eq_ignore_ascii_case("off") {
259 Some(Self::Off)
260 } else if normalized.eq_ignore_ascii_case("exec_only")
261 || normalized.eq_ignore_ascii_case("exec-only")
262 {
263 Some(Self::ExecOnly)
264 } else if normalized.eq_ignore_ascii_case("all") {
265 Some(Self::All)
266 } else {
267 None
268 }
269 }
270}
271
272impl<'de> Deserialize<'de> for ContinuationPolicy {
273 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
274 where
275 D: serde::Deserializer<'de>,
276 {
277 let raw = String::deserialize(deserializer)?;
278 Ok(Self::parse(&raw).unwrap_or_default())
279 }
280}
281
282#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
283#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
284#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
285#[serde(rename_all = "snake_case")]
286pub enum HarnessOrchestrationMode {
287 #[default]
288 Single,
289 PlanBuildEvaluate,
290}
291
292impl HarnessOrchestrationMode {
293 pub fn as_str(&self) -> &'static str {
294 match self {
295 Self::Single => "single",
296 Self::PlanBuildEvaluate => "plan_build_evaluate",
297 }
298 }
299
300 pub fn parse(value: &str) -> Option<Self> {
301 let normalized = value.trim();
302 if normalized.eq_ignore_ascii_case("single") {
303 Some(Self::Single)
304 } else if normalized.eq_ignore_ascii_case("plan_build_evaluate")
305 || normalized.eq_ignore_ascii_case("plan-build-evaluate")
306 || normalized.eq_ignore_ascii_case("planner_generator_evaluator")
307 || normalized.eq_ignore_ascii_case("planner-generator-evaluator")
308 {
309 Some(Self::PlanBuildEvaluate)
310 } else {
311 None
312 }
313 }
314}
315
316impl<'de> Deserialize<'de> for HarnessOrchestrationMode {
317 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
318 where
319 D: serde::Deserializer<'de>,
320 {
321 let raw = String::deserialize(deserializer)?;
322 Ok(Self::parse(&raw).unwrap_or_default())
323 }
324}
325
326#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
327#[derive(Debug, Clone, Deserialize, Serialize)]
328pub struct AgentHarnessConfig {
329 #[serde(default = "default_harness_max_tool_calls_per_turn")]
331 pub max_tool_calls_per_turn: usize,
332 #[serde(default = "default_harness_max_tool_wall_clock_secs")]
334 pub max_tool_wall_clock_secs: u64,
335 #[serde(default = "default_harness_max_tool_retries")]
337 pub max_tool_retries: u32,
338 #[serde(default = "default_harness_auto_compaction_enabled")]
342 pub auto_compaction_enabled: bool,
343 #[serde(default)]
347 pub auto_compaction_threshold_tokens: Option<u64>,
348 #[serde(default)]
350 pub max_budget_usd: Option<f64>,
351 #[serde(default)]
353 pub continuation_policy: ContinuationPolicy,
354 #[serde(default)]
357 pub event_log_path: Option<String>,
358 #[serde(default)]
360 pub orchestration_mode: HarnessOrchestrationMode,
361 #[serde(default = "default_harness_max_revision_rounds")]
363 pub max_revision_rounds: usize,
364}
365
366impl Default for AgentHarnessConfig {
367 fn default() -> Self {
368 Self {
369 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
370 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
371 max_tool_retries: default_harness_max_tool_retries(),
372 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
373 auto_compaction_threshold_tokens: None,
374 max_budget_usd: None,
375 continuation_policy: ContinuationPolicy::default(),
376 event_log_path: None,
377 orchestration_mode: HarnessOrchestrationMode::default(),
378 max_revision_rounds: default_harness_max_revision_rounds(),
379 }
380 }
381}
382
383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384#[derive(Debug, Clone, Deserialize, Serialize)]
385pub struct CircuitBreakerConfig {
386 #[serde(default = "default_circuit_breaker_enabled")]
388 pub enabled: bool,
389
390 #[serde(default = "default_failure_threshold")]
392 pub failure_threshold: u32,
393
394 #[serde(default = "default_pause_on_open")]
396 pub pause_on_open: bool,
397
398 #[serde(default = "default_max_open_circuits")]
400 pub max_open_circuits: usize,
401
402 #[serde(default = "default_recovery_cooldown")]
404 pub recovery_cooldown: u64,
405}
406
407impl Default for CircuitBreakerConfig {
408 fn default() -> Self {
409 Self {
410 enabled: default_circuit_breaker_enabled(),
411 failure_threshold: default_failure_threshold(),
412 pause_on_open: default_pause_on_open(),
413 max_open_circuits: default_max_open_circuits(),
414 recovery_cooldown: default_recovery_cooldown(),
415 }
416 }
417}
418
419#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
425#[derive(Debug, Clone, Deserialize, Serialize)]
426pub struct OpenResponsesConfig {
427 #[serde(default)]
431 pub enabled: bool,
432
433 #[serde(default = "default_open_responses_emit_events")]
437 pub emit_events: bool,
438
439 #[serde(default = "default_open_responses_include_extensions")]
442 pub include_extensions: bool,
443
444 #[serde(default = "default_open_responses_map_tool_calls")]
447 pub map_tool_calls: bool,
448
449 #[serde(default = "default_open_responses_include_reasoning")]
452 pub include_reasoning: bool,
453}
454
455impl Default for OpenResponsesConfig {
456 fn default() -> Self {
457 Self {
458 enabled: false, emit_events: default_open_responses_emit_events(),
460 include_extensions: default_open_responses_include_extensions(),
461 map_tool_calls: default_open_responses_map_tool_calls(),
462 include_reasoning: default_open_responses_include_reasoning(),
463 }
464 }
465}
466
467#[inline]
468const fn default_open_responses_emit_events() -> bool {
469 true }
471
472#[inline]
473const fn default_open_responses_include_extensions() -> bool {
474 true }
476
477#[inline]
478const fn default_open_responses_map_tool_calls() -> bool {
479 true }
481
482#[inline]
483const fn default_open_responses_include_reasoning() -> bool {
484 true }
486
487impl Default for AgentConfig {
488 fn default() -> Self {
489 Self {
490 provider: default_provider(),
491 api_key_env: default_api_key_env(),
492 default_model: default_model(),
493 theme: default_theme(),
494 system_prompt_mode: SystemPromptMode::default(),
495 tool_documentation_mode: ToolDocumentationMode::default(),
496 enable_split_tool_results: default_enable_split_tool_results(),
497 todo_planning_mode: default_todo_planning_mode(),
498 ui_surface: UiSurfacePreference::default(),
499 max_conversation_turns: default_max_conversation_turns(),
500 reasoning_effort: default_reasoning_effort(),
501 verbosity: default_verbosity(),
502 temperature: default_temperature(),
503 refine_temperature: default_refine_temperature(),
504 enable_self_review: default_enable_self_review(),
505 max_review_passes: default_max_review_passes(),
506 refine_prompts_enabled: default_refine_prompts_enabled(),
507 refine_prompts_max_passes: default_refine_max_passes(),
508 refine_prompts_model: String::new(),
509 small_model: AgentSmallModelConfig::default(),
510 prompt_suggestions: AgentPromptSuggestionsConfig::default(),
511 onboarding: AgentOnboardingConfig::default(),
512 project_doc_max_bytes: default_project_doc_max_bytes(),
513 project_doc_fallback_filenames: Vec::new(),
514 instruction_max_bytes: default_instruction_max_bytes(),
515 instruction_files: Vec::new(),
516 custom_api_keys: BTreeMap::new(),
517 credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
518 checkpointing: AgentCheckpointingConfig::default(),
519 vibe_coding: AgentVibeCodingConfig::default(),
520 max_task_retries: default_max_task_retries(),
521 harness: AgentHarnessConfig::default(),
522 include_temporal_context: default_include_temporal_context(),
523 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
525 include_structured_reasoning_tags: None,
526 user_instructions: None,
527 default_editing_mode: EditingMode::default(),
528 require_plan_confirmation: default_require_plan_confirmation(),
529 autonomous_mode: default_autonomous_mode(),
530 circuit_breaker: CircuitBreakerConfig::default(),
531 open_responses: OpenResponsesConfig::default(),
532 }
533 }
534}
535
536impl AgentConfig {
537 pub fn should_include_structured_reasoning_tags(&self) -> bool {
539 self.include_structured_reasoning_tags.unwrap_or(matches!(
540 self.system_prompt_mode,
541 SystemPromptMode::Default | SystemPromptMode::Specialized
542 ))
543 }
544
545 pub fn validate_llm_params(&self) -> Result<(), String> {
547 if !(0.0..=1.0).contains(&self.temperature) {
549 return Err(format!(
550 "temperature must be between 0.0 and 1.0, got {}",
551 self.temperature
552 ));
553 }
554
555 if !(0.0..=1.0).contains(&self.refine_temperature) {
556 return Err(format!(
557 "refine_temperature must be between 0.0 and 1.0, got {}",
558 self.refine_temperature
559 ));
560 }
561
562 Ok(())
563 }
564}
565
566#[inline]
568fn default_provider() -> String {
569 defaults::DEFAULT_PROVIDER.into()
570}
571
572#[inline]
573fn default_api_key_env() -> String {
574 defaults::DEFAULT_API_KEY_ENV.into()
575}
576
577#[inline]
578fn default_model() -> String {
579 defaults::DEFAULT_MODEL.into()
580}
581
582#[inline]
583fn default_theme() -> String {
584 defaults::DEFAULT_THEME.into()
585}
586
587#[inline]
588const fn default_todo_planning_mode() -> bool {
589 true
590}
591
592#[inline]
593const fn default_enable_split_tool_results() -> bool {
594 true }
596
597#[inline]
598const fn default_max_conversation_turns() -> usize {
599 defaults::DEFAULT_MAX_CONVERSATION_TURNS
600}
601
602#[inline]
603fn default_reasoning_effort() -> ReasoningEffortLevel {
604 ReasoningEffortLevel::None
605}
606
607#[inline]
608fn default_verbosity() -> VerbosityLevel {
609 VerbosityLevel::default()
610}
611
612#[inline]
613const fn default_temperature() -> f32 {
614 llm_generation::DEFAULT_TEMPERATURE
615}
616
617#[inline]
618const fn default_refine_temperature() -> f32 {
619 llm_generation::DEFAULT_REFINE_TEMPERATURE
620}
621
622#[inline]
623const fn default_enable_self_review() -> bool {
624 false
625}
626
627#[inline]
628const fn default_max_review_passes() -> usize {
629 1
630}
631
632#[inline]
633const fn default_refine_prompts_enabled() -> bool {
634 false
635}
636
637#[inline]
638const fn default_refine_max_passes() -> usize {
639 1
640}
641
642#[inline]
643const fn default_project_doc_max_bytes() -> usize {
644 prompt_budget::DEFAULT_MAX_BYTES
645}
646
647#[inline]
648const fn default_instruction_max_bytes() -> usize {
649 prompt_budget::DEFAULT_MAX_BYTES
650}
651
652#[inline]
653const fn default_max_task_retries() -> u32 {
654 2 }
656
657#[inline]
658const fn default_harness_max_tool_calls_per_turn() -> usize {
659 defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
660}
661
662#[inline]
663const fn default_harness_max_tool_wall_clock_secs() -> u64 {
664 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
665}
666
667#[inline]
668const fn default_harness_max_tool_retries() -> u32 {
669 defaults::DEFAULT_MAX_TOOL_RETRIES
670}
671
672#[inline]
673const fn default_harness_auto_compaction_enabled() -> bool {
674 false
675}
676
677#[inline]
678const fn default_harness_max_revision_rounds() -> usize {
679 2
680}
681
682#[inline]
683const fn default_include_temporal_context() -> bool {
684 true }
686
687#[inline]
688const fn default_include_working_directory() -> bool {
689 true }
691
692#[inline]
693const fn default_require_plan_confirmation() -> bool {
694 true }
696
697#[inline]
698const fn default_autonomous_mode() -> bool {
699 false }
701
702#[inline]
703const fn default_circuit_breaker_enabled() -> bool {
704 true }
706
707#[inline]
708const fn default_failure_threshold() -> u32 {
709 7 }
711
712#[inline]
713const fn default_pause_on_open() -> bool {
714 true }
716
717#[inline]
718const fn default_max_open_circuits() -> usize {
719 3 }
721
722#[inline]
723const fn default_recovery_cooldown() -> u64 {
724 60 }
726
727#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
728#[derive(Debug, Clone, Deserialize, Serialize)]
729pub struct AgentCheckpointingConfig {
730 #[serde(default = "default_checkpointing_enabled")]
732 pub enabled: bool,
733
734 #[serde(default)]
736 pub storage_dir: Option<String>,
737
738 #[serde(default = "default_checkpointing_max_snapshots")]
740 pub max_snapshots: usize,
741
742 #[serde(default = "default_checkpointing_max_age_days")]
744 pub max_age_days: Option<u64>,
745}
746
747impl Default for AgentCheckpointingConfig {
748 fn default() -> Self {
749 Self {
750 enabled: default_checkpointing_enabled(),
751 storage_dir: None,
752 max_snapshots: default_checkpointing_max_snapshots(),
753 max_age_days: default_checkpointing_max_age_days(),
754 }
755 }
756}
757
758#[inline]
759const fn default_checkpointing_enabled() -> bool {
760 DEFAULT_CHECKPOINTS_ENABLED
761}
762
763#[inline]
764const fn default_checkpointing_max_snapshots() -> usize {
765 DEFAULT_MAX_SNAPSHOTS
766}
767
768#[inline]
769const fn default_checkpointing_max_age_days() -> Option<u64> {
770 Some(DEFAULT_MAX_AGE_DAYS)
771}
772
773#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
774#[derive(Debug, Clone, Deserialize, Serialize)]
775pub struct AgentOnboardingConfig {
776 #[serde(default = "default_onboarding_enabled")]
778 pub enabled: bool,
779
780 #[serde(default = "default_intro_text")]
782 pub intro_text: String,
783
784 #[serde(default = "default_show_project_overview")]
786 pub include_project_overview: bool,
787
788 #[serde(default = "default_show_language_summary")]
790 pub include_language_summary: bool,
791
792 #[serde(default = "default_show_guideline_highlights")]
794 pub include_guideline_highlights: bool,
795
796 #[serde(default = "default_show_usage_tips_in_welcome")]
798 pub include_usage_tips_in_welcome: bool,
799
800 #[serde(default = "default_show_recommended_actions_in_welcome")]
802 pub include_recommended_actions_in_welcome: bool,
803
804 #[serde(default = "default_guideline_highlight_limit")]
806 pub guideline_highlight_limit: usize,
807
808 #[serde(default = "default_usage_tips")]
810 pub usage_tips: Vec<String>,
811
812 #[serde(default = "default_recommended_actions")]
814 pub recommended_actions: Vec<String>,
815
816 #[serde(default)]
818 pub chat_placeholder: Option<String>,
819}
820
821impl Default for AgentOnboardingConfig {
822 fn default() -> Self {
823 Self {
824 enabled: default_onboarding_enabled(),
825 intro_text: default_intro_text(),
826 include_project_overview: default_show_project_overview(),
827 include_language_summary: default_show_language_summary(),
828 include_guideline_highlights: default_show_guideline_highlights(),
829 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
830 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
831 guideline_highlight_limit: default_guideline_highlight_limit(),
832 usage_tips: default_usage_tips(),
833 recommended_actions: default_recommended_actions(),
834 chat_placeholder: None,
835 }
836 }
837}
838
839#[inline]
840const fn default_onboarding_enabled() -> bool {
841 true
842}
843
844const DEFAULT_INTRO_TEXT: &str =
845 "Let's get oriented. I preloaded workspace context so we can move fast.";
846
847#[inline]
848fn default_intro_text() -> String {
849 DEFAULT_INTRO_TEXT.into()
850}
851
852#[inline]
853const fn default_show_project_overview() -> bool {
854 true
855}
856
857#[inline]
858const fn default_show_language_summary() -> bool {
859 false
860}
861
862#[inline]
863const fn default_show_guideline_highlights() -> bool {
864 true
865}
866
867#[inline]
868const fn default_show_usage_tips_in_welcome() -> bool {
869 false
870}
871
872#[inline]
873const fn default_show_recommended_actions_in_welcome() -> bool {
874 false
875}
876
877#[inline]
878const fn default_guideline_highlight_limit() -> usize {
879 3
880}
881
882const DEFAULT_USAGE_TIPS: &[&str] = &[
883 "Describe your current coding goal or ask for a quick status overview.",
884 "Reference AGENTS.md guidelines when proposing changes.",
885 "Prefer asking for targeted file reads or diffs before editing.",
886];
887
888const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
889 "Review the highlighted guidelines and share the task you want to tackle.",
890 "Ask for a workspace tour if you need more context.",
891];
892
893fn default_usage_tips() -> Vec<String> {
894 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
895}
896
897fn default_recommended_actions() -> Vec<String> {
898 DEFAULT_RECOMMENDED_ACTIONS
899 .iter()
900 .map(|s| (*s).into())
901 .collect()
902}
903
904#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
914#[derive(Debug, Clone, Deserialize, Serialize)]
915pub struct AgentSmallModelConfig {
916 #[serde(default = "default_small_model_enabled")]
918 pub enabled: bool,
919
920 #[serde(default)]
923 pub model: String,
924
925 #[serde(default = "default_small_model_temperature")]
927 pub temperature: f32,
928
929 #[serde(default = "default_small_model_for_large_reads")]
931 pub use_for_large_reads: bool,
932
933 #[serde(default = "default_small_model_for_web_summary")]
935 pub use_for_web_summary: bool,
936
937 #[serde(default = "default_small_model_for_git_history")]
939 pub use_for_git_history: bool,
940}
941
942impl Default for AgentSmallModelConfig {
943 fn default() -> Self {
944 Self {
945 enabled: default_small_model_enabled(),
946 model: String::new(),
947 temperature: default_small_model_temperature(),
948 use_for_large_reads: default_small_model_for_large_reads(),
949 use_for_web_summary: default_small_model_for_web_summary(),
950 use_for_git_history: default_small_model_for_git_history(),
951 }
952 }
953}
954
955#[inline]
956const fn default_small_model_enabled() -> bool {
957 true }
959
960#[inline]
961const fn default_small_model_temperature() -> f32 {
962 0.3 }
964
965#[inline]
966const fn default_small_model_for_large_reads() -> bool {
967 true
968}
969
970#[inline]
971const fn default_small_model_for_web_summary() -> bool {
972 true
973}
974
975#[inline]
976const fn default_small_model_for_git_history() -> bool {
977 true
978}
979
980#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
982#[derive(Debug, Clone, Deserialize, Serialize)]
983pub struct AgentPromptSuggestionsConfig {
984 #[serde(default = "default_prompt_suggestions_enabled")]
986 pub enabled: bool,
987
988 #[serde(default)]
991 pub model: String,
992
993 #[serde(default = "default_prompt_suggestions_temperature")]
995 pub temperature: f32,
996
997 #[serde(default = "default_prompt_suggestions_show_cost_notice")]
999 pub show_cost_notice: bool,
1000}
1001
1002impl Default for AgentPromptSuggestionsConfig {
1003 fn default() -> Self {
1004 Self {
1005 enabled: default_prompt_suggestions_enabled(),
1006 model: String::new(),
1007 temperature: default_prompt_suggestions_temperature(),
1008 show_cost_notice: default_prompt_suggestions_show_cost_notice(),
1009 }
1010 }
1011}
1012
1013#[inline]
1014const fn default_prompt_suggestions_enabled() -> bool {
1015 true
1016}
1017
1018#[inline]
1019const fn default_prompt_suggestions_temperature() -> f32 {
1020 0.3
1021}
1022
1023#[inline]
1024const fn default_prompt_suggestions_show_cost_notice() -> bool {
1025 true
1026}
1027
1028#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1033#[derive(Debug, Clone, Deserialize, Serialize)]
1034pub struct AgentVibeCodingConfig {
1035 #[serde(default = "default_vibe_coding_enabled")]
1037 pub enabled: bool,
1038
1039 #[serde(default = "default_vibe_min_prompt_length")]
1041 pub min_prompt_length: usize,
1042
1043 #[serde(default = "default_vibe_min_prompt_words")]
1045 pub min_prompt_words: usize,
1046
1047 #[serde(default = "default_vibe_entity_resolution")]
1049 pub enable_entity_resolution: bool,
1050
1051 #[serde(default = "default_vibe_entity_cache")]
1053 pub entity_index_cache: String,
1054
1055 #[serde(default = "default_vibe_max_entity_matches")]
1057 pub max_entity_matches: usize,
1058
1059 #[serde(default = "default_vibe_track_workspace")]
1061 pub track_workspace_state: bool,
1062
1063 #[serde(default = "default_vibe_max_recent_files")]
1065 pub max_recent_files: usize,
1066
1067 #[serde(default = "default_vibe_track_values")]
1069 pub track_value_history: bool,
1070
1071 #[serde(default = "default_vibe_conversation_memory")]
1073 pub enable_conversation_memory: bool,
1074
1075 #[serde(default = "default_vibe_max_memory_turns")]
1077 pub max_memory_turns: usize,
1078
1079 #[serde(default = "default_vibe_pronoun_resolution")]
1081 pub enable_pronoun_resolution: bool,
1082
1083 #[serde(default = "default_vibe_proactive_context")]
1085 pub enable_proactive_context: bool,
1086
1087 #[serde(default = "default_vibe_max_context_files")]
1089 pub max_context_files: usize,
1090
1091 #[serde(default = "default_vibe_max_snippets_per_file")]
1093 pub max_context_snippets_per_file: usize,
1094
1095 #[serde(default = "default_vibe_max_search_results")]
1097 pub max_search_results: usize,
1098
1099 #[serde(default = "default_vibe_value_inference")]
1101 pub enable_relative_value_inference: bool,
1102}
1103
1104impl Default for AgentVibeCodingConfig {
1105 fn default() -> Self {
1106 Self {
1107 enabled: default_vibe_coding_enabled(),
1108 min_prompt_length: default_vibe_min_prompt_length(),
1109 min_prompt_words: default_vibe_min_prompt_words(),
1110 enable_entity_resolution: default_vibe_entity_resolution(),
1111 entity_index_cache: default_vibe_entity_cache(),
1112 max_entity_matches: default_vibe_max_entity_matches(),
1113 track_workspace_state: default_vibe_track_workspace(),
1114 max_recent_files: default_vibe_max_recent_files(),
1115 track_value_history: default_vibe_track_values(),
1116 enable_conversation_memory: default_vibe_conversation_memory(),
1117 max_memory_turns: default_vibe_max_memory_turns(),
1118 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
1119 enable_proactive_context: default_vibe_proactive_context(),
1120 max_context_files: default_vibe_max_context_files(),
1121 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1122 max_search_results: default_vibe_max_search_results(),
1123 enable_relative_value_inference: default_vibe_value_inference(),
1124 }
1125 }
1126}
1127
1128#[inline]
1130const fn default_vibe_coding_enabled() -> bool {
1131 false }
1133
1134#[inline]
1135const fn default_vibe_min_prompt_length() -> usize {
1136 5
1137}
1138
1139#[inline]
1140const fn default_vibe_min_prompt_words() -> usize {
1141 2
1142}
1143
1144#[inline]
1145const fn default_vibe_entity_resolution() -> bool {
1146 true
1147}
1148
1149#[inline]
1150fn default_vibe_entity_cache() -> String {
1151 ".vtcode/entity_index.json".into()
1152}
1153
1154#[inline]
1155const fn default_vibe_max_entity_matches() -> usize {
1156 5
1157}
1158
1159#[inline]
1160const fn default_vibe_track_workspace() -> bool {
1161 true
1162}
1163
1164#[inline]
1165const fn default_vibe_max_recent_files() -> usize {
1166 20
1167}
1168
1169#[inline]
1170const fn default_vibe_track_values() -> bool {
1171 true
1172}
1173
1174#[inline]
1175const fn default_vibe_conversation_memory() -> bool {
1176 true
1177}
1178
1179#[inline]
1180const fn default_vibe_max_memory_turns() -> usize {
1181 50
1182}
1183
1184#[inline]
1185const fn default_vibe_pronoun_resolution() -> bool {
1186 true
1187}
1188
1189#[inline]
1190const fn default_vibe_proactive_context() -> bool {
1191 true
1192}
1193
1194#[inline]
1195const fn default_vibe_max_context_files() -> usize {
1196 3
1197}
1198
1199#[inline]
1200const fn default_vibe_max_snippets_per_file() -> usize {
1201 20
1202}
1203
1204#[inline]
1205const fn default_vibe_max_search_results() -> usize {
1206 5
1207}
1208
1209#[inline]
1210const fn default_vibe_value_inference() -> bool {
1211 true
1212}
1213
1214#[cfg(test)]
1215mod tests {
1216 use super::*;
1217
1218 #[test]
1219 fn test_continuation_policy_defaults_and_parses() {
1220 assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
1221 assert_eq!(
1222 ContinuationPolicy::parse("off"),
1223 Some(ContinuationPolicy::Off)
1224 );
1225 assert_eq!(
1226 ContinuationPolicy::parse("exec-only"),
1227 Some(ContinuationPolicy::ExecOnly)
1228 );
1229 assert_eq!(
1230 ContinuationPolicy::parse("all"),
1231 Some(ContinuationPolicy::All)
1232 );
1233 assert_eq!(ContinuationPolicy::parse("invalid"), None);
1234 }
1235
1236 #[test]
1237 fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1238 let parsed: AgentHarnessConfig =
1239 toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1240 assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1241
1242 let fallback: AgentHarnessConfig =
1243 toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1244 assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
1245 }
1246
1247 #[test]
1248 fn test_harness_orchestration_mode_defaults_and_parses() {
1249 assert_eq!(
1250 HarnessOrchestrationMode::default(),
1251 HarnessOrchestrationMode::Single
1252 );
1253 assert_eq!(
1254 HarnessOrchestrationMode::parse("single"),
1255 Some(HarnessOrchestrationMode::Single)
1256 );
1257 assert_eq!(
1258 HarnessOrchestrationMode::parse("plan_build_evaluate"),
1259 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1260 );
1261 assert_eq!(
1262 HarnessOrchestrationMode::parse("planner-generator-evaluator"),
1263 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1264 );
1265 assert_eq!(HarnessOrchestrationMode::parse("unexpected"), None);
1266 }
1267
1268 #[test]
1269 fn test_harness_config_orchestration_deserializes_with_fallback() {
1270 let parsed: AgentHarnessConfig =
1271 toml::from_str("orchestration_mode = \"plan_build_evaluate\"")
1272 .expect("valid harness config");
1273 assert_eq!(
1274 parsed.orchestration_mode,
1275 HarnessOrchestrationMode::PlanBuildEvaluate
1276 );
1277 assert_eq!(parsed.max_revision_rounds, 2);
1278
1279 let fallback: AgentHarnessConfig =
1280 toml::from_str("orchestration_mode = \"unexpected\"").expect("fallback config");
1281 assert_eq!(
1282 fallback.orchestration_mode,
1283 HarnessOrchestrationMode::Single
1284 );
1285 }
1286
1287 #[test]
1288 fn test_editing_mode_config_default() {
1289 let config = AgentConfig::default();
1290 assert_eq!(config.default_editing_mode, EditingMode::Edit);
1291 assert!(config.require_plan_confirmation);
1292 assert!(!config.autonomous_mode);
1293 }
1294
1295 #[test]
1296 fn test_structured_reasoning_defaults_follow_prompt_mode() {
1297 let default_mode = AgentConfig {
1298 system_prompt_mode: SystemPromptMode::Default,
1299 ..Default::default()
1300 };
1301 assert!(default_mode.should_include_structured_reasoning_tags());
1302
1303 let specialized_mode = AgentConfig {
1304 system_prompt_mode: SystemPromptMode::Specialized,
1305 ..Default::default()
1306 };
1307 assert!(specialized_mode.should_include_structured_reasoning_tags());
1308
1309 let minimal_mode = AgentConfig {
1310 system_prompt_mode: SystemPromptMode::Minimal,
1311 ..Default::default()
1312 };
1313 assert!(!minimal_mode.should_include_structured_reasoning_tags());
1314
1315 let lightweight_mode = AgentConfig {
1316 system_prompt_mode: SystemPromptMode::Lightweight,
1317 ..Default::default()
1318 };
1319 assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1320 }
1321
1322 #[test]
1323 fn test_structured_reasoning_explicit_override() {
1324 let mut config = AgentConfig {
1325 system_prompt_mode: SystemPromptMode::Minimal,
1326 include_structured_reasoning_tags: Some(true),
1327 ..AgentConfig::default()
1328 };
1329 assert!(config.should_include_structured_reasoning_tags());
1330
1331 config.include_structured_reasoning_tags = Some(false);
1332 assert!(!config.should_include_structured_reasoning_tags());
1333 }
1334}