Skip to main content

vtcode_config/loader/
config.rs

1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6use crate::acp::AgentClientProtocolConfig;
7use crate::codex::{FileOpener, HistoryConfig, TuiConfig};
8use crate::context::ContextFeaturesConfig;
9use crate::core::{
10    AgentConfig, AnthropicConfig, AuthConfig, AutomationConfig, CommandsConfig,
11    CustomProviderConfig, DotfileProtectionConfig, ModelConfig, OpenAIConfig, PermissionsConfig,
12    PromptCachingConfig, SandboxConfig, SecurityConfig, SkillsConfig, ToolsConfig,
13};
14use crate::debug::DebugConfig;
15use crate::defaults::{self, ConfigDefaultsProvider};
16use crate::hooks::HooksConfig;
17use crate::ide_context::IdeContextConfig;
18use crate::mcp::McpClientConfig;
19use crate::optimization::OptimizationConfig;
20use crate::output_styles::OutputStyleConfig;
21use crate::root::{ChatConfig, PtyConfig, UiConfig};
22use crate::subagents::SubagentRuntimeLimits;
23use crate::telemetry::TelemetryConfig;
24use crate::timeouts::TimeoutsConfig;
25
26use crate::loader::syntax_highlighting::SyntaxHighlightingConfig;
27
28/// Provider-specific configuration
29#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[derive(Debug, Clone, Deserialize, Serialize, Default)]
31pub struct ProviderConfig {
32    /// OpenAI provider configuration
33    #[serde(default)]
34    pub openai: OpenAIConfig,
35
36    /// Anthropic provider configuration
37    #[serde(default)]
38    pub anthropic: AnthropicConfig,
39}
40
41/// Main configuration structure for VT Code
42#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
43#[derive(Debug, Clone, Deserialize, Serialize, Default)]
44pub struct VTCodeConfig {
45    /// Codex-compatible clickable citation URI scheme.
46    #[serde(default)]
47    pub file_opener: FileOpener,
48
49    /// Codex-compatible project doc byte limit override.
50    #[serde(default)]
51    pub project_doc_max_bytes: Option<usize>,
52
53    /// Codex-compatible fallback filenames for project instructions discovery.
54    #[serde(default)]
55    pub project_doc_fallback_filenames: Vec<String>,
56
57    /// External notification command invoked for supported events.
58    #[serde(default)]
59    pub notify: Vec<String>,
60
61    /// Codex-compatible local history persistence controls.
62    #[serde(default)]
63    pub history: HistoryConfig,
64
65    /// Codex-compatible TUI settings.
66    #[serde(default)]
67    pub tui: TuiConfig,
68
69    /// Agent-wide settings
70    #[serde(default)]
71    pub agent: AgentConfig,
72
73    /// Authentication configuration for OAuth flows
74    #[serde(default)]
75    pub auth: AuthConfig,
76
77    /// Tool execution policies
78    #[serde(default)]
79    pub tools: ToolsConfig,
80
81    /// Unix command permissions
82    #[serde(default)]
83    pub commands: CommandsConfig,
84
85    /// Permission system settings (resolution, audit logging, caching)
86    #[serde(default)]
87    pub permissions: PermissionsConfig,
88
89    /// Security settings
90    #[serde(default)]
91    pub security: SecurityConfig,
92
93    /// Sandbox settings for command execution isolation
94    #[serde(default)]
95    pub sandbox: SandboxConfig,
96
97    /// UI settings
98    #[serde(default)]
99    pub ui: UiConfig,
100
101    /// Chat settings
102    #[serde(default)]
103    pub chat: ChatConfig,
104
105    /// PTY settings
106    #[serde(default)]
107    pub pty: PtyConfig,
108
109    /// Debug and tracing settings
110    #[serde(default)]
111    pub debug: DebugConfig,
112
113    /// Context features (e.g., Decision Ledger)
114    #[serde(default)]
115    pub context: ContextFeaturesConfig,
116
117    /// Telemetry configuration (logging, trajectory)
118    #[serde(default)]
119    pub telemetry: TelemetryConfig,
120
121    /// Performance optimization settings
122    #[serde(default)]
123    pub optimization: OptimizationConfig,
124
125    /// Syntax highlighting configuration
126    #[serde(default)]
127    pub syntax_highlighting: SyntaxHighlightingConfig,
128
129    /// Timeout ceilings and UI warning thresholds
130    #[serde(default)]
131    pub timeouts: TimeoutsConfig,
132
133    /// Automation configuration
134    #[serde(default)]
135    pub automation: AutomationConfig,
136
137    /// Subagent runtime configuration
138    #[serde(default)]
139    pub subagents: SubagentRuntimeLimits,
140
141    /// Prompt cache configuration (local + provider integration)
142    #[serde(default)]
143    pub prompt_cache: PromptCachingConfig,
144
145    /// Model Context Protocol configuration
146    #[serde(default)]
147    pub mcp: McpClientConfig,
148
149    /// Agent Client Protocol configuration
150    #[serde(default)]
151    pub acp: AgentClientProtocolConfig,
152
153    /// IDE context configuration
154    #[serde(default)]
155    pub ide_context: IdeContextConfig,
156
157    /// Lifecycle hooks configuration
158    #[serde(default)]
159    pub hooks: HooksConfig,
160
161    /// Model-specific behavior configuration
162    #[serde(default)]
163    pub model: ModelConfig,
164
165    /// Provider-specific configuration
166    #[serde(default)]
167    pub provider: ProviderConfig,
168
169    /// Skills system configuration (Agent Skills spec)
170    #[serde(default)]
171    pub skills: SkillsConfig,
172
173    /// User-defined OpenAI-compatible provider endpoints.
174    /// These entries are editable in `/config` and appear in the model picker
175    /// using each entry's `display_name`.
176    #[serde(default)]
177    pub custom_providers: Vec<CustomProviderConfig>,
178
179    /// Output style configuration
180    #[serde(default)]
181    pub output_style: OutputStyleConfig,
182
183    /// Dotfile protection configuration
184    #[serde(default)]
185    pub dotfile_protection: DotfileProtectionConfig,
186}
187
188impl VTCodeConfig {
189    pub fn apply_compat_defaults(&mut self) {
190        if let Some(max_bytes) = self.project_doc_max_bytes {
191            self.agent.project_doc_max_bytes = max_bytes;
192        }
193
194        if !self.project_doc_fallback_filenames.is_empty() {
195            self.agent.project_doc_fallback_filenames = self.project_doc_fallback_filenames.clone();
196        }
197    }
198
199    pub fn validate(&self) -> Result<()> {
200        self.syntax_highlighting
201            .validate()
202            .context("Invalid syntax_highlighting configuration")?;
203
204        self.context
205            .validate()
206            .context("Invalid context configuration")?;
207
208        self.hooks
209            .validate()
210            .context("Invalid hooks configuration")?;
211
212        self.timeouts
213            .validate()
214            .context("Invalid timeouts configuration")?;
215
216        self.prompt_cache
217            .validate()
218            .context("Invalid prompt_cache configuration")?;
219
220        self.ui
221            .keyboard_protocol
222            .validate()
223            .context("Invalid keyboard_protocol configuration")?;
224
225        self.pty.validate().context("Invalid pty configuration")?;
226
227        // Validate custom providers
228        let mut seen_names = std::collections::HashSet::new();
229        for cp in &self.custom_providers {
230            cp.validate()
231                .map_err(|msg| anyhow::anyhow!(msg))
232                .context("Invalid custom_providers configuration")?;
233            if !seen_names.insert(cp.name.to_lowercase()) {
234                anyhow::bail!("custom_providers: duplicate name `{}`", cp.name);
235            }
236        }
237
238        Ok(())
239    }
240
241    /// Look up a custom provider by its stable key.
242    pub fn custom_provider(&self, name: &str) -> Option<&CustomProviderConfig> {
243        let lower = name.to_lowercase();
244        self.custom_providers
245            .iter()
246            .find(|cp| cp.name.to_lowercase() == lower)
247    }
248
249    /// Get the display name for any provider key, falling back to the raw key
250    /// if no custom provider matches.
251    pub fn provider_display_name(&self, provider_key: &str) -> String {
252        if let Some(cp) = self.custom_provider(provider_key) {
253            cp.display_name.clone()
254        } else if let Ok(p) = std::str::FromStr::from_str(provider_key) {
255            let p: crate::models::Provider = p;
256            p.label().to_string()
257        } else {
258            provider_key.to_string()
259        }
260    }
261
262    #[cfg(feature = "bootstrap")]
263    /// Bootstrap project with config + gitignore
264    pub fn bootstrap_project<P: AsRef<Path>>(workspace: P, force: bool) -> Result<Vec<String>> {
265        Self::bootstrap_project_with_options(workspace, force, false)
266    }
267
268    #[cfg(feature = "bootstrap")]
269    /// Bootstrap project with config + gitignore, with option to create in home directory
270    pub fn bootstrap_project_with_options<P: AsRef<Path>>(
271        workspace: P,
272        force: bool,
273        use_home_dir: bool,
274    ) -> Result<Vec<String>> {
275        let workspace = workspace.as_ref().to_path_buf();
276        defaults::with_config_defaults(|provider| {
277            Self::bootstrap_project_with_provider(&workspace, force, use_home_dir, provider)
278        })
279    }
280
281    #[cfg(feature = "bootstrap")]
282    /// Bootstrap project files using the supplied [`ConfigDefaultsProvider`].
283    pub fn bootstrap_project_with_provider<P: AsRef<Path>>(
284        workspace: P,
285        force: bool,
286        use_home_dir: bool,
287        defaults_provider: &dyn ConfigDefaultsProvider,
288    ) -> Result<Vec<String>> {
289        let workspace = workspace.as_ref();
290        let config_file_name = defaults_provider.config_file_name().to_string();
291        let (config_path, gitignore_path) = crate::loader::bootstrap::determine_bootstrap_targets(
292            workspace,
293            use_home_dir,
294            &config_file_name,
295            defaults_provider,
296        )?;
297
298        crate::loader::bootstrap::ensure_parent_dir(&config_path)?;
299        crate::loader::bootstrap::ensure_parent_dir(&gitignore_path)?;
300
301        let mut created_files = Vec::new();
302
303        if !config_path.exists() || force {
304            let config_content = Self::default_vtcode_toml_template();
305
306            fs::write(&config_path, config_content).with_context(|| {
307                format!("Failed to write config file: {}", config_path.display())
308            })?;
309
310            if let Some(file_name) = config_path.file_name().and_then(|name| name.to_str()) {
311                created_files.push(file_name.to_string());
312            }
313        }
314
315        if !gitignore_path.exists() || force {
316            let gitignore_content = Self::default_vtcode_gitignore();
317            fs::write(&gitignore_path, gitignore_content).with_context(|| {
318                format!(
319                    "Failed to write gitignore file: {}",
320                    gitignore_path.display()
321                )
322            })?;
323
324            if let Some(file_name) = gitignore_path.file_name().and_then(|name| name.to_str()) {
325                created_files.push(file_name.to_string());
326            }
327        }
328
329        Ok(created_files)
330    }
331
332    #[cfg(feature = "bootstrap")]
333    /// Generate the default `vtcode.toml` template used by bootstrap helpers.
334    fn default_vtcode_toml_template() -> String {
335        r#"# VT Code Configuration File (Example)
336# Getting-started reference; see docs/config/CONFIGURATION_PRECEDENCE.md for override order.
337# Copy this file to vtcode.toml and customize as needed.
338
339# Clickable file citation URI scheme ("vscode", "cursor", "windsurf", "vscode-insiders", "none")
340file_opener = "none"
341
342# Additional fallback filenames to use when AGENTS.md is absent
343project_doc_fallback_filenames = []
344
345# Optional external command invoked after each completed agent turn
346notify = []
347
348# User-defined OpenAI-compatible providers
349custom_providers = []
350
351# [[custom_providers]]
352# name = "mycorp"
353# display_name = "MyCorporateName"
354# base_url = "https://llm.corp.example/v1"
355# api_key_env = "MYCORP_API_KEY"
356# model = "gpt-5-mini"
357
358[history]
359# Persist local session transcripts to disk
360persistence = "file"
361
362# Optional max size budget for each persisted session snapshot
363# max_bytes = 104857600
364
365[tui]
366# Enable all built-in TUI notifications, disable them, or restrict to specific event types.
367# notifications = true
368# notifications = ["agent-turn-complete", "approval-requested"]
369
370# Notification transport: "auto", "osc9", or "bel"
371# notification_method = "auto"
372
373# Set to false to reduce shimmer/animation effects
374# animations = true
375
376# Alternate-screen override: "always" or "never"
377# alternate_screen = "never"
378
379# Show onboarding hints on the welcome screen
380# show_tooltips = true
381
382# Core agent behavior; see docs/config/CONFIGURATION_PRECEDENCE.md.
383[agent]
384# Primary LLM provider to use (e.g., "openai", "gemini", "anthropic", "openrouter")
385provider = "openai"
386
387# Environment variable containing the API key for the provider
388api_key_env = "OPENAI_API_KEY"
389
390# Default model to use when no specific model is specified
391default_model = "gpt-5.4"
392
393# Visual theme for the terminal interface
394theme = "ciapre-dark"
395
396# Enable TODO planning helper mode for structured task management
397todo_planning_mode = true
398
399# UI surface to use ("auto", "alternate", "inline")
400ui_surface = "auto"
401
402# Maximum number of conversation turns before rotating context (affects memory usage)
403# Lower values reduce memory footprint but may lose context; higher values preserve context but use more memory
404max_conversation_turns = 150
405
406# Reasoning effort level ("none", "minimal", "low", "medium", "high", "xhigh") - affects model usage and response speed
407reasoning_effort = "none"
408
409# Temperature for main model responses (0.0-1.0)
410temperature = 0.7
411
412# Enable self-review loop to check and improve responses (increases API calls)
413enable_self_review = false
414
415# Maximum number of review passes when self-review is enabled
416max_review_passes = 1
417
418# Enable prompt refinement loop for improved prompt quality (increases processing time)
419refine_prompts_enabled = false
420
421# Maximum passes for prompt refinement when enabled
422refine_prompts_max_passes = 1
423
424# Optional alternate model for refinement (leave empty to use default)
425refine_prompts_model = ""
426
427# Maximum size of project documentation to include in context (in bytes)
428project_doc_max_bytes = 16384
429
430# Maximum size of instruction files to process (in bytes)
431instruction_max_bytes = 16384
432
433# List of additional instruction files to include in context
434instruction_files = []
435
436# Default editing mode on startup: "edit" or "plan"
437# "edit" - Full tool access for file modifications and command execution (default)
438# "plan" - Read-only mode that produces implementation plans without making changes
439# Toggle during session with Shift+Tab or /plan command
440default_editing_mode = "edit"
441
442# Inline prompt suggestions for the chat composer
443[agent.prompt_suggestions]
444# Enable Alt+P ghost-text suggestions in the composer
445enabled = true
446
447# Lightweight model for prompt suggestions (leave empty to auto-pick)
448model = ""
449
450# Lower values keep suggestions stable and completion-like
451temperature = 0.3
452
453# Show a one-time note that LLM-backed suggestions can consume tokens
454show_cost_notice = true
455
456# Onboarding configuration - Customize the startup experience
457[agent.onboarding]
458# Enable the onboarding welcome message on startup
459enabled = true
460
461# Custom introduction text shown on startup
462intro_text = "Let's get oriented. I preloaded workspace context so we can move fast."
463
464# Include project overview information in welcome
465include_project_overview = true
466
467# Include language summary information in welcome
468include_language_summary = false
469
470# Include key guideline highlights from AGENTS.md
471include_guideline_highlights = true
472
473# Include usage tips in the welcome message
474include_usage_tips_in_welcome = false
475
476# Include recommended actions in the welcome message
477include_recommended_actions_in_welcome = false
478
479# Maximum number of guideline highlights to show
480guideline_highlight_limit = 3
481
482# List of usage tips shown during onboarding
483usage_tips = [
484    "Describe your current coding goal or ask for a quick status overview.",
485    "Reference AGENTS.md guidelines when proposing changes.",
486    "Prefer asking for targeted file reads or diffs before editing.",
487]
488
489# List of recommended actions shown during onboarding
490recommended_actions = [
491    "Review the highlighted guidelines and share the task you want to tackle.",
492    "Ask for a workspace tour if you need more context.",
493]
494
495# Checkpointing configuration for session persistence
496[agent.checkpointing]
497# Enable automatic session checkpointing
498enabled = true
499
500# Maximum number of checkpoints to keep on disk
501max_snapshots = 50
502
503# Maximum age of checkpoints to keep (in days)
504max_age_days = 30
505
506# Tool security configuration
507[tools]
508# Default policy when no specific policy is defined ("allow", "prompt", "deny")
509# "allow" - Execute without confirmation
510# "prompt" - Ask for confirmation
511# "deny" - Block the tool
512default_policy = "prompt"
513
514# Maximum number of tool loops allowed per turn
515# Set to 0 to disable the limit and let other turn safeguards govern termination.
516max_tool_loops = 0
517
518# Maximum number of repeated identical tool calls (prevents stuck loops)
519max_repeated_tool_calls = 2
520
521# Maximum consecutive blocked tool calls before force-breaking the turn
522# Helps prevent high-CPU churn when calls are repeatedly denied/blocked
523max_consecutive_blocked_tool_calls_per_turn = 8
524
525# Maximum sequential spool-chunk reads per turn before nudging targeted extraction/summarization
526max_sequential_spool_chunk_reads = 6
527
528# Specific tool policies - Override default policy for individual tools
529[tools.policies]
530apply_patch = "prompt"            # Apply code patches (requires confirmation)
531request_user_input = "allow"      # Ask focused user questions when the task requires it
532task_tracker = "prompt"           # Create or update explicit task plans
533unified_exec = "prompt"           # Run commands; pipe-first by default, set tty=true for PTY/interactive sessions
534unified_file = "allow"            # Canonical file read/write/edit/move/copy/delete surface
535unified_search = "allow"          # Canonical search/list/intelligence/error surface
536
537# Command security - Define safe and dangerous command patterns
538[commands]
539# Commands that are always allowed without confirmation
540allow_list = [
541    "ls",           # List directory contents
542    "pwd",          # Print working directory
543    "git status",   # Show git status
544    "git diff",     # Show git differences
545    "cargo check",  # Check Rust code
546    "echo",         # Print text
547]
548
549# Commands that are never allowed
550deny_list = [
551    "rm -rf /",        # Delete root directory (dangerous)
552    "rm -rf ~",        # Delete home directory (dangerous)
553    "shutdown",        # Shut down system (dangerous)
554    "reboot",          # Reboot system (dangerous)
555    "sudo *",          # Any sudo command (dangerous)
556    ":(){ :|:& };:",   # Fork bomb (dangerous)
557]
558
559# Command patterns that are allowed (supports glob patterns)
560allow_glob = [
561    "git *",        # All git commands
562    "cargo *",      # All cargo commands
563    "python -m *",  # Python module commands
564]
565
566# Command patterns that are denied (supports glob patterns)
567deny_glob = [
568    "rm *",         # All rm commands
569    "sudo *",       # All sudo commands
570    "chmod *",      # All chmod commands
571    "chown *",      # All chown commands
572    "kubectl *",    # All kubectl commands (admin access)
573]
574
575# Regular expression patterns for allowed commands (if needed)
576allow_regex = []
577
578# Regular expression patterns for denied commands (if needed)
579deny_regex = []
580
581# Security configuration - Safety settings for automated operations
582[security]
583# Require human confirmation for potentially dangerous actions
584human_in_the_loop = true
585
586# Require explicit write tool usage for claims about file modifications
587require_write_tool_for_claims = true
588
589# Auto-apply patches without prompting (DANGEROUS - disable for safety)
590auto_apply_detected_patches = false
591
592# UI configuration - Terminal and display settings
593[ui]
594# Tool output display mode
595# "compact" - Concise tool output
596# "full" - Detailed tool output
597tool_output_mode = "compact"
598
599# Maximum number of lines to display in tool output (prevents transcript flooding)
600# Lines beyond this limit are truncated to a tail preview
601tool_output_max_lines = 600
602
603# Maximum bytes threshold for spooling tool output to disk
604# Output exceeding this size is written to .vtcode/tool-output/*.log
605tool_output_spool_bytes = 200000
606
607# Optional custom directory for spooled tool output logs
608# If not set, defaults to .vtcode/tool-output/
609# tool_output_spool_dir = "/path/to/custom/spool/dir"
610
611# Allow ANSI escape sequences in tool output (enables colors but may cause layout issues)
612allow_tool_ansi = false
613
614# Number of rows to allocate for inline UI viewport
615inline_viewport_rows = 16
616
617# Show elapsed time divider after each completed turn
618show_turn_timer = false
619
620# Show warning/error/fatal diagnostic lines directly in transcript
621# Effective in debug/development builds only
622show_diagnostics_in_transcript = false
623
624# Show timeline navigation panel
625show_timeline_pane = false
626
627# Runtime notification preferences
628[ui.notifications]
629# Master toggle for terminal/desktop notifications
630enabled = true
631
632# Delivery mode: "terminal", "hybrid", or "desktop"
633delivery_mode = "hybrid"
634
635# Suppress notifications while terminal is focused
636suppress_when_focused = true
637
638# Failure/error notifications
639command_failure = false
640tool_failure = false
641error = true
642
643# Completion notifications
644# Legacy master toggle (fallback for split settings when unset)
645completion = true
646completion_success = false
647completion_failure = true
648
649# Human approval/interaction notifications
650hitl = true
651policy_approval = true
652request = false
653
654# Success notifications for tool call results
655tool_success = false
656
657# Repeated notification suppression
658repeat_window_seconds = 30
659max_identical_in_window = 1
660
661# Status line configuration
662[ui.status_line]
663# Status line mode ("auto", "command", "hidden")
664mode = "auto"
665
666# How often to refresh status line (milliseconds)
667refresh_interval_ms = 2000
668
669# Timeout for command execution in status line (milliseconds)
670command_timeout_ms = 200
671
672# Enable Vim-style prompt editing in interactive mode
673vim_mode = false
674
675# PTY (Pseudo Terminal) configuration - For interactive command execution
676[pty]
677# Enable PTY support for interactive commands
678enabled = true
679
680# Default number of terminal rows for PTY sessions
681default_rows = 24
682
683# Default number of terminal columns for PTY sessions
684default_cols = 80
685
686# Maximum number of concurrent PTY sessions
687max_sessions = 10
688
689# Command timeout in seconds (prevents hanging commands)
690command_timeout_seconds = 300
691
692# Number of recent lines to show in PTY output
693stdout_tail_lines = 20
694
695# Total lines to keep in PTY scrollback buffer
696scrollback_lines = 400
697
698# Terminal emulation backend for PTY snapshots
699emulation_backend = "legacy_vt100"
700
701# Optional preferred shell for PTY sessions (falls back to $SHELL when unset)
702# preferred_shell = "/bin/zsh"
703
704# Route shell execution through zsh EXEC_WRAPPER intercept hooks (feature-gated)
705shell_zsh_fork = false
706
707# Absolute path to patched zsh used when shell_zsh_fork is enabled
708# zsh_path = "/usr/local/bin/zsh"
709
710# Context management configuration - Controls conversation memory
711[context]
712# Maximum number of tokens to keep in context (affects model cost and performance)
713# Higher values preserve more context but cost more and may hit token limits
714max_context_tokens = 90000
715
716# Percentage to trim context to when it gets too large
717trim_to_percent = 60
718
719# Number of recent conversation turns to always preserve
720preserve_recent_turns = 6
721
722# Decision ledger configuration - Track important decisions
723[context.ledger]
724# Enable decision tracking and persistence
725enabled = true
726
727# Maximum number of decisions to keep in ledger
728max_entries = 12
729
730# Include ledger summary in model prompts
731include_in_prompt = true
732
733# Preserve ledger during context compression
734preserve_in_compression = true
735
736# AI model routing - Intelligent model selection
737# Telemetry and analytics
738[telemetry]
739# Enable trajectory logging for usage analysis
740trajectory_enabled = true
741
742# Syntax highlighting configuration
743[syntax_highlighting]
744# Enable syntax highlighting for code in tool output
745enabled = true
746
747# Theme for syntax highlighting
748theme = "base16-ocean.dark"
749
750# Cache syntax highlighting themes for performance
751cache_themes = true
752
753# Maximum file size for syntax highlighting (in MB)
754max_file_size_mb = 10
755
756# Programming languages to enable syntax highlighting for
757enabled_languages = [
758    "rust",
759    "python",
760    "javascript",
761    "typescript",
762    "go",
763    "java",
764    "bash",
765    "sh",
766    "shell",
767    "zsh",
768    "markdown",
769    "md",
770]
771
772# Timeout for syntax highlighting operations (milliseconds)
773highlight_timeout_ms = 1000
774
775# Automation features - Full-auto mode settings
776[automation.full_auto]
777# Enable full automation mode (DANGEROUS - requires careful oversight)
778enabled = false
779
780# Maximum number of turns before asking for human input
781max_turns = 30
782
783# Tools allowed in full automation mode
784allowed_tools = [
785    "write_file",
786    "read_file",
787    "list_files",
788    "grep_file",
789]
790
791# Require profile acknowledgment before using full auto
792require_profile_ack = true
793
794# Path to full auto profile configuration
795profile_path = "automation/full_auto_profile.toml"
796
797# Prompt caching - Cache model responses for efficiency
798[prompt_cache]
799# Enable prompt caching (reduces API calls for repeated prompts)
800enabled = true
801
802# Directory for cache storage
803cache_dir = "~/.vtcode/cache/prompts"
804
805# Maximum number of cache entries to keep
806max_entries = 1000
807
808# Maximum age of cache entries (in days)
809max_age_days = 30
810
811# Enable automatic cache cleanup
812enable_auto_cleanup = true
813
814# Minimum quality threshold to keep cache entries
815min_quality_threshold = 0.7
816
817# Keep volatile runtime counters at the end of system prompts to improve provider-side prefix cache reuse
818# (enabled by default; disable only if you need legacy prompt layout)
819cache_friendly_prompt_shaping = true
820
821# Prompt cache configuration for OpenAI
822    [prompt_cache.providers.openai]
823    enabled = true
824    min_prefix_tokens = 1024
825    idle_expiration_seconds = 3600
826    surface_metrics = true
827    # Routing key strategy for OpenAI prompt cache locality.
828    # "session" creates one stable key per VT Code conversation.
829    prompt_cache_key_mode = "session"
830    # Optional: server-side prompt cache retention for OpenAI Responses API
831    # Supported values: "in_memory" or "24h" (leave commented out for default behavior)
832    # prompt_cache_retention = "24h"
833
834# Prompt cache configuration for Anthropic
835[prompt_cache.providers.anthropic]
836enabled = true
837default_ttl_seconds = 300
838extended_ttl_seconds = 3600
839max_breakpoints = 4
840cache_system_messages = true
841cache_user_messages = true
842
843# Prompt cache configuration for Gemini
844[prompt_cache.providers.gemini]
845enabled = true
846mode = "implicit"
847min_prefix_tokens = 1024
848explicit_ttl_seconds = 3600
849
850# Prompt cache configuration for OpenRouter
851[prompt_cache.providers.openrouter]
852enabled = true
853propagate_provider_capabilities = true
854report_savings = true
855
856# Prompt cache configuration for Moonshot
857[prompt_cache.providers.moonshot]
858enabled = true
859
860# Prompt cache configuration for DeepSeek
861[prompt_cache.providers.deepseek]
862enabled = true
863surface_metrics = true
864
865# Prompt cache configuration for Z.AI
866[prompt_cache.providers.zai]
867enabled = false
868
869# Model Context Protocol (MCP) - Connect external tools and services
870[mcp]
871# Enable Model Context Protocol (may impact startup time if services unavailable)
872enabled = true
873max_concurrent_connections = 5
874request_timeout_seconds = 30
875retry_attempts = 3
876
877# MCP UI configuration
878[mcp.ui]
879mode = "compact"
880max_events = 50
881show_provider_names = true
882
883# MCP renderer profiles for different services
884[mcp.ui.renderers]
885sequential-thinking = "sequential-thinking"
886context7 = "context7"
887
888# MCP provider configuration - External services that connect via MCP
889[[mcp.providers]]
890name = "time"
891command = "uvx"
892args = ["mcp-server-time"]
893enabled = true
894max_concurrent_requests = 3
895[mcp.providers.env]
896
897# Agent Client Protocol (ACP) - IDE integration
898[acp]
899enabled = true
900
901[acp.zed]
902enabled = true
903transport = "stdio"
904# workspace_trust controls ACP trust mode: "tools_policy" (prompts) or "full_auto" (no prompts)
905workspace_trust = "full_auto"
906
907[acp.zed.tools]
908read_file = true
909list_files = true
910
911# Cross-IDE editor context bridge
912[ide_context]
913enabled = true
914inject_into_prompt = true
915show_in_tui = true
916include_selection_text = true
917provider_mode = "auto"
918
919[ide_context.providers.vscode_compatible]
920enabled = true
921
922[ide_context.providers.zed]
923enabled = true
924
925[ide_context.providers.generic]
926enabled = true"#.to_string()
927    }
928
929    #[cfg(feature = "bootstrap")]
930    fn default_vtcode_gitignore() -> String {
931        r#"# Security-focused exclusions
932.env, .env.local, secrets/, .aws/, .ssh/
933
934# Development artifacts
935target/, build/, dist/, node_modules/, vendor/
936
937# Database files
938*.db, *.sqlite, *.sqlite3
939
940# Binary files
941*.exe, *.dll, *.so, *.dylib, *.bin
942
943# IDE files (comprehensive)
944.vscode/, .idea/, *.swp, *.swo
945"#
946        .to_string()
947    }
948
949    #[cfg(feature = "bootstrap")]
950    /// Create sample configuration file
951    pub fn create_sample_config<P: AsRef<Path>>(output: P) -> Result<()> {
952        let output = output.as_ref();
953        let config_content = Self::default_vtcode_toml_template();
954
955        fs::write(output, config_content)
956            .with_context(|| format!("Failed to write config file: {}", output.display()))?;
957
958        Ok(())
959    }
960}