Skip to main content

vtcode_config/core/
agent.rs

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/// Agent-wide configuration
14#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct AgentConfig {
17    /// AI provider for single agent mode (gemini, openai, anthropic, openrouter, zai)
18    #[serde(default = "default_provider")]
19    pub provider: String,
20
21    /// Environment variable that stores the API key for the active provider
22    #[serde(default = "default_api_key_env")]
23    pub api_key_env: String,
24
25    /// Default model to use
26    #[serde(default = "default_model")]
27    pub default_model: String,
28
29    /// UI theme identifier controlling ANSI styling
30    #[serde(default = "default_theme")]
31    pub theme: String,
32
33    /// System prompt mode controlling prompt verbosity and token overhead.
34    /// Options target lean base prompts: minimal (~150-250 tokens), lightweight/default
35    /// (~250-350 tokens), specialized (~350-500 tokens) before dynamic runtime addenda.
36    #[serde(default)]
37    pub system_prompt_mode: SystemPromptMode,
38
39    /// Tool documentation mode controlling token overhead for tool definitions
40    /// Options: minimal (~800 tokens), progressive (~1.2k), full (~3k current)
41    /// Progressive: signatures upfront, detailed docs on-demand (recommended)
42    /// Minimal: signatures only, pi-coding-agent style (power users)
43    /// Full: all documentation upfront (current behavior, default)
44    #[serde(default)]
45    pub tool_documentation_mode: ToolDocumentationMode,
46
47    /// Enable split tool results for massive token savings (Phase 4)
48    /// When enabled, tools return dual-channel output:
49    /// - llm_content: Concise summary sent to LLM (token-optimized, 53-95% reduction)
50    /// - ui_content: Rich output displayed to user (full details preserved)
51    ///   Applies to: unified_search, unified_file, unified_exec
52    ///   Default: true (opt-out for compatibility), recommended for production use
53    #[serde(default = "default_enable_split_tool_results")]
54    pub enable_split_tool_results: bool,
55
56    /// Enable TODO planning helper mode for structured task management
57    #[serde(default = "default_todo_planning_mode")]
58    pub todo_planning_mode: bool,
59
60    /// Preferred rendering surface for the interactive chat UI (auto, alternate, inline)
61    #[serde(default)]
62    pub ui_surface: UiSurfacePreference,
63
64    /// Maximum number of conversation turns before auto-termination
65    #[serde(default = "default_max_conversation_turns")]
66    pub max_conversation_turns: usize,
67
68    /// Reasoning effort level for models that support it (none, minimal, low, medium, high, xhigh)
69    /// Applies to: Claude, GPT-5 family, Gemini, Qwen3, DeepSeek with reasoning capability
70    #[serde(default = "default_reasoning_effort")]
71    pub reasoning_effort: ReasoningEffortLevel,
72
73    /// Verbosity level for output text (low, medium, high)
74    /// Applies to: GPT-5.4-family Responses workflows and other models that support verbosity control
75    #[serde(default = "default_verbosity")]
76    pub verbosity: VerbosityLevel,
77
78    /// Temperature for main LLM responses (0.0-1.0)
79    /// Lower values = more deterministic, higher values = more creative
80    /// Recommended: 0.7 for balanced creativity and consistency
81    /// Range: 0.0 (deterministic) to 1.0 (maximum randomness)
82    #[serde(default = "default_temperature")]
83    pub temperature: f32,
84
85    /// Temperature for prompt refinement (0.0-1.0, default: 0.3)
86    /// Lower values ensure prompt refinement is more deterministic/consistent
87    /// Keep lower than main temperature for stable prompt improvement
88    #[serde(default = "default_refine_temperature")]
89    pub refine_temperature: f32,
90
91    /// Enable an extra self-review pass to refine final responses
92    #[serde(default = "default_enable_self_review")]
93    pub enable_self_review: bool,
94
95    /// Maximum number of self-review passes
96    #[serde(default = "default_max_review_passes")]
97    pub max_review_passes: usize,
98
99    /// Enable prompt refinement pass before sending to LLM
100    #[serde(default = "default_refine_prompts_enabled")]
101    pub refine_prompts_enabled: bool,
102
103    /// Max refinement passes for prompt writing
104    #[serde(default = "default_refine_max_passes")]
105    pub refine_prompts_max_passes: usize,
106
107    /// Optional model override for the refiner (empty = auto pick efficient sibling)
108    #[serde(default)]
109    pub refine_prompts_model: String,
110
111    /// Small/lightweight model configuration for efficient operations
112    /// Used for tasks like large file reads, parsing, git history, conversation summarization
113    /// Typically 70-80% cheaper than main model; ~50% of VT Code's calls use this tier
114    #[serde(default)]
115    pub small_model: AgentSmallModelConfig,
116
117    /// Inline prompt suggestion configuration for the chat composer
118    #[serde(default)]
119    pub prompt_suggestions: AgentPromptSuggestionsConfig,
120
121    /// Session onboarding and welcome message configuration
122    #[serde(default)]
123    pub onboarding: AgentOnboardingConfig,
124
125    /// Maximum bytes of AGENTS.md content to load from project hierarchy
126    #[serde(default = "default_project_doc_max_bytes")]
127    pub project_doc_max_bytes: usize,
128
129    /// Additional filenames to check when AGENTS.md is absent at a directory level.
130    #[serde(default)]
131    pub project_doc_fallback_filenames: Vec<String>,
132
133    /// Maximum bytes of instruction content to load from AGENTS.md hierarchy
134    #[serde(
135        default = "default_instruction_max_bytes",
136        alias = "rule_doc_max_bytes"
137    )]
138    pub instruction_max_bytes: usize,
139
140    /// Additional instruction files or globs to merge into the hierarchy
141    #[serde(default, alias = "instruction_paths", alias = "instructions")]
142    pub instruction_files: Vec<String>,
143
144    /// Instruction files or globs to exclude from AGENTS.md and rules discovery
145    #[serde(default)]
146    pub instruction_excludes: Vec<String>,
147
148    /// Maximum recursive `@path` import depth for instruction and rule files
149    #[serde(default = "default_instruction_import_max_depth")]
150    pub instruction_import_max_depth: usize,
151
152    /// Durable per-repository memory for main sessions
153    #[serde(default)]
154    pub persistent_memory: PersistentMemoryConfig,
155
156    /// Provider-specific API keys captured from interactive configuration flows
157    ///
158    /// Note: Actual API keys are stored securely in the OS keyring.
159    /// This field only tracks which providers have keys stored (for UI/migration purposes).
160    /// The keys themselves are NOT serialized to the config file for security.
161    #[serde(default, skip_serializing)]
162    pub custom_api_keys: BTreeMap<String, String>,
163
164    /// Preferred storage backend for credentials (OAuth tokens, API keys, etc.)
165    ///
166    /// - `keyring`: Use OS-specific secure storage (macOS Keychain, Windows Credential
167    ///   Manager, Linux Secret Service). This is the default as it's the most secure.
168    /// - `file`: Use AES-256-GCM encrypted file with machine-derived key
169    /// - `auto`: Try keyring first, fall back to file if unavailable
170    #[serde(default)]
171    pub credential_storage_mode: crate::auth::AuthCredentialsStoreMode,
172
173    /// Checkpointing configuration for automatic turn snapshots
174    #[serde(default)]
175    pub checkpointing: AgentCheckpointingConfig,
176
177    /// Vibe coding configuration for lazy or vague request support
178    #[serde(default)]
179    pub vibe_coding: AgentVibeCodingConfig,
180
181    /// Maximum number of retries for agent task execution (default: 2)
182    /// When an agent task fails due to retryable errors (timeout, network, 503, etc.),
183    /// it will be retried up to this many times with exponential backoff
184    #[serde(default = "default_max_task_retries")]
185    pub max_task_retries: u32,
186
187    /// Harness configuration for turn-level budgets, telemetry, and execution limits
188    #[serde(default)]
189    pub harness: AgentHarnessConfig,
190
191    /// Experimental Codex app-server sidecar configuration.
192    #[serde(default)]
193    pub codex_app_server: AgentCodexAppServerConfig,
194
195    /// Include current date/time in system prompt for temporal awareness
196    /// Helps LLM understand context for time-sensitive tasks (default: true)
197    #[serde(default = "default_include_temporal_context")]
198    pub include_temporal_context: bool,
199
200    /// Use UTC instead of local time for temporal context in system prompts
201    #[serde(default)]
202    pub temporal_context_use_utc: bool,
203
204    /// Include current working directory in system prompt (default: true)
205    #[serde(default = "default_include_working_directory")]
206    pub include_working_directory: bool,
207
208    /// Controls inclusion of the structured reasoning tag instructions block.
209    ///
210    /// Behavior:
211    /// - `Some(true)`: always include structured reasoning instructions.
212    /// - `Some(false)`: never include structured reasoning instructions.
213    /// - `None` (default): include only for `default` and `specialized` prompt modes.
214    ///
215    /// This keeps lightweight/minimal prompts smaller by default while allowing
216    /// explicit opt-in when users want tag-based reasoning guidance.
217    #[serde(default)]
218    pub include_structured_reasoning_tags: Option<bool>,
219
220    /// Custom instructions provided by the user via configuration to guide agent behavior
221    #[serde(default)]
222    pub user_instructions: Option<String>,
223
224    /// Default editing mode on startup: "edit" (default) or "plan"
225    /// Codex-inspired: Encourages structured planning before execution.
226    #[serde(default)]
227    pub default_editing_mode: EditingMode,
228
229    /// Require user confirmation before executing a plan generated in plan mode
230    /// When true, exiting plan mode shows the implementation blueprint and
231    /// requires explicit user approval before enabling edit tools.
232    #[serde(default = "default_require_plan_confirmation")]
233    pub require_plan_confirmation: bool,
234
235    /// Deprecated compatibility flag for pre-classifier autonomous mode settings.
236    /// When true and `[permissions].default_mode` is not explicitly set, VT Code maps
237    /// the session to `permissions.default_mode = "auto"`.
238    #[serde(default = "default_autonomous_mode")]
239    pub autonomous_mode: bool,
240
241    /// Circuit breaker configuration for resilient tool execution
242    /// Controls when the agent should pause and ask for user guidance due to repeated failures
243    #[serde(default)]
244    pub circuit_breaker: CircuitBreakerConfig,
245
246    /// Open Responses specification compliance configuration
247    /// Enables vendor-neutral LLM API format for interoperable workflows
248    #[serde(default)]
249    pub open_responses: OpenResponsesConfig,
250}
251
252#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
253#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
254#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
255#[serde(rename_all = "snake_case")]
256pub enum ContinuationPolicy {
257    Off,
258    ExecOnly,
259    #[default]
260    All,
261}
262
263impl ContinuationPolicy {
264    pub fn as_str(&self) -> &'static str {
265        match self {
266            Self::Off => "off",
267            Self::ExecOnly => "exec_only",
268            Self::All => "all",
269        }
270    }
271
272    pub fn parse(value: &str) -> Option<Self> {
273        let normalized = value.trim();
274        if normalized.eq_ignore_ascii_case("off") {
275            Some(Self::Off)
276        } else if normalized.eq_ignore_ascii_case("exec_only")
277            || normalized.eq_ignore_ascii_case("exec-only")
278        {
279            Some(Self::ExecOnly)
280        } else if normalized.eq_ignore_ascii_case("all") {
281            Some(Self::All)
282        } else {
283            None
284        }
285    }
286}
287
288impl<'de> Deserialize<'de> for ContinuationPolicy {
289    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
290    where
291        D: serde::Deserializer<'de>,
292    {
293        let raw = String::deserialize(deserializer)?;
294        Ok(Self::parse(&raw).unwrap_or_default())
295    }
296}
297
298#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
299#[cfg_attr(feature = "schema", schemars(rename_all = "snake_case"))]
300#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
301#[serde(rename_all = "snake_case")]
302pub enum HarnessOrchestrationMode {
303    #[default]
304    Single,
305    PlanBuildEvaluate,
306}
307
308impl HarnessOrchestrationMode {
309    pub fn as_str(&self) -> &'static str {
310        match self {
311            Self::Single => "single",
312            Self::PlanBuildEvaluate => "plan_build_evaluate",
313        }
314    }
315
316    pub fn parse(value: &str) -> Option<Self> {
317        let normalized = value.trim();
318        if normalized.eq_ignore_ascii_case("single") {
319            Some(Self::Single)
320        } else if normalized.eq_ignore_ascii_case("plan_build_evaluate")
321            || normalized.eq_ignore_ascii_case("plan-build-evaluate")
322            || normalized.eq_ignore_ascii_case("planner_generator_evaluator")
323            || normalized.eq_ignore_ascii_case("planner-generator-evaluator")
324        {
325            Some(Self::PlanBuildEvaluate)
326        } else {
327            None
328        }
329    }
330}
331
332impl<'de> Deserialize<'de> for HarnessOrchestrationMode {
333    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
334    where
335        D: serde::Deserializer<'de>,
336    {
337        let raw = String::deserialize(deserializer)?;
338        Ok(Self::parse(&raw).unwrap_or_default())
339    }
340}
341
342#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
343#[derive(Debug, Clone, Deserialize, Serialize)]
344pub struct AgentHarnessConfig {
345    /// Maximum number of tool calls allowed per turn. Set to `0` to disable the cap.
346    #[serde(default = "default_harness_max_tool_calls_per_turn")]
347    pub max_tool_calls_per_turn: usize,
348    /// Maximum wall clock time (seconds) for tool execution in a turn
349    #[serde(default = "default_harness_max_tool_wall_clock_secs")]
350    pub max_tool_wall_clock_secs: u64,
351    /// Maximum retries for retryable tool errors
352    #[serde(default = "default_harness_max_tool_retries")]
353    pub max_tool_retries: u32,
354    /// Enable automatic context compaction when token pressure crosses threshold.
355    ///
356    /// Disabled by default. When disabled, no automatic compaction is triggered.
357    #[serde(default = "default_harness_auto_compaction_enabled")]
358    pub auto_compaction_enabled: bool,
359    /// Optional absolute compact threshold (tokens) for Responses server-side compaction.
360    ///
361    /// When unset, VT Code derives a threshold from the provider context window.
362    #[serde(default)]
363    pub auto_compaction_threshold_tokens: Option<u64>,
364    /// Provider-native tool-result clearing policy.
365    #[serde(default)]
366    pub tool_result_clearing: ToolResultClearingConfig,
367    /// Optional maximum estimated API cost in USD before VT Code stops the session.
368    #[serde(default)]
369    pub max_budget_usd: Option<f64>,
370    /// Controls whether harness-managed continuation loops are enabled.
371    #[serde(default)]
372    pub continuation_policy: ContinuationPolicy,
373    /// Optional JSONL event log path for harness events.
374    /// Defaults to `~/.vtcode/sessions/` when unset.
375    #[serde(default)]
376    pub event_log_path: Option<String>,
377    /// Select the exec/full-auto harness orchestration path.
378    #[serde(default)]
379    pub orchestration_mode: HarnessOrchestrationMode,
380    /// Maximum generator revision rounds after evaluator rejection.
381    #[serde(default = "default_harness_max_revision_rounds")]
382    pub max_revision_rounds: usize,
383}
384
385impl Default for AgentHarnessConfig {
386    fn default() -> Self {
387        Self {
388            max_tool_calls_per_turn: default_harness_max_tool_calls_per_turn(),
389            max_tool_wall_clock_secs: default_harness_max_tool_wall_clock_secs(),
390            max_tool_retries: default_harness_max_tool_retries(),
391            auto_compaction_enabled: default_harness_auto_compaction_enabled(),
392            auto_compaction_threshold_tokens: None,
393            tool_result_clearing: ToolResultClearingConfig::default(),
394            max_budget_usd: None,
395            continuation_policy: ContinuationPolicy::default(),
396            event_log_path: None,
397            orchestration_mode: HarnessOrchestrationMode::default(),
398            max_revision_rounds: default_harness_max_revision_rounds(),
399        }
400    }
401}
402
403#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
404#[derive(Debug, Clone, Deserialize, Serialize)]
405pub struct ToolResultClearingConfig {
406    #[serde(default = "default_tool_result_clearing_enabled")]
407    pub enabled: bool,
408    #[serde(default = "default_tool_result_clearing_trigger_tokens")]
409    pub trigger_tokens: u64,
410    #[serde(default = "default_tool_result_clearing_keep_tool_uses")]
411    pub keep_tool_uses: u32,
412    #[serde(default = "default_tool_result_clearing_clear_at_least_tokens")]
413    pub clear_at_least_tokens: u64,
414    #[serde(default)]
415    pub clear_tool_inputs: bool,
416}
417
418impl Default for ToolResultClearingConfig {
419    fn default() -> Self {
420        Self {
421            enabled: default_tool_result_clearing_enabled(),
422            trigger_tokens: default_tool_result_clearing_trigger_tokens(),
423            keep_tool_uses: default_tool_result_clearing_keep_tool_uses(),
424            clear_at_least_tokens: default_tool_result_clearing_clear_at_least_tokens(),
425            clear_tool_inputs: false,
426        }
427    }
428}
429
430impl ToolResultClearingConfig {
431    pub fn validate(&self) -> Result<(), String> {
432        if self.trigger_tokens == 0 {
433            return Err("tool_result_clearing.trigger_tokens must be greater than 0".to_string());
434        }
435        if self.keep_tool_uses == 0 {
436            return Err("tool_result_clearing.keep_tool_uses must be greater than 0".to_string());
437        }
438        if self.clear_at_least_tokens == 0 {
439            return Err(
440                "tool_result_clearing.clear_at_least_tokens must be greater than 0".to_string(),
441            );
442        }
443        Ok(())
444    }
445}
446
447#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
448#[derive(Debug, Clone, Deserialize, Serialize)]
449pub struct AgentCodexAppServerConfig {
450    /// Executable used to launch the official Codex app-server sidecar.
451    #[serde(default = "default_codex_app_server_command")]
452    pub command: String,
453    /// Arguments passed before VT Code appends `--listen stdio://`.
454    #[serde(default = "default_codex_app_server_args")]
455    pub args: Vec<String>,
456    /// Maximum startup handshake time when launching the sidecar.
457    #[serde(default = "default_codex_app_server_startup_timeout_secs")]
458    pub startup_timeout_secs: u64,
459    /// Enable experimental Codex app-server features such as collaboration modes
460    /// and native review routing.
461    #[serde(default = "default_codex_app_server_experimental_features")]
462    pub experimental_features: bool,
463}
464
465impl Default for AgentCodexAppServerConfig {
466    fn default() -> Self {
467        Self {
468            command: default_codex_app_server_command(),
469            args: default_codex_app_server_args(),
470            startup_timeout_secs: default_codex_app_server_startup_timeout_secs(),
471            experimental_features: default_codex_app_server_experimental_features(),
472        }
473    }
474}
475
476#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
477#[derive(Debug, Clone, Deserialize, Serialize)]
478pub struct CircuitBreakerConfig {
479    /// Enable circuit breaker functionality
480    #[serde(default = "default_circuit_breaker_enabled")]
481    pub enabled: bool,
482
483    /// Number of consecutive failures before opening circuit
484    #[serde(default = "default_failure_threshold")]
485    pub failure_threshold: u32,
486
487    /// Pause and ask user when circuit opens (vs auto-backoff)
488    #[serde(default = "default_pause_on_open")]
489    pub pause_on_open: bool,
490
491    /// Number of open circuits before triggering pause
492    #[serde(default = "default_max_open_circuits")]
493    pub max_open_circuits: usize,
494
495    /// Cooldown period between recovery prompts (seconds)
496    #[serde(default = "default_recovery_cooldown")]
497    pub recovery_cooldown: u64,
498}
499
500impl Default for CircuitBreakerConfig {
501    fn default() -> Self {
502        Self {
503            enabled: default_circuit_breaker_enabled(),
504            failure_threshold: default_failure_threshold(),
505            pause_on_open: default_pause_on_open(),
506            max_open_circuits: default_max_open_circuits(),
507            recovery_cooldown: default_recovery_cooldown(),
508        }
509    }
510}
511
512/// Open Responses specification compliance configuration
513///
514/// Enables vendor-neutral LLM API format per the Open Responses specification
515/// (<https://www.openresponses.org/>). When enabled, VT Code emits semantic
516/// streaming events and uses standardized response/item structures.
517#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
518#[derive(Debug, Clone, Deserialize, Serialize)]
519pub struct OpenResponsesConfig {
520    /// Enable Open Responses specification compliance layer
521    /// When true, VT Code emits semantic streaming events alongside internal events
522    /// Default: false (opt-in feature)
523    #[serde(default)]
524    pub enabled: bool,
525
526    /// Emit Open Responses events to the event sink
527    /// When true, streaming events follow Open Responses format
528    /// (response.created, response.output_item.added, response.output_text.delta, etc.)
529    #[serde(default = "default_open_responses_emit_events")]
530    pub emit_events: bool,
531
532    /// Include VT Code extension items (vtcode:file_change, vtcode:web_search, etc.)
533    /// When false, extension items are omitted from the Open Responses output
534    #[serde(default = "default_open_responses_include_extensions")]
535    pub include_extensions: bool,
536
537    /// Map internal tool calls to Open Responses function_call items
538    /// When true, command executions and MCP tool calls are represented as function_call items
539    #[serde(default = "default_open_responses_map_tool_calls")]
540    pub map_tool_calls: bool,
541
542    /// Include reasoning items in Open Responses output
543    /// When true, model reasoning/thinking is exposed as reasoning items
544    #[serde(default = "default_open_responses_include_reasoning")]
545    pub include_reasoning: bool,
546}
547
548impl Default for OpenResponsesConfig {
549    fn default() -> Self {
550        Self {
551            enabled: false, // Opt-in by default
552            emit_events: default_open_responses_emit_events(),
553            include_extensions: default_open_responses_include_extensions(),
554            map_tool_calls: default_open_responses_map_tool_calls(),
555            include_reasoning: default_open_responses_include_reasoning(),
556        }
557    }
558}
559
560#[inline]
561const fn default_open_responses_emit_events() -> bool {
562    true // When enabled, emit events by default
563}
564
565#[inline]
566const fn default_open_responses_include_extensions() -> bool {
567    true // Include VT Code-specific extensions by default
568}
569
570#[inline]
571const fn default_open_responses_map_tool_calls() -> bool {
572    true // Map tool calls to function_call items by default
573}
574
575#[inline]
576const fn default_open_responses_include_reasoning() -> bool {
577    true // Include reasoning items by default
578}
579
580#[inline]
581fn default_codex_app_server_command() -> String {
582    "codex".to_string()
583}
584
585#[inline]
586fn default_codex_app_server_args() -> Vec<String> {
587    vec!["app-server".to_string()]
588}
589
590#[inline]
591const fn default_codex_app_server_startup_timeout_secs() -> u64 {
592    10
593}
594
595#[inline]
596const fn default_codex_app_server_experimental_features() -> bool {
597    false
598}
599
600impl Default for AgentConfig {
601    fn default() -> Self {
602        Self {
603            provider: default_provider(),
604            api_key_env: default_api_key_env(),
605            default_model: default_model(),
606            theme: default_theme(),
607            system_prompt_mode: SystemPromptMode::default(),
608            tool_documentation_mode: ToolDocumentationMode::default(),
609            enable_split_tool_results: default_enable_split_tool_results(),
610            todo_planning_mode: default_todo_planning_mode(),
611            ui_surface: UiSurfacePreference::default(),
612            max_conversation_turns: default_max_conversation_turns(),
613            reasoning_effort: default_reasoning_effort(),
614            verbosity: default_verbosity(),
615            temperature: default_temperature(),
616            refine_temperature: default_refine_temperature(),
617            enable_self_review: default_enable_self_review(),
618            max_review_passes: default_max_review_passes(),
619            refine_prompts_enabled: default_refine_prompts_enabled(),
620            refine_prompts_max_passes: default_refine_max_passes(),
621            refine_prompts_model: String::new(),
622            small_model: AgentSmallModelConfig::default(),
623            prompt_suggestions: AgentPromptSuggestionsConfig::default(),
624            onboarding: AgentOnboardingConfig::default(),
625            project_doc_max_bytes: default_project_doc_max_bytes(),
626            project_doc_fallback_filenames: Vec::new(),
627            instruction_max_bytes: default_instruction_max_bytes(),
628            instruction_files: Vec::new(),
629            instruction_excludes: Vec::new(),
630            instruction_import_max_depth: default_instruction_import_max_depth(),
631            persistent_memory: PersistentMemoryConfig::default(),
632            custom_api_keys: BTreeMap::new(),
633            credential_storage_mode: crate::auth::AuthCredentialsStoreMode::default(),
634            checkpointing: AgentCheckpointingConfig::default(),
635            vibe_coding: AgentVibeCodingConfig::default(),
636            max_task_retries: default_max_task_retries(),
637            harness: AgentHarnessConfig::default(),
638            codex_app_server: AgentCodexAppServerConfig::default(),
639            include_temporal_context: default_include_temporal_context(),
640            temporal_context_use_utc: false, // Default to local time
641            include_working_directory: default_include_working_directory(),
642            include_structured_reasoning_tags: None,
643            user_instructions: None,
644            default_editing_mode: EditingMode::default(),
645            require_plan_confirmation: default_require_plan_confirmation(),
646            autonomous_mode: default_autonomous_mode(),
647            circuit_breaker: CircuitBreakerConfig::default(),
648            open_responses: OpenResponsesConfig::default(),
649        }
650    }
651}
652
653impl AgentConfig {
654    /// Determine whether structured reasoning tag instructions should be included.
655    pub fn should_include_structured_reasoning_tags(&self) -> bool {
656        self.include_structured_reasoning_tags.unwrap_or(matches!(
657            self.system_prompt_mode,
658            SystemPromptMode::Specialized
659        ))
660    }
661
662    /// Validate LLM generation parameters
663    pub fn validate_llm_params(&self) -> Result<(), String> {
664        // Validate temperature range
665        if !(0.0..=1.0).contains(&self.temperature) {
666            return Err(format!(
667                "temperature must be between 0.0 and 1.0, got {}",
668                self.temperature
669            ));
670        }
671
672        if !(0.0..=1.0).contains(&self.refine_temperature) {
673            return Err(format!(
674                "refine_temperature must be between 0.0 and 1.0, got {}",
675                self.refine_temperature
676            ));
677        }
678
679        if self.instruction_import_max_depth == 0 {
680            return Err("instruction_import_max_depth must be greater than 0".to_string());
681        }
682
683        self.persistent_memory.validate()?;
684        self.harness.tool_result_clearing.validate()?;
685
686        Ok(())
687    }
688}
689
690// Optimized: Use inline defaults with constants to reduce function call overhead
691#[inline]
692fn default_provider() -> String {
693    defaults::DEFAULT_PROVIDER.into()
694}
695
696#[inline]
697fn default_api_key_env() -> String {
698    defaults::DEFAULT_API_KEY_ENV.into()
699}
700
701#[inline]
702fn default_model() -> String {
703    defaults::DEFAULT_MODEL.into()
704}
705
706#[inline]
707fn default_theme() -> String {
708    defaults::DEFAULT_THEME.into()
709}
710
711#[inline]
712const fn default_todo_planning_mode() -> bool {
713    true
714}
715
716#[inline]
717const fn default_enable_split_tool_results() -> bool {
718    true // Default: enabled for production use (84% token savings)
719}
720
721#[inline]
722const fn default_max_conversation_turns() -> usize {
723    defaults::DEFAULT_MAX_CONVERSATION_TURNS
724}
725
726#[inline]
727fn default_reasoning_effort() -> ReasoningEffortLevel {
728    ReasoningEffortLevel::None
729}
730
731#[inline]
732fn default_verbosity() -> VerbosityLevel {
733    VerbosityLevel::default()
734}
735
736#[inline]
737const fn default_temperature() -> f32 {
738    llm_generation::DEFAULT_TEMPERATURE
739}
740
741#[inline]
742const fn default_refine_temperature() -> f32 {
743    llm_generation::DEFAULT_REFINE_TEMPERATURE
744}
745
746#[inline]
747const fn default_enable_self_review() -> bool {
748    false
749}
750
751#[inline]
752const fn default_max_review_passes() -> usize {
753    1
754}
755
756#[inline]
757const fn default_refine_prompts_enabled() -> bool {
758    false
759}
760
761#[inline]
762const fn default_refine_max_passes() -> usize {
763    1
764}
765
766#[inline]
767const fn default_project_doc_max_bytes() -> usize {
768    prompt_budget::DEFAULT_MAX_BYTES
769}
770
771#[inline]
772const fn default_instruction_max_bytes() -> usize {
773    prompt_budget::DEFAULT_MAX_BYTES
774}
775
776#[inline]
777const fn default_instruction_import_max_depth() -> usize {
778    5
779}
780
781#[inline]
782const fn default_max_task_retries() -> u32 {
783    2 // Retry twice on transient failures
784}
785
786#[inline]
787const fn default_harness_max_tool_calls_per_turn() -> usize {
788    defaults::DEFAULT_MAX_TOOL_CALLS_PER_TURN
789}
790
791#[inline]
792const fn default_harness_max_tool_wall_clock_secs() -> u64 {
793    defaults::DEFAULT_MAX_TOOL_WALL_CLOCK_SECS
794}
795
796#[inline]
797const fn default_harness_max_tool_retries() -> u32 {
798    defaults::DEFAULT_MAX_TOOL_RETRIES
799}
800
801#[inline]
802const fn default_harness_auto_compaction_enabled() -> bool {
803    false
804}
805
806#[inline]
807const fn default_tool_result_clearing_enabled() -> bool {
808    false
809}
810
811#[inline]
812const fn default_tool_result_clearing_trigger_tokens() -> u64 {
813    100_000
814}
815
816#[inline]
817const fn default_tool_result_clearing_keep_tool_uses() -> u32 {
818    3
819}
820
821#[inline]
822const fn default_tool_result_clearing_clear_at_least_tokens() -> u64 {
823    30_000
824}
825
826#[inline]
827const fn default_harness_max_revision_rounds() -> usize {
828    2
829}
830
831#[inline]
832const fn default_include_temporal_context() -> bool {
833    true // Enable by default - minimal overhead (~20 tokens)
834}
835
836#[inline]
837const fn default_include_working_directory() -> bool {
838    true // Enable by default - minimal overhead (~10 tokens)
839}
840
841#[inline]
842const fn default_require_plan_confirmation() -> bool {
843    true // Default: require confirmation (HITL pattern)
844}
845
846#[inline]
847const fn default_autonomous_mode() -> bool {
848    false // Default: interactive mode with full HITL
849}
850
851#[inline]
852const fn default_circuit_breaker_enabled() -> bool {
853    true // Default: enabled for resilient execution
854}
855
856#[inline]
857const fn default_failure_threshold() -> u32 {
858    7 // Open circuit after 7 consecutive failures
859}
860
861#[inline]
862const fn default_pause_on_open() -> bool {
863    true // Default: ask user for guidance on circuit breaker
864}
865
866#[inline]
867const fn default_max_open_circuits() -> usize {
868    3 // Pause when 3+ tools have open circuits
869}
870
871#[inline]
872const fn default_recovery_cooldown() -> u64 {
873    60 // Cooldown between recovery prompts (seconds)
874}
875
876#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
877#[derive(Debug, Clone, Deserialize, Serialize)]
878pub struct AgentCheckpointingConfig {
879    /// Enable automatic checkpoints after each successful turn
880    #[serde(default = "default_checkpointing_enabled")]
881    pub enabled: bool,
882
883    /// Optional custom directory for storing checkpoints (relative to workspace or absolute)
884    #[serde(default)]
885    pub storage_dir: Option<String>,
886
887    /// Maximum number of checkpoints to retain on disk
888    #[serde(default = "default_checkpointing_max_snapshots")]
889    pub max_snapshots: usize,
890
891    /// Maximum age in days before checkpoints are removed automatically (None disables)
892    #[serde(default = "default_checkpointing_max_age_days")]
893    pub max_age_days: Option<u64>,
894}
895
896impl Default for AgentCheckpointingConfig {
897    fn default() -> Self {
898        Self {
899            enabled: default_checkpointing_enabled(),
900            storage_dir: None,
901            max_snapshots: default_checkpointing_max_snapshots(),
902            max_age_days: default_checkpointing_max_age_days(),
903        }
904    }
905}
906
907#[inline]
908const fn default_checkpointing_enabled() -> bool {
909    DEFAULT_CHECKPOINTS_ENABLED
910}
911
912#[inline]
913const fn default_checkpointing_max_snapshots() -> usize {
914    DEFAULT_MAX_SNAPSHOTS
915}
916
917#[inline]
918const fn default_checkpointing_max_age_days() -> Option<u64> {
919    Some(DEFAULT_MAX_AGE_DAYS)
920}
921
922#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
923#[derive(Debug, Clone, Deserialize, Serialize)]
924pub struct PersistentMemoryConfig {
925    /// Toggle main-session persistent memory for this repository
926    #[serde(default = "default_persistent_memory_enabled")]
927    pub enabled: bool,
928
929    /// Write durable memory after completed turns and session finalization
930    #[serde(default = "default_persistent_memory_auto_write")]
931    pub auto_write: bool,
932
933    /// Optional user-local directory override for persistent memory storage
934    #[serde(default)]
935    pub directory_override: Option<String>,
936
937    /// Startup line budget scanned from memory_summary.md before VT Code renders a compact startup summary
938    #[serde(default = "default_persistent_memory_startup_line_limit")]
939    pub startup_line_limit: usize,
940
941    /// Startup byte budget scanned from memory_summary.md before VT Code renders a compact startup summary
942    #[serde(default = "default_persistent_memory_startup_byte_limit")]
943    pub startup_byte_limit: usize,
944}
945
946impl Default for PersistentMemoryConfig {
947    fn default() -> Self {
948        Self {
949            enabled: default_persistent_memory_enabled(),
950            auto_write: default_persistent_memory_auto_write(),
951            directory_override: None,
952            startup_line_limit: default_persistent_memory_startup_line_limit(),
953            startup_byte_limit: default_persistent_memory_startup_byte_limit(),
954        }
955    }
956}
957
958impl PersistentMemoryConfig {
959    pub fn validate(&self) -> Result<(), String> {
960        if self.startup_line_limit == 0 {
961            return Err("persistent_memory.startup_line_limit must be greater than 0".to_string());
962        }
963
964        if self.startup_byte_limit == 0 {
965            return Err("persistent_memory.startup_byte_limit must be greater than 0".to_string());
966        }
967
968        Ok(())
969    }
970}
971
972#[inline]
973const fn default_persistent_memory_enabled() -> bool {
974    false
975}
976
977#[inline]
978const fn default_persistent_memory_auto_write() -> bool {
979    true
980}
981
982#[inline]
983const fn default_persistent_memory_startup_line_limit() -> usize {
984    200
985}
986
987#[inline]
988const fn default_persistent_memory_startup_byte_limit() -> usize {
989    25 * 1024
990}
991
992#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
993#[derive(Debug, Clone, Deserialize, Serialize)]
994pub struct AgentOnboardingConfig {
995    /// Toggle onboarding message rendering
996    #[serde(default = "default_onboarding_enabled")]
997    pub enabled: bool,
998
999    /// Introductory text shown at session start
1000    #[serde(default = "default_intro_text")]
1001    pub intro_text: String,
1002
1003    /// Whether to include project overview in onboarding message
1004    #[serde(default = "default_show_project_overview")]
1005    pub include_project_overview: bool,
1006
1007    /// Whether to include language summary in onboarding message
1008    #[serde(default = "default_show_language_summary")]
1009    pub include_language_summary: bool,
1010
1011    /// Whether to include AGENTS.md highlights in onboarding message
1012    #[serde(default = "default_show_guideline_highlights")]
1013    pub include_guideline_highlights: bool,
1014
1015    /// Whether to surface usage tips inside the welcome text banner
1016    #[serde(default = "default_show_usage_tips_in_welcome")]
1017    pub include_usage_tips_in_welcome: bool,
1018
1019    /// Whether to surface suggested actions inside the welcome text banner
1020    #[serde(default = "default_show_recommended_actions_in_welcome")]
1021    pub include_recommended_actions_in_welcome: bool,
1022
1023    /// Maximum number of guideline bullets to surface
1024    #[serde(default = "default_guideline_highlight_limit")]
1025    pub guideline_highlight_limit: usize,
1026
1027    /// Tips for collaborating with the agent effectively
1028    #[serde(default = "default_usage_tips")]
1029    pub usage_tips: Vec<String>,
1030
1031    /// Recommended follow-up actions to display
1032    #[serde(default = "default_recommended_actions")]
1033    pub recommended_actions: Vec<String>,
1034
1035    /// Placeholder suggestion for the chat input bar
1036    #[serde(default)]
1037    pub chat_placeholder: Option<String>,
1038}
1039
1040impl Default for AgentOnboardingConfig {
1041    fn default() -> Self {
1042        Self {
1043            enabled: default_onboarding_enabled(),
1044            intro_text: default_intro_text(),
1045            include_project_overview: default_show_project_overview(),
1046            include_language_summary: default_show_language_summary(),
1047            include_guideline_highlights: default_show_guideline_highlights(),
1048            include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
1049            include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
1050            guideline_highlight_limit: default_guideline_highlight_limit(),
1051            usage_tips: default_usage_tips(),
1052            recommended_actions: default_recommended_actions(),
1053            chat_placeholder: None,
1054        }
1055    }
1056}
1057
1058#[inline]
1059const fn default_onboarding_enabled() -> bool {
1060    true
1061}
1062
1063const DEFAULT_INTRO_TEXT: &str =
1064    "Let's get oriented. I preloaded workspace context so we can move fast.";
1065
1066#[inline]
1067fn default_intro_text() -> String {
1068    DEFAULT_INTRO_TEXT.into()
1069}
1070
1071#[inline]
1072const fn default_show_project_overview() -> bool {
1073    true
1074}
1075
1076#[inline]
1077const fn default_show_language_summary() -> bool {
1078    false
1079}
1080
1081#[inline]
1082const fn default_show_guideline_highlights() -> bool {
1083    true
1084}
1085
1086#[inline]
1087const fn default_show_usage_tips_in_welcome() -> bool {
1088    false
1089}
1090
1091#[inline]
1092const fn default_show_recommended_actions_in_welcome() -> bool {
1093    false
1094}
1095
1096#[inline]
1097const fn default_guideline_highlight_limit() -> usize {
1098    3
1099}
1100
1101const DEFAULT_USAGE_TIPS: &[&str] = &[
1102    "Describe your current coding goal or ask for a quick status overview.",
1103    "Reference AGENTS.md guidelines when proposing changes.",
1104    "Prefer asking for targeted file reads or diffs before editing.",
1105];
1106
1107const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
1108    "Review the highlighted guidelines and share the task you want to tackle.",
1109    "Ask for a workspace tour if you need more context.",
1110];
1111
1112fn default_usage_tips() -> Vec<String> {
1113    DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
1114}
1115
1116fn default_recommended_actions() -> Vec<String> {
1117    DEFAULT_RECOMMENDED_ACTIONS
1118        .iter()
1119        .map(|s| (*s).into())
1120        .collect()
1121}
1122
1123/// Small/lightweight model configuration for efficient operations
1124///
1125/// Following VT Code's pattern, use a smaller model (e.g., Haiku, GPT-4 Mini) for 50%+ of calls:
1126/// - Large file reads and parsing (>50KB)
1127/// - Web page summarization and analysis
1128/// - Git history and commit message processing
1129/// - One-word processing labels and simple classifications
1130///
1131/// Typically 70-80% cheaper than the main model while maintaining quality for these tasks.
1132#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1133#[derive(Debug, Clone, Deserialize, Serialize)]
1134pub struct AgentSmallModelConfig {
1135    /// Enable small model tier for efficient operations
1136    #[serde(default = "default_small_model_enabled")]
1137    pub enabled: bool,
1138
1139    /// Small model to use (e.g., claude-4-5-haiku, "gpt-4-mini", "gemini-2.0-flash")
1140    /// Leave empty to auto-select a lightweight sibling of the main model
1141    #[serde(default)]
1142    pub model: String,
1143
1144    /// Temperature for small model responses
1145    #[serde(default = "default_small_model_temperature")]
1146    pub temperature: f32,
1147
1148    /// Enable small model for large file reads (>50KB)
1149    #[serde(default = "default_small_model_for_large_reads")]
1150    pub use_for_large_reads: bool,
1151
1152    /// Enable small model for web content summarization
1153    #[serde(default = "default_small_model_for_web_summary")]
1154    pub use_for_web_summary: bool,
1155
1156    /// Enable small model for git history processing
1157    #[serde(default = "default_small_model_for_git_history")]
1158    pub use_for_git_history: bool,
1159
1160    /// Enable small model for persistent memory classification and summary refresh
1161    #[serde(default = "default_small_model_for_memory")]
1162    pub use_for_memory: bool,
1163}
1164
1165impl Default for AgentSmallModelConfig {
1166    fn default() -> Self {
1167        Self {
1168            enabled: default_small_model_enabled(),
1169            model: String::new(),
1170            temperature: default_small_model_temperature(),
1171            use_for_large_reads: default_small_model_for_large_reads(),
1172            use_for_web_summary: default_small_model_for_web_summary(),
1173            use_for_git_history: default_small_model_for_git_history(),
1174            use_for_memory: default_small_model_for_memory(),
1175        }
1176    }
1177}
1178
1179#[inline]
1180const fn default_small_model_enabled() -> bool {
1181    true // Enable by default following VT Code pattern
1182}
1183
1184#[inline]
1185const fn default_small_model_temperature() -> f32 {
1186    0.3 // More deterministic for parsing/summarization
1187}
1188
1189#[inline]
1190const fn default_small_model_for_large_reads() -> bool {
1191    true
1192}
1193
1194#[inline]
1195const fn default_small_model_for_web_summary() -> bool {
1196    true
1197}
1198
1199#[inline]
1200const fn default_small_model_for_git_history() -> bool {
1201    true
1202}
1203
1204#[inline]
1205const fn default_small_model_for_memory() -> bool {
1206    true
1207}
1208
1209/// Inline prompt suggestion configuration for the chat composer.
1210#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1211#[derive(Debug, Clone, Deserialize, Serialize)]
1212pub struct AgentPromptSuggestionsConfig {
1213    /// Enable inline prompt suggestions in the chat composer.
1214    #[serde(default = "default_prompt_suggestions_enabled")]
1215    pub enabled: bool,
1216
1217    /// Lightweight model to use for suggestions.
1218    /// Leave empty to auto-select an efficient sibling of the main model.
1219    #[serde(default)]
1220    pub model: String,
1221
1222    /// Temperature for inline prompt suggestion generation.
1223    #[serde(default = "default_prompt_suggestions_temperature")]
1224    pub temperature: f32,
1225
1226    /// Whether VT Code should remind users that LLM-backed suggestions consume tokens.
1227    #[serde(default = "default_prompt_suggestions_show_cost_notice")]
1228    pub show_cost_notice: bool,
1229}
1230
1231impl Default for AgentPromptSuggestionsConfig {
1232    fn default() -> Self {
1233        Self {
1234            enabled: default_prompt_suggestions_enabled(),
1235            model: String::new(),
1236            temperature: default_prompt_suggestions_temperature(),
1237            show_cost_notice: default_prompt_suggestions_show_cost_notice(),
1238        }
1239    }
1240}
1241
1242#[inline]
1243const fn default_prompt_suggestions_enabled() -> bool {
1244    true
1245}
1246
1247#[inline]
1248const fn default_prompt_suggestions_temperature() -> f32 {
1249    0.3
1250}
1251
1252#[inline]
1253const fn default_prompt_suggestions_show_cost_notice() -> bool {
1254    true
1255}
1256
1257/// Vibe coding configuration for lazy/vague request support
1258///
1259/// Enables intelligent context gathering and entity resolution to support
1260/// casual, imprecise requests like "make it blue" or "decrease by half".
1261#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
1262#[derive(Debug, Clone, Deserialize, Serialize)]
1263pub struct AgentVibeCodingConfig {
1264    /// Enable vibe coding support
1265    #[serde(default = "default_vibe_coding_enabled")]
1266    pub enabled: bool,
1267
1268    /// Minimum prompt length for refinement (default: 5 chars)
1269    #[serde(default = "default_vibe_min_prompt_length")]
1270    pub min_prompt_length: usize,
1271
1272    /// Minimum prompt words for refinement (default: 2 words)
1273    #[serde(default = "default_vibe_min_prompt_words")]
1274    pub min_prompt_words: usize,
1275
1276    /// Enable fuzzy entity resolution
1277    #[serde(default = "default_vibe_entity_resolution")]
1278    pub enable_entity_resolution: bool,
1279
1280    /// Entity index cache file path (relative to workspace)
1281    #[serde(default = "default_vibe_entity_cache")]
1282    pub entity_index_cache: String,
1283
1284    /// Maximum entity matches to return (default: 5)
1285    #[serde(default = "default_vibe_max_entity_matches")]
1286    pub max_entity_matches: usize,
1287
1288    /// Track workspace state (file activity, value changes)
1289    #[serde(default = "default_vibe_track_workspace")]
1290    pub track_workspace_state: bool,
1291
1292    /// Maximum recent files to track (default: 20)
1293    #[serde(default = "default_vibe_max_recent_files")]
1294    pub max_recent_files: usize,
1295
1296    /// Track value history for inference
1297    #[serde(default = "default_vibe_track_values")]
1298    pub track_value_history: bool,
1299
1300    /// Enable conversation memory for pronoun resolution
1301    #[serde(default = "default_vibe_conversation_memory")]
1302    pub enable_conversation_memory: bool,
1303
1304    /// Maximum conversation turns to remember (default: 50)
1305    #[serde(default = "default_vibe_max_memory_turns")]
1306    pub max_memory_turns: usize,
1307
1308    /// Enable pronoun resolution (it, that, this)
1309    #[serde(default = "default_vibe_pronoun_resolution")]
1310    pub enable_pronoun_resolution: bool,
1311
1312    /// Enable proactive context gathering
1313    #[serde(default = "default_vibe_proactive_context")]
1314    pub enable_proactive_context: bool,
1315
1316    /// Maximum files to gather for context (default: 3)
1317    #[serde(default = "default_vibe_max_context_files")]
1318    pub max_context_files: usize,
1319
1320    /// Maximum code snippets per file (default: 20 lines)
1321    #[serde(default = "default_vibe_max_snippets_per_file")]
1322    pub max_context_snippets_per_file: usize,
1323
1324    /// Maximum search results to include (default: 5)
1325    #[serde(default = "default_vibe_max_search_results")]
1326    pub max_search_results: usize,
1327
1328    /// Enable relative value inference (by half, double, etc.)
1329    #[serde(default = "default_vibe_value_inference")]
1330    pub enable_relative_value_inference: bool,
1331}
1332
1333impl Default for AgentVibeCodingConfig {
1334    fn default() -> Self {
1335        Self {
1336            enabled: default_vibe_coding_enabled(),
1337            min_prompt_length: default_vibe_min_prompt_length(),
1338            min_prompt_words: default_vibe_min_prompt_words(),
1339            enable_entity_resolution: default_vibe_entity_resolution(),
1340            entity_index_cache: default_vibe_entity_cache(),
1341            max_entity_matches: default_vibe_max_entity_matches(),
1342            track_workspace_state: default_vibe_track_workspace(),
1343            max_recent_files: default_vibe_max_recent_files(),
1344            track_value_history: default_vibe_track_values(),
1345            enable_conversation_memory: default_vibe_conversation_memory(),
1346            max_memory_turns: default_vibe_max_memory_turns(),
1347            enable_pronoun_resolution: default_vibe_pronoun_resolution(),
1348            enable_proactive_context: default_vibe_proactive_context(),
1349            max_context_files: default_vibe_max_context_files(),
1350            max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
1351            max_search_results: default_vibe_max_search_results(),
1352            enable_relative_value_inference: default_vibe_value_inference(),
1353        }
1354    }
1355}
1356
1357// Vibe coding default functions
1358#[inline]
1359const fn default_vibe_coding_enabled() -> bool {
1360    false // Conservative default, opt-in
1361}
1362
1363#[inline]
1364const fn default_vibe_min_prompt_length() -> usize {
1365    5
1366}
1367
1368#[inline]
1369const fn default_vibe_min_prompt_words() -> usize {
1370    2
1371}
1372
1373#[inline]
1374const fn default_vibe_entity_resolution() -> bool {
1375    true
1376}
1377
1378#[inline]
1379fn default_vibe_entity_cache() -> String {
1380    ".vtcode/entity_index.json".into()
1381}
1382
1383#[inline]
1384const fn default_vibe_max_entity_matches() -> usize {
1385    5
1386}
1387
1388#[inline]
1389const fn default_vibe_track_workspace() -> bool {
1390    true
1391}
1392
1393#[inline]
1394const fn default_vibe_max_recent_files() -> usize {
1395    20
1396}
1397
1398#[inline]
1399const fn default_vibe_track_values() -> bool {
1400    true
1401}
1402
1403#[inline]
1404const fn default_vibe_conversation_memory() -> bool {
1405    true
1406}
1407
1408#[inline]
1409const fn default_vibe_max_memory_turns() -> usize {
1410    50
1411}
1412
1413#[inline]
1414const fn default_vibe_pronoun_resolution() -> bool {
1415    true
1416}
1417
1418#[inline]
1419const fn default_vibe_proactive_context() -> bool {
1420    true
1421}
1422
1423#[inline]
1424const fn default_vibe_max_context_files() -> usize {
1425    3
1426}
1427
1428#[inline]
1429const fn default_vibe_max_snippets_per_file() -> usize {
1430    20
1431}
1432
1433#[inline]
1434const fn default_vibe_max_search_results() -> usize {
1435    5
1436}
1437
1438#[inline]
1439const fn default_vibe_value_inference() -> bool {
1440    true
1441}
1442
1443#[cfg(test)]
1444mod tests {
1445    use super::*;
1446
1447    #[test]
1448    fn test_continuation_policy_defaults_and_parses() {
1449        assert_eq!(ContinuationPolicy::default(), ContinuationPolicy::All);
1450        assert_eq!(
1451            ContinuationPolicy::parse("off"),
1452            Some(ContinuationPolicy::Off)
1453        );
1454        assert_eq!(
1455            ContinuationPolicy::parse("exec-only"),
1456            Some(ContinuationPolicy::ExecOnly)
1457        );
1458        assert_eq!(
1459            ContinuationPolicy::parse("all"),
1460            Some(ContinuationPolicy::All)
1461        );
1462        assert_eq!(ContinuationPolicy::parse("invalid"), None);
1463    }
1464
1465    #[test]
1466    fn test_harness_config_continuation_policy_deserializes_with_fallback() {
1467        let parsed: AgentHarnessConfig =
1468            toml::from_str("continuation_policy = \"all\"").expect("valid harness config");
1469        assert_eq!(parsed.continuation_policy, ContinuationPolicy::All);
1470
1471        let fallback: AgentHarnessConfig =
1472            toml::from_str("continuation_policy = \"unexpected\"").expect("fallback config");
1473        assert_eq!(fallback.continuation_policy, ContinuationPolicy::All);
1474    }
1475
1476    #[test]
1477    fn test_harness_orchestration_mode_defaults_and_parses() {
1478        assert_eq!(
1479            HarnessOrchestrationMode::default(),
1480            HarnessOrchestrationMode::Single
1481        );
1482        assert_eq!(
1483            HarnessOrchestrationMode::parse("single"),
1484            Some(HarnessOrchestrationMode::Single)
1485        );
1486        assert_eq!(
1487            HarnessOrchestrationMode::parse("plan_build_evaluate"),
1488            Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1489        );
1490        assert_eq!(
1491            HarnessOrchestrationMode::parse("planner-generator-evaluator"),
1492            Some(HarnessOrchestrationMode::PlanBuildEvaluate)
1493        );
1494        assert_eq!(HarnessOrchestrationMode::parse("unexpected"), None);
1495    }
1496
1497    #[test]
1498    fn test_harness_config_orchestration_deserializes_with_fallback() {
1499        let parsed: AgentHarnessConfig =
1500            toml::from_str("orchestration_mode = \"plan_build_evaluate\"")
1501                .expect("valid harness config");
1502        assert_eq!(
1503            parsed.orchestration_mode,
1504            HarnessOrchestrationMode::PlanBuildEvaluate
1505        );
1506        assert_eq!(parsed.max_revision_rounds, 2);
1507
1508        let fallback: AgentHarnessConfig =
1509            toml::from_str("orchestration_mode = \"unexpected\"").expect("fallback config");
1510        assert_eq!(
1511            fallback.orchestration_mode,
1512            HarnessOrchestrationMode::Single
1513        );
1514    }
1515
1516    #[test]
1517    fn test_editing_mode_config_default() {
1518        let config = AgentConfig::default();
1519        assert_eq!(config.default_editing_mode, EditingMode::Edit);
1520        assert!(config.require_plan_confirmation);
1521        assert!(!config.autonomous_mode);
1522    }
1523
1524    #[test]
1525    fn test_persistent_memory_is_disabled_by_default() {
1526        let config = AgentConfig::default();
1527        assert!(!config.persistent_memory.enabled);
1528        assert!(config.persistent_memory.auto_write);
1529    }
1530
1531    #[test]
1532    fn test_tool_result_clearing_defaults() {
1533        let config = AgentConfig::default();
1534        let clearing = config.harness.tool_result_clearing;
1535
1536        assert!(!clearing.enabled);
1537        assert_eq!(clearing.trigger_tokens, 100_000);
1538        assert_eq!(clearing.keep_tool_uses, 3);
1539        assert_eq!(clearing.clear_at_least_tokens, 30_000);
1540        assert!(!clearing.clear_tool_inputs);
1541    }
1542
1543    #[test]
1544    fn test_codex_app_server_experimental_features_default_to_disabled() {
1545        let config = AgentConfig::default();
1546
1547        assert!(!config.codex_app_server.experimental_features);
1548    }
1549
1550    #[test]
1551    fn test_codex_app_server_experimental_features_parse_from_toml() {
1552        let parsed: AgentCodexAppServerConfig = toml::from_str(
1553            r#"
1554                command = "codex"
1555                args = ["app-server"]
1556                startup_timeout_secs = 15
1557                experimental_features = true
1558            "#,
1559        )
1560        .expect("valid codex app-server config");
1561
1562        assert!(parsed.experimental_features);
1563        assert_eq!(parsed.startup_timeout_secs, 15);
1564    }
1565
1566    #[test]
1567    fn test_tool_result_clearing_parses_and_validates() {
1568        let parsed: AgentHarnessConfig = toml::from_str(
1569            r#"
1570                [tool_result_clearing]
1571                enabled = true
1572                trigger_tokens = 123456
1573                keep_tool_uses = 6
1574                clear_at_least_tokens = 4096
1575                clear_tool_inputs = true
1576            "#,
1577        )
1578        .expect("valid harness config");
1579
1580        assert!(parsed.tool_result_clearing.enabled);
1581        assert_eq!(parsed.tool_result_clearing.trigger_tokens, 123_456);
1582        assert_eq!(parsed.tool_result_clearing.keep_tool_uses, 6);
1583        assert_eq!(parsed.tool_result_clearing.clear_at_least_tokens, 4_096);
1584        assert!(parsed.tool_result_clearing.clear_tool_inputs);
1585        assert!(parsed.tool_result_clearing.validate().is_ok());
1586    }
1587
1588    #[test]
1589    fn test_tool_result_clearing_rejects_zero_values() {
1590        let clearing = ToolResultClearingConfig {
1591            trigger_tokens: 0,
1592            ..ToolResultClearingConfig::default()
1593        };
1594        assert!(clearing.validate().is_err());
1595
1596        let clearing = ToolResultClearingConfig {
1597            keep_tool_uses: 0,
1598            ..ToolResultClearingConfig::default()
1599        };
1600        assert!(clearing.validate().is_err());
1601
1602        let clearing = ToolResultClearingConfig {
1603            clear_at_least_tokens: 0,
1604            ..ToolResultClearingConfig::default()
1605        };
1606        assert!(clearing.validate().is_err());
1607    }
1608
1609    #[test]
1610    fn test_structured_reasoning_defaults_follow_prompt_mode() {
1611        let default_mode = AgentConfig {
1612            system_prompt_mode: SystemPromptMode::Default,
1613            ..Default::default()
1614        };
1615        assert!(!default_mode.should_include_structured_reasoning_tags());
1616
1617        let specialized_mode = AgentConfig {
1618            system_prompt_mode: SystemPromptMode::Specialized,
1619            ..Default::default()
1620        };
1621        assert!(specialized_mode.should_include_structured_reasoning_tags());
1622
1623        let minimal_mode = AgentConfig {
1624            system_prompt_mode: SystemPromptMode::Minimal,
1625            ..Default::default()
1626        };
1627        assert!(!minimal_mode.should_include_structured_reasoning_tags());
1628
1629        let lightweight_mode = AgentConfig {
1630            system_prompt_mode: SystemPromptMode::Lightweight,
1631            ..Default::default()
1632        };
1633        assert!(!lightweight_mode.should_include_structured_reasoning_tags());
1634    }
1635
1636    #[test]
1637    fn test_structured_reasoning_explicit_override() {
1638        let mut config = AgentConfig {
1639            system_prompt_mode: SystemPromptMode::Minimal,
1640            include_structured_reasoning_tags: Some(true),
1641            ..AgentConfig::default()
1642        };
1643        assert!(config.should_include_structured_reasoning_tags());
1644
1645        config.include_structured_reasoning_tags = Some(false);
1646        assert!(!config.should_include_structured_reasoning_tags());
1647    }
1648}