Skip to main content

vtcode_config/core/
agent.rs

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