1use crate::constants::{defaults, llm_generation, prompt_budget};
2use crate::types::{
3 ReasoningEffortLevel, SystemPromptMode, ToolDocumentationMode, UiSurfacePreference,
4 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)]
146 pub instruction_excludes: Vec<String>,
147
148 #[serde(default = "default_instruction_import_max_depth")]
150 pub instruction_import_max_depth: usize,
151
152 #[serde(default)]
154 pub persistent_memory: PersistentMemoryConfig,
155
156 #[serde(default, skip_serializing)]
162 pub custom_api_keys: BTreeMap<String, String>,
163
164 #[serde(default)]
171 pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
172
173 #[serde(default)]
175 pub checkpointing: AgentCheckpointingConfig,
176
177 #[serde(default)]
179 pub vibe_coding: AgentVibeCodingConfig,
180
181 #[serde(default = "default_max_task_retries")]
185 pub max_task_retries: u32,
186
187 #[serde(default)]
189 pub harness: AgentHarnessConfig,
190
191 #[serde(default)]
193 pub codex_app_server: AgentCodexAppServerConfig,
194
195 #[serde(default = "default_include_temporal_context")]
198 pub include_temporal_context: bool,
199
200 #[serde(default)]
202 pub temporal_context_use_utc: bool,
203
204 #[serde(default = "default_include_working_directory")]
206 pub include_working_directory: bool,
207
208 #[serde(default)]
218 pub include_structured_reasoning_tags: Option<bool>,
219
220 #[serde(default)]
222 pub user_instructions: Option<String>,
223
224 #[serde(default = "default_require_plan_confirmation")]
228 pub require_plan_confirmation: bool,
229
230 #[serde(default)]
233 pub circuit_breaker: CircuitBreakerConfig,
234
235 #[serde(default)]
238 pub open_responses: OpenResponsesConfig,
239}
240
241#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
242#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
243#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
244#[serde(rename_all = "snake_case")]
245pub enum ContinuationPolicy {
246 Off,
247 ExecOnly,
248 #[default]
249 All,
250}
251
252impl ContinuationPolicy {
253 pub fn as_str(&self) -> &'static str {
254 match self {
255 Self::Off => "off",
256 Self::ExecOnly => "exec_only",
257 Self::All => "all",
258 }
259 }
260
261 pub fn parse(value: &str) -> Option<Self> {
262 let normalized = value.trim();
263 if normalized.eq_ignore_ascii_case("off") {
264 Some(Self::Off)
265 } else if normalized.eq_ignore_ascii_case("exec_only")
266 || normalized.eq_ignore_ascii_case("exec-only")
267 {
268 Some(Self::ExecOnly)
269 } else if normalized.eq_ignore_ascii_case("all") {
270 Some(Self::All)
271 } else {
272 None
273 }
274 }
275}
276
277impl<'de> Deserialize<'de> for ContinuationPolicy {
278 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
279 where
280 D: serde::Deserializer<'de>,
281 {
282 let raw = String::deserialize(deserializer)?;
283 Ok(Self::parse(&raw).unwrap_or_default())
284 }
285}
286
287#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
288#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
289#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
290#[serde(rename_all = "snake_case")]
291pub enum HarnessOrchestrationMode {
292 #[default]
293 Single,
294 PlanBuildEvaluate,
295}
296
297impl HarnessOrchestrationMode {
298 pub fn as_str(&self) -> &'static str {
299 match self {
300 Self::Single => "single",
301 Self::PlanBuildEvaluate => "plan_build_evaluate",
302 }
303 }
304
305 pub fn parse(value: &str) -> Option<Self> {
306 let normalized = value.trim();
307 if normalized.eq_ignore_ascii_case("single") {
308 Some(Self::Single)
309 } else if normalized.eq_ignore_ascii_case("plan_build_evaluate")
310 || normalized.eq_ignore_ascii_case("plan-build-evaluate")
311 || normalized.eq_ignore_ascii_case("planner_generator_evaluator")
312 || normalized.eq_ignore_ascii_case("planner-generator-evaluator")
313 {
314 Some(Self::PlanBuildEvaluate)
315 } else {
316 None
317 }
318 }
319}
320
321impl<'de> Deserialize<'de> for HarnessOrchestrationMode {
322 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
323 where
324 D: serde::Deserializer<'de>,
325 {
326 let raw = String::deserialize(deserializer)?;
327 Ok(Self::parse(&raw).unwrap_or_default())
328 }
329}
330
331#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
332#[derive(Debug, Clone, Deserialize, Serialize)]
333pub struct AgentHarnessConfig {
334 #[serde(default = "default_harness_max_tool_calls_per_turn")]
336 pub max_tool_calls_per_turn: usize,
337 #[serde(default = "default_harness_max_tool_wall_clock_secs")]
339 pub max_tool_wall_clock_secs: u64,
340 #[serde(default = "default_harness_max_tool_retries")]
342 pub max_tool_retries: u32,
343 #[serde(default = "default_harness_auto_compaction_enabled")]
347 pub auto_compaction_enabled: bool,
348 #[serde(default)]
352 pub auto_compaction_threshold_tokens: Option<u64>,
353 #[serde(default)]
355 pub tool_result_clearing: ToolResultClearingConfig,
356 #[serde(default)]
358 pub max_budget_usd: Option<f64>,
359 #[serde(default)]
361 pub continuation_policy: ContinuationPolicy,
362 #[serde(default)]
365 pub event_log_path: Option<String>,
366 #[serde(default)]
368 pub orchestration_mode: HarnessOrchestrationMode,
369 #[serde(default = "default_harness_max_revision_rounds")]
371 pub max_revision_rounds: usize,
372}
373
374impl Default for AgentHarnessConfig {
375 fn default() -> Self {
376 Self {
377 max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
378 max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
379 max_tool_retries: default_harness_max_tool_retries(),
380 auto_compaction_enabled: default_harness_auto_compaction_enabled(),
381 auto_compaction_threshold_tokens: None,
382 tool_result_clearing: ToolResultClearingConfig::default(),
383 max_budget_usd: None,
384 continuation_policy: ContinuationPolicy::default(),
385 event_log_path: None,
386 orchestration_mode: HarnessOrchestrationMode::default(),
387 max_revision_rounds: default_harness_max_revision_rounds(),
388 }
389 }
390}
391
392#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
393#[derive(Debug, Clone, Deserialize, Serialize)]
394pub struct ToolResultClearingConfig {
395 #[serde(default = "default_tool_result_clearing_enabled")]
396 pub enabled: bool,
397 #[serde(default = "default_tool_result_clearing_trigger_tokens")]
398 pub trigger_tokens: u64,
399 #[serde(default = "default_tool_result_clearing_keep_tool_uses")]
400 pub keep_tool_uses: u32,
401 #[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
402 pub clear_at_least_tokens: u64,
403 #[serde(default)]
404 pub clear_tool_inputs: bool,
405}
406
407impl Default for ToolResultClearingConfig {
408 fn default() -> Self {
409 Self {
410 enabled: default_tool_result_clearing_enabled(),
411 trigger_tokens: default_tool_result_clearing_trigger_tokens(),
412 keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
413 clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
414 clear_tool_inputs: false,
415 }
416 }
417}
418
419impl ToolResultClearingConfig {
420 pub fn validate(&self) -> Result<(), String> {
421 if self.trigger_tokens == 0 {
422 return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
423 }
424 if self.keep_tool_uses == 0 {
425 return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
426 }
427 if self.clear_at_least_tokens == 0 {
428 return Err(
429 "tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string(),
430 );
431 }
432 Ok(())
433 }
434}
435
436#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
437#[derive(Debug, Clone, Deserialize, Serialize)]
438pub struct AgentCodexAppServerConfig {
439 #[serde(default = "default_codex_app_server_command")]
441 pub command: String,
442 #[serde(default = "default_codex_app_server_args")]
444 pub args: Vec<String>,
445 #[serde(default = "default_codex_app_server_startup_timeout_secs")]
447 pub startup_timeout_secs: u64,
448 #[serde(default = "default_codex_app_server_experimental_features")]
451 pub experimental_features: bool,
452}
453
454impl Default for AgentCodexAppServerConfig {
455 fn default() -> Self {
456 Self {
457 command: default_codex_app_server_command(),
458 args: default_codex_app_server_args(),
459 startup_timeout_secs: default_codex_app_server_startup_timeout_secs(),
460 experimental_features: default_codex_app_server_experimental_features(),
461 }
462 }
463}
464
465#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
466#[derive(Debug, Clone, Deserialize, Serialize)]
467pub struct CircuitBreakerConfig {
468 #[serde(default = "default_circuit_breaker_enabled")]
470 pub enabled: bool,
471
472 #[serde(default = "default_failure_threshold")]
474 pub failure_threshold: u32,
475
476 #[serde(default = "default_pause_on_open")]
478 pub pause_on_open: bool,
479
480 #[serde(default = "default_max_open_circuits")]
482 pub max_open_circuits: usize,
483
484 #[serde(default = "default_recovery_cooldown")]
486 pub recovery_cooldown: u64,
487}
488
489impl Default for CircuitBreakerConfig {
490 fn default() -> Self {
491 Self {
492 enabled: default_circuit_breaker_enabled(),
493 failure_threshold: default_failure_threshold(),
494 pause_on_open: default_pause_on_open(),
495 max_open_circuits: default_max_open_circuits(),
496 recovery_cooldown: default_recovery_cooldown(),
497 }
498 }
499}
500
501#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
507#[derive(Debug, Clone, Deserialize, Serialize)]
508pub struct OpenResponsesConfig {
509 #[serde(default)]
513 pub enabled: bool,
514
515 #[serde(default = "default_open_responses_emit_events")]
519 pub emit_events: bool,
520
521 #[serde(default = "default_open_responses_include_extensions")]
524 pub include_extensions: bool,
525
526 #[serde(default = "default_open_responses_map_tool_calls")]
529 pub map_tool_calls: bool,
530
531 #[serde(default = "default_open_responses_include_reasoning")]
534 pub include_reasoning: bool,
535}
536
537impl Default for OpenResponsesConfig {
538 fn default() -> Self {
539 Self {
540 enabled: false, emit_events: default_open_responses_emit_events(),
542 include_extensions: default_open_responses_include_extensions(),
543 map_tool_calls: default_open_responses_map_tool_calls(),
544 include_reasoning: default_open_responses_include_reasoning(),
545 }
546 }
547}
548
549#[inline]
550const fn default_open_responses_emit_events() -> bool {
551 true }
553
554#[inline]
555const fn default_open_responses_include_extensions() -> bool {
556 true }
558
559#[inline]
560const fn default_open_responses_map_tool_calls() -> bool {
561 true }
563
564#[inline]
565const fn default_open_responses_include_reasoning() -> bool {
566 true }
568
569#[inline]
570fn default_codex_app_server_command() -> String {
571 "codex".to_string()
572}
573
574#[inline]
575fn default_codex_app_server_args() -> Vec<String> {
576 vec!["app-server".to_string()]
577}
578
579#[inline]
580const fn default_codex_app_server_startup_timeout_secs() -> u64 {
581 10
582}
583
584#[inline]
585const fn default_codex_app_server_experimental_features() -> bool {
586 false
587}
588
589impl Default for AgentConfig {
590 fn default() -> Self {
591 Self {
592 provider: default_provider(),
593 api_key_env: default_api_key_env(),
594 default_model: default_model(),
595 theme: default_theme(),
596 system_prompt_mode: SystemPromptMode::default(),
597 tool_documentation_mode: ToolDocumentationMode::default(),
598 enable_split_tool_results: default_enable_split_tool_results(),
599 todo_planning_mode: default_todo_planning_mode(),
600 ui_surface: UiSurfacePreference::default(),
601 max_conversation_turns: default_max_conversation_turns(),
602 reasoning_effort: default_reasoning_effort(),
603 verbosity: default_verbosity(),
604 temperature: default_temperature(),
605 refine_temperature: default_refine_temperature(),
606 enable_self_review: default_enable_self_review(),
607 max_review_passes: default_max_review_passes(),
608 refine_prompts_enabled: default_refine_prompts_enabled(),
609 refine_prompts_max_passes: default_refine_max_passes(),
610 refine_prompts_model: String::new(),
611 small_model: AgentSmallModelConfig::default(),
612 prompt_suggestions: AgentPromptSuggestionsConfig::default(),
613 onboarding: AgentOnboardingConfig::default(),
614 project_doc_max_bytes: default_project_doc_max_bytes(),
615 project_doc_fallback_filenames: Vec::new(),
616 instruction_max_bytes: default_instruction_max_bytes(),
617 instruction_files: Vec::new(),
618 instruction_excludes: Vec::new(),
619 instruction_import_max_depth: default_instruction_import_max_depth(),
620 persistent_memory: PersistentMemoryConfig::default(),
621 custom_api_keys: BTreeMap::new(),
622 credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
623 checkpointing: AgentCheckpointingConfig::default(),
624 vibe_coding: AgentVibeCodingConfig::default(),
625 max_task_retries: default_max_task_retries(),
626 harness: AgentHarnessConfig::default(),
627 codex_app_server: AgentCodexAppServerConfig::default(),
628 include_temporal_context: default_include_temporal_context(),
629 temporal_context_use_utc: false, include_working_directory: default_include_working_directory(),
631 include_structured_reasoning_tags: None,
632 user_instructions: None,
633 require_plan_confirmation: default_require_plan_confirmation(),
634 circuit_breaker: CircuitBreakerConfig::default(),
635 open_responses: OpenResponsesConfig::default(),
636 }
637 }
638}
639
640impl AgentConfig {
641 pub fn should_include_structured_reasoning_tags(&self) -> bool {
643 self.include_structured_reasoning_tags.unwrap_or(matches!(
644 self.system_prompt_mode,
645 SystemPromptMode::Specialized
646 ))
647 }
648
649 pub fn validate_llm_params(&self) -> Result<(), String> {
651 if !(0.0..=1.0).contains(&self.temperature) {
653 return Err(format!(
654 "temperature must be between 0.0 and 1.0, got {}",
655 self.temperature
656 ));
657 }
658
659 if !(0.0..=1.0).contains(&self.refine_temperature) {
660 return Err(format!(
661 "refine_temperature must be between 0.0 and 1.0, got {}",
662 self.refine_temperature
663 ));
664 }
665
666 if self.instruction_import_max_depth == 0 {
667 return Err("instruction_import_max_depth must be greater than 0".to_string());
668 }
669
670 self.persistent_memory.validate()?;
671 self.harness.tool_result_clearing.validate()?;
672
673 Ok(())
674 }
675}
676
677#[inline]
679fn default_provider() -> String {
680 defaults::DEFAULT_PROVIDER.into()
681}
682
683#[inline]
684fn default_api_key_env() -> String {
685 defaults::DEFAULT_API_KEY_ENV.into()
686}
687
688#[inline]
689fn default_model() -> String {
690 defaults::DEFAULT_MODEL.into()
691}
692
693#[inline]
694fn default_theme() -> String {
695 defaults::DEFAULT_THEME.into()
696}
697
698#[inline]
699const fn default_todo_planning_mode() -> bool {
700 true
701}
702
703#[inline]
704const fn default_enable_split_tool_results() -> bool {
705 true }
707
708#[inline]
709const fn default_max_conversation_turns() -> usize {
710 defaults::DEFAULT_MAX_CONVERSATION_TURNS
711}
712
713#[inline]
714fn default_reasoning_effort() -> ReasoningEffortLevel {
715 ReasoningEffortLevel::None
716}
717
718#[inline]
719fn default_verbosity() -> VerbosityLevel {
720 VerbosityLevel::default()
721}
722
723#[inline]
724const fn default_temperature() -> f32 {
725 llm_generation::DEFAULT_TEMPERATURE
726}
727
728#[inline]
729const fn default_refine_temperature() -> f32 {
730 llm_generation::DEFAULT_REFINE_TEMPERATURE
731}
732
733#[inline]
734const fn default_enable_self_review() -> bool {
735 false
736}
737
738#[inline]
739const fn default_max_review_passes() -> usize {
740 1
741}
742
743#[inline]
744const fn default_refine_prompts_enabled() -> bool {
745 false
746}
747
748#[inline]
749const fn default_refine_max_passes() -> usize {
750 1
751}
752
753#[inline]
754const fn default_project_doc_max_bytes() -> usize {
755 prompt_budget::DEFAULT_MAX_BYTES
756}
757
758#[inline]
759const fn default_instruction_max_bytes() -> usize {
760 prompt_budget::DEFAULT_MAX_BYTES
761}
762
763#[inline]
764const fn default_instruction_import_max_depth() -> usize {
765 5
766}
767
768#[inline]
769const fn default_max_task_retries() -> u32 {
770 2 }
772
773#[inline]
774const fn default_harness_max_tool_calls_per_turn() -> usize {
775 defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
776}
777
778#[inline]
779const fn default_harness_max_tool_wall_clock_secs() -> u64 {
780 defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
781}
782
783#[inline]
784const fn default_harness_max_tool_retries() -> u32 {
785 defaults::DEFAULT_MAX_TOOL_RETRIES
786}
787
788#[inline]
789const fn default_harness_auto_compaction_enabled() -> bool {
790 false
791}
792
793#[inline]
794const fn default_tool_result_clearing_enabled() -> bool {
795 false
796}
797
798#[inline]
799const fn default_tool_result_clearing_trigger_tokens() -> u64 {
800 100_000
801}
802
803#[inline]
804const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
805 3
806}
807
808#[inline]
809const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
810 30_000
811}
812
813#[inline]
814const fn default_harness_max_revision_rounds() -> usize {
815 2
816}
817
818#[inline]
819const fn default_include_temporal_context() -> bool {
820 true }
822
823#[inline]
824const fn default_include_working_directory() -> bool {
825 true }
827
828#[inline]
829const fn default_require_plan_confirmation() -> bool {
830 true }
832
833#[inline]
834const fn default_circuit_breaker_enabled() -> bool {
835 true }
837
838#[inline]
839const fn default_failure_threshold() -> u32 {
840 7 }
842
843#[inline]
844const fn default_pause_on_open() -> bool {
845 true }
847
848#[inline]
849const fn default_max_open_circuits() -> usize {
850 3 }
852
853#[inline]
854const fn default_recovery_cooldown() -> u64 {
855 60 }
857
858#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
859#[derive(Debug, Clone, Deserialize, Serialize)]
860pub struct AgentCheckpointingConfig {
861 #[serde(default = "default_checkpointing_enabled")]
863 pub enabled: bool,
864
865 #[serde(default)]
867 pub storage_dir: Option<String>,
868
869 #[serde(default = "default_checkpointing_max_snapshots")]
871 pub max_snapshots: usize,
872
873 #[serde(default = "default_checkpointing_max_age_days")]
875 pub max_age_days: Option<u64>,
876}
877
878impl Default for AgentCheckpointingConfig {
879 fn default() -> Self {
880 Self {
881 enabled: default_checkpointing_enabled(),
882 storage_dir: None,
883 max_snapshots: default_checkpointing_max_snapshots(),
884 max_age_days: default_checkpointing_max_age_days(),
885 }
886 }
887}
888
889#[inline]
890const fn default_checkpointing_enabled() -> bool {
891 DEFAULT_CHECKPOINTS_ENABLED
892}
893
894#[inline]
895const fn default_checkpointing_max_snapshots() -> usize {
896 DEFAULT_MAX_SNAPSHOTS
897}
898
899#[inline]
900const fn default_checkpointing_max_age_days() -> Option<u64> {
901 Some(DEFAULT_MAX_AGE_DAYS)
902}
903
904#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
909#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
910pub struct MemoriesConfig {
911 #[serde(default = "default_memories_generate")]
914 pub generate_memories: bool,
915
916 #[serde(default = "default_memories_use")]
918 pub use_memories: bool,
919
920 #[serde(default)]
922 pub extract_model: Option<String>,
923
924 #[serde(default)]
926 pub consolidation_model: Option<String>,
927}
928
929impl Default for MemoriesConfig {
930 fn default() -> Self {
931 Self {
932 generate_memories: default_memories_generate(),
933 use_memories: default_memories_use(),
934 extract_model: None,
935 consolidation_model: None,
936 }
937 }
938}
939
940#[inline]
941const fn default_memories_generate() -> bool {
942 true
943}
944
945#[inline]
946const fn default_memories_use() -> bool {
947 true
948}
949
950#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
951#[derive(Debug, Clone, Deserialize, Serialize)]
952pub struct PersistentMemoryConfig {
953 #[serde(default = "default_persistent_memory_enabled")]
955 pub enabled: bool,
956
957 #[serde(default = "default_persistent_memory_auto_write")]
959 pub auto_write: bool,
960
961 #[serde(default)]
963 pub directory_override: Option<String>,
964
965 #[serde(default = "default_persistent_memory_startup_line_limit")]
967 pub startup_line_limit: usize,
968
969 #[serde(default = "default_persistent_memory_startup_byte_limit")]
971 pub startup_byte_limit: usize,
972
973 #[serde(default)]
975 pub memories: MemoriesConfig,
976}
977
978impl Default for PersistentMemoryConfig {
979 fn default() -> Self {
980 Self {
981 enabled: default_persistent_memory_enabled(),
982 auto_write: default_persistent_memory_auto_write(),
983 directory_override: None,
984 startup_line_limit: default_persistent_memory_startup_line_limit(),
985 startup_byte_limit: default_persistent_memory_startup_byte_limit(),
986 memories: MemoriesConfig::default(),
987 }
988 }
989}
990
991impl PersistentMemoryConfig {
992 pub fn validate(&self) -> Result<(), String> {
993 if self.startup_line_limit == 0 {
994 return Err("persistent_memory.startup_line_limit must be greater than 0".to_string());
995 }
996
997 if self.startup_byte_limit == 0 {
998 return Err("persistent_memory.startup_byte_limit must be greater than 0".to_string());
999 }
1000
1001 Ok(())
1002 }
1003}
1004
1005#[inline]
1006const fn default_persistent_memory_enabled() -> bool {
1007 false
1008}
1009
1010#[inline]
1011const fn default_persistent_memory_auto_write() -> bool {
1012 true
1013}
1014
1015#[inline]
1016const fn default_persistent_memory_startup_line_limit() -> usize {
1017 200
1018}
1019
1020#[inline]
1021const fn default_persistent_memory_startup_byte_limit() -> usize {
1022 25 * 1024
1023}
1024
1025#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1026#[derive(Debug, Clone, Deserialize, Serialize)]
1027pub struct AgentOnboardingConfig {
1028 #[serde(default = "default_onboarding_enabled")]
1030 pub enabled: bool,
1031
1032 #[serde(default = "default_intro_text")]
1034 pub intro_text: String,
1035
1036 #[serde(default = "default_show_project_overview")]
1038 pub include_project_overview: bool,
1039
1040 #[serde(default = "default_show_language_summary")]
1042 pub include_language_summary: bool,
1043
1044 #[serde(default = "default_show_guideline_highlights")]
1046 pub include_guideline_highlights: bool,
1047
1048 #[serde(default = "default_show_usage_tips_in_welcome")]
1050 pub include_usage_tips_in_welcome: bool,
1051
1052 #[serde(default = "default_show_recommended_actions_in_welcome")]
1054 pub include_recommended_actions_in_welcome: bool,
1055
1056 #[serde(default = "default_guideline_highlight_limit")]
1058 pub guideline_highlight_limit: usize,
1059
1060 #[serde(default = "default_usage_tips")]
1062 pub usage_tips: Vec<String>,
1063
1064 #[serde(default = "default_recommended_actions")]
1066 pub recommended_actions: Vec<String>,
1067
1068 #[serde(default)]
1070 pub chat_placeholder: Option<String>,
1071}
1072
1073impl Default for AgentOnboardingConfig {
1074 fn default() -> Self {
1075 Self {
1076 enabled: default_onboarding_enabled(),
1077 intro_text: default_intro_text(),
1078 include_project_overview: default_show_project_overview(),
1079 include_language_summary: default_show_language_summary(),
1080 include_guideline_highlights: default_show_guideline_highlights(),
1081 include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
1082 include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
1083 guideline_highlight_limit: default_guideline_highlight_limit(),
1084 usage_tips: default_usage_tips(),
1085 recommended_actions: default_recommended_actions(),
1086 chat_placeholder: None,
1087 }
1088 }
1089}
1090
1091#[inline]
1092const fn default_onboarding_enabled() -> bool {
1093 true
1094}
1095
1096const DEFAULT_INTRO_TEXT: &str =
1097 "Let's get oriented. I preloaded workspace context so we can move fast.";
1098
1099#[inline]
1100fn default_intro_text() -> String {
1101 DEFAULT_INTRO_TEXT.into()
1102}
1103
1104#[inline]
1105const fn default_show_project_overview() -> bool {
1106 true
1107}
1108
1109#[inline]
1110const fn default_show_language_summary() -> bool {
1111 false
1112}
1113
1114#[inline]
1115const fn default_show_guideline_highlights() -> bool {
1116 true
1117}
1118
1119#[inline]
1120const fn default_show_usage_tips_in_welcome() -> bool {
1121 false
1122}
1123
1124#[inline]
1125const fn default_show_recommended_actions_in_welcome() -> bool {
1126 false
1127}
1128
1129#[inline]
1130const fn default_guideline_highlight_limit() -> usize {
1131 3
1132}
1133
1134const DEFAULT_USAGE_TIPS: &[&str] = &[
1135 "Describe your current coding goal or ask for a quick status overview.",
1136 "Reference AGENTS.md guidelines when proposing changes.",
1137 "Prefer asking for targeted file reads or diffs before editing.",
1138];
1139
1140const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
1141 "Review the highlighted guidelines and share the task you want to tackle.",
1142 "Ask for a workspace tour if you need more context.",
1143];
1144
1145fn default_usage_tips() -> Vec<String> {
1146 DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
1147}
1148
1149fn default_recommended_actions() -> Vec<String> {
1150 DEFAULT_RECOMMENDED_ACTIONS
1151 .iter()
1152 .map(|s| (*s).into())
1153 .collect()
1154}
1155
1156#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1166#[derive(Debug, Clone, Deserialize, Serialize)]
1167pub struct AgentSmallModelConfig {
1168 #[serde(default = "default_small_model_enabled")]
1170 pub enabled: bool,
1171
1172 #[serde(default)]
1175 pub model: String,
1176
1177 #[serde(default = "default_small_model_temperature")]
1179 pub temperature: f32,
1180
1181 #[serde(default = "default_small_model_for_large_reads")]
1183 pub use_for_large_reads: bool,
1184
1185 #[serde(default = "default_small_model_for_web_summary")]
1187 pub use_for_web_summary: bool,
1188
1189 #[serde(default = "default_small_model_for_git_history")]
1191 pub use_for_git_history: bool,
1192
1193 #[serde(default = "default_small_model_for_memory")]
1195 pub use_for_memory: bool,
1196}
1197
1198impl Default for AgentSmallModelConfig {
1199 fn default() -> Self {
1200 Self {
1201 enabled: default_small_model_enabled(),
1202 model: String::new(),
1203 temperature: default_small_model_temperature(),
1204 use_for_large_reads: default_small_model_for_large_reads(),
1205 use_for_web_summary: default_small_model_for_web_summary(),
1206 use_for_git_history: default_small_model_for_git_history(),
1207 use_for_memory: default_small_model_for_memory(),
1208 }
1209 }
1210}
1211
1212#[inline]
1213const fn default_small_model_enabled() -> bool {
1214 true }
1216
1217#[inline]
1218const fn default_small_model_temperature() -> f32 {
1219 0.3 }
1221
1222#[inline]
1223const fn default_small_model_for_large_reads() -> bool {
1224 true
1225}
1226
1227#[inline]
1228const fn default_small_model_for_web_summary() -> bool {
1229 true
1230}
1231
1232#[inline]
1233const fn default_small_model_for_git_history() -> bool {
1234 true
1235}
1236
1237#[inline]
1238const fn default_small_model_for_memory() -> bool {
1239 true
1240}
1241
1242#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1244#[derive(Debug, Clone, Deserialize, Serialize)]
1245pub struct AgentPromptSuggestionsConfig {
1246 #[serde(default = "default_prompt_suggestions_enabled")]
1248 pub enabled: bool,
1249
1250 #[serde(default)]
1253 pub model: String,
1254
1255 #[serde(default = "default_prompt_suggestions_temperature")]
1257 pub temperature: f32,
1258
1259 #[serde(default = "default_prompt_suggestions_show_cost_notice")]
1261 pub show_cost_notice: bool,
1262}
1263
1264impl Default for AgentPromptSuggestionsConfig {
1265 fn default() -> Self {
1266 Self {
1267 enabled: default_prompt_suggestions_enabled(),
1268 model: String::new(),
1269 temperature: default_prompt_suggestions_temperature(),
1270 show_cost_notice: default_prompt_suggestions_show_cost_notice(),
1271 }
1272 }
1273}
1274
1275#[inline]
1276const fn default_prompt_suggestions_enabled() -> bool {
1277 true
1278}
1279
1280#[inline]
1281const fn default_prompt_suggestions_temperature() -> f32 {
1282 0.3
1283}
1284
1285#[inline]
1286const fn default_prompt_suggestions_show_cost_notice() -> bool {
1287 true
1288}
1289
1290#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1295#[derive(Debug, Clone, Deserialize, Serialize)]
1296pub struct AgentVibeCodingConfig {
1297 #[serde(default = "default_vibe_coding_enabled")]
1299 pub enabled: bool,
1300
1301 #[serde(default = "default_vibe_min_prompt_length")]
1303 pub min_prompt_length: usize,
1304
1305 #[serde(default = "default_vibe_min_prompt_words")]
1307 pub min_prompt_words: usize,
1308
1309 #[serde(default = "default_vibe_entity_resolution")]
1311 pub enable_entity_resolution: bool,
1312
1313 #[serde(default = "default_vibe_entity_cache")]
1315 pub entity_index_cache: String,
1316
1317 #[serde(default = "default_vibe_max_entity_matches")]
1319 pub max_entity_matches: usize,
1320
1321 #[serde(default = "default_vibe_track_workspace")]
1323 pub track_workspace_state: bool,
1324
1325 #[serde(default = "default_vibe_max_recent_files")]
1327 pub max_recent_files: usize,
1328
1329 #[serde(default = "default_vibe_track_values")]
1331 pub track_value_history: bool,
1332
1333 #[serde(default = "default_vibe_conversation_memory")]
1335 pub enable_conversation_memory: bool,
1336
1337 #[serde(default = "default_vibe_max_memory_turns")]
1339 pub max_memory_turns: usize,
1340
1341 #[serde(default = "default_vibe_pronoun_resolution")]
1343 pub enable_pronoun_resolution: bool,
1344
1345 #[serde(default = "default_vibe_proactive_context")]
1347 pub enable_proactive_context: bool,
1348
1349 #[serde(default = "default_vibe_max_context_files")]
1351 pub max_context_files: usize,
1352
1353 #[serde(default = "default_vibe_max_snippets_per_file")]
1355 pub max_context_snippets_per_file: usize,
1356
1357 #[serde(default = "default_vibe_max_search_results")]
1359 pub max_search_results: usize,
1360
1361 #[serde(default = "default_vibe_value_inference")]
1363 pub enable_relative_value_inference: bool,
1364}
1365
1366impl Default for AgentVibeCodingConfig {
1367 fn default() -> Self {
1368 Self {
1369 enabled: default_vibe_coding_enabled(),
1370 min_prompt_length: default_vibe_min_prompt_length(),
1371 min_prompt_words: default_vibe_min_prompt_words(),
1372 enable_entity_resolution: default_vibe_entity_resolution(),
1373 entity_index_cache: default_vibe_entity_cache(),
1374 max_entity_matches: default_vibe_max_entity_matches(),
1375 track_workspace_state: default_vibe_track_workspace(),
1376 max_recent_files: default_vibe_max_recent_files(),
1377 track_value_history: default_vibe_track_values(),
1378 enable_conversation_memory: default_vibe_conversation_memory(),
1379 max_memory_turns: default_vibe_max_memory_turns(),
1380 enable_pronoun_resolution: default_vibe_pronoun_resolution(),
1381 enable_proactive_context: default_vibe_proactive_context(),
1382 max_context_files: default_vibe_max_context_files(),
1383 max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1384 max_search_results: default_vibe_max_search_results(),
1385 enable_relative_value_inference: default_vibe_value_inference(),
1386 }
1387 }
1388}
1389
1390#[inline]
1392const fn default_vibe_coding_enabled() -> bool {
1393 false }
1395
1396#[inline]
1397const fn default_vibe_min_prompt_length() -> usize {
1398 5
1399}
1400
1401#[inline]
1402const fn default_vibe_min_prompt_words() -> usize {
1403 2
1404}
1405
1406#[inline]
1407const fn default_vibe_entity_resolution() -> bool {
1408 true
1409}
1410
1411#[inline]
1412fn default_vibe_entity_cache() -> String {
1413 ".vtcode/entity_index.json".into()
1414}
1415
1416#[inline]
1417const fn default_vibe_max_entity_matches() -> usize {
1418 5
1419}
1420
1421#[inline]
1422const fn default_vibe_track_workspace() -> bool {
1423 true
1424}
1425
1426#[inline]
1427const fn default_vibe_max_recent_files() -> usize {
1428 20
1429}
1430
1431#[inline]
1432const fn default_vibe_track_values() -> bool {
1433 true
1434}
1435
1436#[inline]
1437const fn default_vibe_conversation_memory() -> bool {
1438 true
1439}
1440
1441#[inline]
1442const fn default_vibe_max_memory_turns() -> usize {
1443 50
1444}
1445
1446#[inline]
1447const fn default_vibe_pronoun_resolution() -> bool {
1448 true
1449}
1450
1451#[inline]
1452const fn default_vibe_proactive_context() -> bool {
1453 true
1454}
1455
1456#[inline]
1457const fn default_vibe_max_context_files() -> usize {
1458 3
1459}
1460
1461#[inline]
1462const fn default_vibe_max_snippets_per_file() -> usize {
1463 20
1464}
1465
1466#[inline]
1467const fn default_vibe_max_search_results() -> usize {
1468 5
1469}
1470
1471#[inline]
1472const fn default_vibe_value_inference() -> bool {
1473 true
1474}
1475
1476#[cfg(test)]
1477mod tests {
1478 use super::*;
1479
1480 #[test]
1481 fn test_continuation_policy_defaults_and_parses() {
1482 assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
1483 assert_eq!(
1484 ContinuationPolicy::parse("off"),
1485 Some(ContinuationPolicy::Off)
1486 );
1487 assert_eq!(
1488 ContinuationPolicy::parse("exec-only"),
1489 Some(ContinuationPolicy::ExecOnly)
1490 );
1491 assert_eq!(
1492 ContinuationPolicy::parse("all"),
1493 Some(ContinuationPolicy::All)
1494 );
1495 assert_eq!(ContinuationPolicy::parse("invalid"), None);
1496 }
1497
1498 #[test]
1499 fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1500 let parsed: AgentHarnessConfig =
1501 toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1502 assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1503
1504 let fallback: AgentHarnessConfig =
1505 toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1506 assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
1507 }
1508
1509 #[test]
1510 fn test_harness_orchestration_mode_defaults_and_parses() {
1511 assert_eq!(
1512 HarnessOrchestrationMode::default(),
1513 HarnessOrchestrationMode::Single
1514 );
1515 assert_eq!(
1516 HarnessOrchestrationMode::parse("single"),
1517 Some(HarnessOrchestrationMode::Single)
1518 );
1519 assert_eq!(
1520 HarnessOrchestrationMode::parse("plan_build_evaluate"),
1521 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1522 );
1523 assert_eq!(
1524 HarnessOrchestrationMode::parse("planner-generator-evaluator"),
1525 Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1526 );
1527 assert_eq!(HarnessOrchestrationMode::parse("unexpected"), None);
1528 }
1529
1530 #[test]
1531 fn test_harness_config_orchestration_deserializes_with_fallback() {
1532 let parsed: AgentHarnessConfig =
1533 toml::from_str("orchestration_mode = \"plan_build_evaluate\"")
1534 .expect("valid harness config");
1535 assert_eq!(
1536 parsed.orchestration_mode,
1537 HarnessOrchestrationMode::PlanBuildEvaluate
1538 );
1539 assert_eq!(parsed.max_revision_rounds, 2);
1540
1541 let fallback: AgentHarnessConfig =
1542 toml::from_str("orchestration_mode = \"unexpected\"").expect("fallback config");
1543 assert_eq!(
1544 fallback.orchestration_mode,
1545 HarnessOrchestrationMode::Single
1546 );
1547 }
1548
1549 #[test]
1550 fn test_plan_confirmation_config_default() {
1551 let config = AgentConfig::default();
1552 assert!(config.require_plan_confirmation);
1553 }
1554
1555 #[test]
1556 fn test_persistent_memory_is_disabled_by_default() {
1557 let config = AgentConfig::default();
1558 assert!(!config.persistent_memory.enabled);
1559 assert!(config.persistent_memory.auto_write);
1560 }
1561
1562 #[test]
1563 fn test_tool_result_clearing_defaults() {
1564 let config = AgentConfig::default();
1565 let clearing = config.harness.tool_result_clearing;
1566
1567 assert!(!clearing.enabled);
1568 assert_eq!(clearing.trigger_tokens, 100_000);
1569 assert_eq!(clearing.keep_tool_uses, 3);
1570 assert_eq!(clearing.clear_at_least_tokens, 30_000);
1571 assert!(!clearing.clear_tool_inputs);
1572 }
1573
1574 #[test]
1575 fn test_codex_app_server_experimental_features_default_to_disabled() {
1576 let config = AgentConfig::default();
1577
1578 assert!(!config.codex_app_server.experimental_features);
1579 }
1580
1581 #[test]
1582 fn test_codex_app_server_experimental_features_parse_from_toml() {
1583 let parsed: AgentCodexAppServerConfig = toml::from_str(
1584 r#"
1585 command = "codex"
1586 args = ["app-server"]
1587 startup_timeout_secs = 15
1588 experimental_features = true
1589 "#,
1590 )
1591 .expect("valid codex app-server config");
1592
1593 assert!(parsed.experimental_features);
1594 assert_eq!(parsed.startup_timeout_secs, 15);
1595 }
1596
1597 #[test]
1598 fn test_tool_result_clearing_parses_and_validates() {
1599 let parsed: AgentHarnessConfig = toml::from_str(
1600 r#"
1601 [tool_result_clearing]
1602 enabled = true
1603 trigger_tokens = 123456
1604 keep_tool_uses = 6
1605 clear_at_least_tokens = 4096
1606 clear_tool_inputs = true
1607 "#,
1608 )
1609 .expect("valid harness config");
1610
1611 assert!(parsed.tool_result_clearing.enabled);
1612 assert_eq!(parsed.tool_result_clearing.trigger_tokens, 123_456);
1613 assert_eq!(parsed.tool_result_clearing.keep_tool_uses, 6);
1614 assert_eq!(parsed.tool_result_clearing.clear_at_least_tokens, 4_096);
1615 assert!(parsed.tool_result_clearing.clear_tool_inputs);
1616 assert!(parsed.tool_result_clearing.validate().is_ok());
1617 }
1618
1619 #[test]
1620 fn test_tool_result_clearing_rejects_zero_values() {
1621 let clearing = ToolResultClearingConfig {
1622 trigger_tokens: 0,
1623 ..ToolResultClearingConfig::default()
1624 };
1625 assert!(clearing.validate().is_err());
1626
1627 let clearing = ToolResultClearingConfig {
1628 keep_tool_uses: 0,
1629 ..ToolResultClearingConfig::default()
1630 };
1631 assert!(clearing.validate().is_err());
1632
1633 let clearing = ToolResultClearingConfig {
1634 clear_at_least_tokens: 0,
1635 ..ToolResultClearingConfig::default()
1636 };
1637 assert!(clearing.validate().is_err());
1638 }
1639
1640 #[test]
1641 fn test_structured_reasoning_defaults_follow_prompt_mode() {
1642 let default_mode = AgentConfig {
1643 system_prompt_mode: SystemPromptMode::Default,
1644 ..Default::default()
1645 };
1646 assert!(!default_mode.should_include_structured_reasoning_tags());
1647
1648 let specialized_mode = AgentConfig {
1649 system_prompt_mode: SystemPromptMode::Specialized,
1650 ..Default::default()
1651 };
1652 assert!(specialized_mode.should_include_structured_reasoning_tags());
1653
1654 let minimal_mode = AgentConfig {
1655 system_prompt_mode: SystemPromptMode::Minimal,
1656 ..Default::default()
1657 };
1658 assert!(!minimal_mode.should_include_structured_reasoning_tags());
1659
1660 let lightweight_mode = AgentConfig {
1661 system_prompt_mode: SystemPromptMode::Lightweight,
1662 ..Default::default()
1663 };
1664 assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1665 }
1666
1667 #[test]
1668 fn test_structured_reasoning_explicit_override() {
1669 let mut config = AgentConfig {
1670 system_prompt_mode: SystemPromptMode::Minimal,
1671 include_structured_reasoning_tags: Some(true),
1672 ..AgentConfig::default()
1673 };
1674 assert!(config.should_include_structured_reasoning_tags());
1675
1676 config.include_structured_reasoning_tags = Some(false);
1677 assert!(!config.should_include_structured_reasoning_tags());
1678 }
1679}