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