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::context::ContextFeaturesConfig;
8use crate::core::{
9    AgentConfig, AnthropicConfig, AuthConfig, AutomationConfig, CommandsConfig,
10    DotfileProtectionConfig, ModelConfig, PermissionsConfig, PromptCachingConfig, SandboxConfig,
11    SecurityConfig, SkillsConfig, ToolsConfig,
12};
13use crate::debug::DebugConfig;
14use crate::defaults::{self, ConfigDefaultsProvider};
15use crate::hooks::HooksConfig;
16use crate::mcp::McpClientConfig;
17use crate::optimization::OptimizationConfig;
18use crate::output_styles::OutputStyleConfig;
19use crate::root::{PtyConfig, UiConfig};
20use crate::subagent::SubagentsConfig;
21use crate::telemetry::TelemetryConfig;
22use crate::timeouts::TimeoutsConfig;
23
24use crate::loader::syntax_highlighting::SyntaxHighlightingConfig;
25
26/// Provider-specific configuration
27#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
28#[derive(Debug, Clone, Deserialize, Serialize, Default)]
29pub struct ProviderConfig {
30    /// Anthropic provider configuration
31    #[serde(default)]
32    pub anthropic: AnthropicConfig,
33}
34
35/// Main configuration structure for VT Code
36#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
37#[derive(Debug, Clone, Deserialize, Serialize, Default)]
38pub struct VTCodeConfig {
39    /// Agent-wide settings
40    #[serde(default)]
41    pub agent: AgentConfig,
42
43    /// Authentication configuration for OAuth flows
44    #[serde(default)]
45    pub auth: AuthConfig,
46
47    /// Tool execution policies
48    #[serde(default)]
49    pub tools: ToolsConfig,
50
51    /// Unix command permissions
52    #[serde(default)]
53    pub commands: CommandsConfig,
54
55    /// Permission system settings (resolution, audit logging, caching)
56    #[serde(default)]
57    pub permissions: PermissionsConfig,
58
59    /// Security settings
60    #[serde(default)]
61    pub security: SecurityConfig,
62
63    /// Sandbox settings for command execution isolation
64    #[serde(default)]
65    pub sandbox: SandboxConfig,
66
67    /// UI settings
68    #[serde(default)]
69    pub ui: UiConfig,
70
71    /// PTY settings
72    #[serde(default)]
73    pub pty: PtyConfig,
74
75    /// Debug and tracing settings
76    #[serde(default)]
77    pub debug: DebugConfig,
78
79    /// Context features (e.g., Decision Ledger)
80    #[serde(default)]
81    pub context: ContextFeaturesConfig,
82
83    /// Telemetry configuration (logging, trajectory)
84    #[serde(default)]
85    pub telemetry: TelemetryConfig,
86
87    /// Performance optimization settings
88    #[serde(default)]
89    pub optimization: OptimizationConfig,
90
91    /// Syntax highlighting configuration
92    #[serde(default)]
93    pub syntax_highlighting: SyntaxHighlightingConfig,
94
95    /// Timeout ceilings and UI warning thresholds
96    #[serde(default)]
97    pub timeouts: TimeoutsConfig,
98
99    /// Automation configuration
100    #[serde(default)]
101    pub automation: AutomationConfig,
102
103    /// Prompt cache configuration (local + provider integration)
104    #[serde(default)]
105    pub prompt_cache: PromptCachingConfig,
106
107    /// Model Context Protocol configuration
108    #[serde(default)]
109    pub mcp: McpClientConfig,
110
111    /// Agent Client Protocol configuration
112    #[serde(default)]
113    pub acp: AgentClientProtocolConfig,
114
115    /// Lifecycle hooks configuration
116    #[serde(default)]
117    pub hooks: HooksConfig,
118
119    /// Model-specific behavior configuration
120    #[serde(default)]
121    pub model: ModelConfig,
122
123    /// Provider-specific configuration
124    #[serde(default)]
125    pub provider: ProviderConfig,
126
127    /// Skills system configuration (Agent Skills spec)
128    #[serde(default)]
129    pub skills: SkillsConfig,
130
131    /// Subagent system configuration
132    #[serde(default)]
133    pub subagents: SubagentsConfig,
134
135    /// Output style configuration
136    #[serde(default)]
137    pub output_style: OutputStyleConfig,
138
139    /// Dotfile protection configuration
140    #[serde(default)]
141    pub dotfile_protection: DotfileProtectionConfig,
142}
143
144impl VTCodeConfig {
145    pub fn validate(&self) -> Result<()> {
146        self.syntax_highlighting
147            .validate()
148            .context("Invalid syntax_highlighting configuration")?;
149
150        self.context
151            .validate()
152            .context("Invalid context configuration")?;
153
154        self.hooks
155            .validate()
156            .context("Invalid hooks configuration")?;
157
158        self.timeouts
159            .validate()
160            .context("Invalid timeouts configuration")?;
161
162        self.prompt_cache
163            .validate()
164            .context("Invalid prompt_cache configuration")?;
165
166        self.ui
167            .keyboard_protocol
168            .validate()
169            .context("Invalid keyboard_protocol configuration")?;
170
171        Ok(())
172    }
173
174    #[cfg(feature = "bootstrap")]
175    /// Bootstrap project with config + gitignore
176    pub fn bootstrap_project<P: AsRef<Path>>(workspace: P, force: bool) -> Result<Vec<String>> {
177        Self::bootstrap_project_with_options(workspace, force, false)
178    }
179
180    #[cfg(feature = "bootstrap")]
181    /// Bootstrap project with config + gitignore, with option to create in home directory
182    pub fn bootstrap_project_with_options<P: AsRef<Path>>(
183        workspace: P,
184        force: bool,
185        use_home_dir: bool,
186    ) -> Result<Vec<String>> {
187        let workspace = workspace.as_ref().to_path_buf();
188        defaults::with_config_defaults(|provider| {
189            Self::bootstrap_project_with_provider(&workspace, force, use_home_dir, provider)
190        })
191    }
192
193    #[cfg(feature = "bootstrap")]
194    /// Bootstrap project files using the supplied [`ConfigDefaultsProvider`].
195    pub fn bootstrap_project_with_provider<P: AsRef<Path>>(
196        workspace: P,
197        force: bool,
198        use_home_dir: bool,
199        defaults_provider: &dyn ConfigDefaultsProvider,
200    ) -> Result<Vec<String>> {
201        let workspace = workspace.as_ref();
202        let config_file_name = defaults_provider.config_file_name().to_string();
203        let (config_path, gitignore_path) = crate::loader::bootstrap::determine_bootstrap_targets(
204            workspace,
205            use_home_dir,
206            &config_file_name,
207            defaults_provider,
208        )?;
209
210        crate::loader::bootstrap::ensure_parent_dir(&config_path)?;
211        crate::loader::bootstrap::ensure_parent_dir(&gitignore_path)?;
212
213        let mut created_files = Vec::new();
214
215        if !config_path.exists() || force {
216            let config_content = Self::default_vtcode_toml_template();
217
218            fs::write(&config_path, config_content).with_context(|| {
219                format!("Failed to write config file: {}", config_path.display())
220            })?;
221
222            if let Some(file_name) = config_path.file_name().and_then(|name| name.to_str()) {
223                created_files.push(file_name.to_string());
224            }
225        }
226
227        if !gitignore_path.exists() || force {
228            let gitignore_content = Self::default_vtcode_gitignore();
229            fs::write(&gitignore_path, gitignore_content).with_context(|| {
230                format!(
231                    "Failed to write gitignore file: {}",
232                    gitignore_path.display()
233                )
234            })?;
235
236            if let Some(file_name) = gitignore_path.file_name().and_then(|name| name.to_str()) {
237                created_files.push(file_name.to_string());
238            }
239        }
240
241        Ok(created_files)
242    }
243
244    #[cfg(feature = "bootstrap")]
245    /// Generate the default `vtcode.toml` template used by bootstrap helpers.
246    fn default_vtcode_toml_template() -> String {
247        r#"# VT Code Configuration File (Example)
248# Getting-started reference; see docs/config/CONFIGURATION_PRECEDENCE.md for override order.
249# Copy this file to vtcode.toml and customize as needed.
250
251# Core agent behavior; see docs/config/CONFIGURATION_PRECEDENCE.md.
252[agent]
253# Primary LLM provider to use (e.g., "openai", "gemini", "anthropic", "openrouter")
254provider = "openai"
255
256# Environment variable containing the API key for the provider
257api_key_env = "OPENAI_API_KEY"
258
259# Default model to use when no specific model is specified
260default_model = "gpt-5-nano"
261
262# Visual theme for the terminal interface
263theme = "ciapre-dark"
264
265# Enable TODO planning helper mode for structured task management
266todo_planning_mode = true
267
268# UI surface to use ("auto", "alternate", "inline")
269ui_surface = "auto"
270
271# Maximum number of conversation turns before rotating context (affects memory usage)
272# Lower values reduce memory footprint but may lose context; higher values preserve context but use more memory
273max_conversation_turns = 50
274
275# Reasoning effort level ("low", "medium", "high") - affects model usage and response speed
276reasoning_effort = "low"
277
278# Enable self-review loop to check and improve responses (increases API calls)
279enable_self_review = false
280
281# Maximum number of review passes when self-review is enabled
282max_review_passes = 1
283
284# Enable prompt refinement loop for improved prompt quality (increases processing time)
285refine_prompts_enabled = false
286
287# Maximum passes for prompt refinement when enabled
288refine_prompts_max_passes = 1
289
290# Optional alternate model for refinement (leave empty to use default)
291refine_prompts_model = ""
292
293# Maximum size of project documentation to include in context (in bytes)
294project_doc_max_bytes = 16384
295
296# Maximum size of instruction files to process (in bytes)
297instruction_max_bytes = 16384
298
299# List of additional instruction files to include in context
300instruction_files = []
301
302# Default editing mode on startup: "edit" or "plan"
303# "edit" - Full tool access for file modifications and command execution (default)
304# "plan" - Read-only mode that produces implementation plans without making changes
305# Toggle during session with Shift+Tab or /plan command
306default_editing_mode = "edit"
307
308# Onboarding configuration - Customize the startup experience
309[agent.onboarding]
310# Enable the onboarding welcome message on startup
311enabled = true
312
313# Custom introduction text shown on startup
314intro_text = "Let's get oriented. I preloaded workspace context so we can move fast."
315
316# Include project overview information in welcome
317include_project_overview = true
318
319# Include language summary information in welcome
320include_language_summary = false
321
322# Include key guideline highlights from AGENTS.md
323include_guideline_highlights = true
324
325# Include usage tips in the welcome message
326include_usage_tips_in_welcome = false
327
328# Include recommended actions in the welcome message
329include_recommended_actions_in_welcome = false
330
331# Maximum number of guideline highlights to show
332guideline_highlight_limit = 3
333
334# List of usage tips shown during onboarding
335usage_tips = [
336    "Describe your current coding goal or ask for a quick status overview.",
337    "Reference AGENTS.md guidelines when proposing changes.",
338    "Prefer asking for targeted file reads or diffs before editing.",
339]
340
341# List of recommended actions shown during onboarding
342recommended_actions = [
343    "Review the highlighted guidelines and share the task you want to tackle.",
344    "Ask for a workspace tour if you need more context.",
345]
346
347# Custom prompts configuration - Define personal assistant commands
348[agent.custom_prompts]
349# Enable the custom prompts feature with /prompt:<name> syntax
350enabled = true
351
352# Directory where custom prompt files are stored
353directory = "~/.vtcode/prompts"
354
355# Additional directories to search for custom prompts
356extra_directories = []
357
358# Maximum file size for custom prompts (in kilobytes)
359max_file_size_kb = 64
360
361# Custom API keys for specific providers
362[agent.custom_api_keys]
363# Moonshot AI API key (for specific provider access)
364moonshot = "sk-sDj3JUXDbfARCYKNL4q7iGWRtWuhL1M4O6zzgtDpN3Yxt9EA"
365
366# Checkpointing configuration for session persistence
367[agent.checkpointing]
368# Enable automatic session checkpointing
369enabled = false
370
371# Maximum number of checkpoints to keep on disk
372max_snapshots = 50
373
374# Maximum age of checkpoints to keep (in days)
375max_age_days = 30
376
377# Subagent system (opt-in)
378[subagents]
379# Enable subagents (default: false)
380enabled = false
381
382# Maximum concurrent subagents
383# max_concurrent = 3
384
385# Default timeout for subagent execution (seconds)
386# default_timeout_seconds = 300
387
388# Default model for subagents (override per-agent model if set)
389# default_model = ""
390
391# Additional directories to search for subagent definitions
392# additional_agent_dirs = []
393
394# Tool security configuration
395[tools]
396# Default policy when no specific policy is defined ("allow", "prompt", "deny")
397# "allow" - Execute without confirmation
398# "prompt" - Ask for confirmation
399# "deny" - Block the tool
400default_policy = "prompt"
401
402# Maximum number of tool loops allowed per turn (prevents infinite loops)
403# Higher values allow more complex operations but risk performance issues
404# Recommended: 20 for most tasks, 50 for complex multi-step workflows
405max_tool_loops = 20
406
407# Maximum number of repeated identical tool calls (prevents stuck loops)
408max_repeated_tool_calls = 2
409
410# Specific tool policies - Override default policy for individual tools
411[tools.policies]
412apply_patch = "prompt"            # Apply code patches (requires confirmation)
413close_pty_session = "allow"        # Close PTY sessions (no confirmation needed)
414create_pty_session = "allow"       # Create PTY sessions (no confirmation needed)
415edit_file = "allow"               # Edit files directly (no confirmation needed)
416grep_file = "allow"               # Sole content-search tool (ripgrep-backed)
417list_files = "allow"              # List directory contents (no confirmation needed)
418list_pty_sessions = "allow"       # List PTY sessions (no confirmation needed)
419read_file = "allow"               # Read files (no confirmation needed)
420read_pty_session = "allow"        # Read PTY session output (no no confirmation needed)
421resize_pty_session = "allow"      # Resize PTY sessions (no confirmation needed)
422run_pty_cmd = "prompt"            # Run commands in PTY (requires confirmation)
423exec_command = "prompt"           # Execute command in unified session (requires confirmation)
424write_stdin = "prompt"            # Write to stdin in unified session (requires confirmation)
425
426send_pty_input = "prompt"         # Send input to PTY (requires confirmation)
427write_file = "allow"              # Write files (no confirmation needed)
428
429# Command security - Define safe and dangerous command patterns
430[commands]
431# Commands that are always allowed without confirmation
432allow_list = [
433    "ls",           # List directory contents
434    "pwd",          # Print working directory
435    "git status",   # Show git status
436    "git diff",     # Show git differences
437    "cargo check",  # Check Rust code
438    "echo",         # Print text
439]
440
441# Commands that are never allowed
442deny_list = [
443    "rm -rf /",        # Delete root directory (dangerous)
444    "rm -rf ~",        # Delete home directory (dangerous)
445    "shutdown",        # Shut down system (dangerous)
446    "reboot",          # Reboot system (dangerous)
447    "sudo *",          # Any sudo command (dangerous)
448    ":(){ :|:& };:",   # Fork bomb (dangerous)
449]
450
451# Command patterns that are allowed (supports glob patterns)
452allow_glob = [
453    "git *",        # All git commands
454    "cargo *",      # All cargo commands
455    "python -m *",  # Python module commands
456]
457
458# Command patterns that are denied (supports glob patterns)
459deny_glob = [
460    "rm *",         # All rm commands
461    "sudo *",       # All sudo commands
462    "chmod *",      # All chmod commands
463    "chown *",      # All chown commands
464    "kubectl *",    # All kubectl commands (admin access)
465]
466
467# Regular expression patterns for allowed commands (if needed)
468allow_regex = []
469
470# Regular expression patterns for denied commands (if needed)
471deny_regex = []
472
473# Security configuration - Safety settings for automated operations
474[security]
475# Require human confirmation for potentially dangerous actions
476human_in_the_loop = true
477
478# Require explicit write tool usage for claims about file modifications
479require_write_tool_for_claims = true
480
481# Auto-apply patches without prompting (DANGEROUS - disable for safety)
482auto_apply_detected_patches = false
483
484# UI configuration - Terminal and display settings
485[ui]
486# Tool output display mode
487# "compact" - Concise tool output
488# "full" - Detailed tool output
489tool_output_mode = "compact"
490
491# Maximum number of lines to display in tool output (prevents transcript flooding)
492# Lines beyond this limit are truncated to a tail preview
493tool_output_max_lines = 600
494
495# Maximum bytes threshold for spooling tool output to disk
496# Output exceeding this size is written to .vtcode/tool-output/*.log
497tool_output_spool_bytes = 200000
498
499# Optional custom directory for spooled tool output logs
500# If not set, defaults to .vtcode/tool-output/
501# tool_output_spool_dir = "/path/to/custom/spool/dir"
502
503# Allow ANSI escape sequences in tool output (enables colors but may cause layout issues)
504allow_tool_ansi = false
505
506# Number of rows to allocate for inline UI viewport
507inline_viewport_rows = 16
508
509# Show timeline navigation panel
510show_timeline_pane = false
511
512# Status line configuration
513[ui.status_line]
514# Status line mode ("auto", "command", "hidden")
515mode = "auto"
516
517# How often to refresh status line (milliseconds)
518refresh_interval_ms = 2000
519
520# Timeout for command execution in status line (milliseconds)
521command_timeout_ms = 200
522
523# PTY (Pseudo Terminal) configuration - For interactive command execution
524[pty]
525# Enable PTY support for interactive commands
526enabled = true
527
528# Default number of terminal rows for PTY sessions
529default_rows = 24
530
531# Default number of terminal columns for PTY sessions
532default_cols = 80
533
534# Maximum number of concurrent PTY sessions
535max_sessions = 10
536
537# Command timeout in seconds (prevents hanging commands)
538command_timeout_seconds = 300
539
540# Number of recent lines to show in PTY output
541stdout_tail_lines = 20
542
543# Total lines to keep in PTY scrollback buffer
544scrollback_lines = 400
545
546# Context management configuration - Controls conversation memory
547[context]
548# Maximum number of tokens to keep in context (affects model cost and performance)
549# Higher values preserve more context but cost more and may hit token limits
550max_context_tokens = 90000
551
552# Percentage to trim context to when it gets too large
553trim_to_percent = 60
554
555# Number of recent conversation turns to always preserve
556preserve_recent_turns = 6
557
558# Decision ledger configuration - Track important decisions
559[context.ledger]
560# Enable decision tracking and persistence
561enabled = true
562
563# Maximum number of decisions to keep in ledger
564max_entries = 12
565
566# Include ledger summary in model prompts
567include_in_prompt = true
568
569# Preserve ledger during context compression
570preserve_in_compression = true
571
572# AI model routing - Intelligent model selection
573# Telemetry and analytics
574[telemetry]
575# Enable trajectory logging for usage analysis
576trajectory_enabled = true
577
578# Syntax highlighting configuration
579[syntax_highlighting]
580# Enable syntax highlighting for code in tool output
581enabled = true
582
583# Theme for syntax highlighting
584theme = "base16-ocean.dark"
585
586# Cache syntax highlighting themes for performance
587cache_themes = true
588
589# Maximum file size for syntax highlighting (in MB)
590max_file_size_mb = 10
591
592# Programming languages to enable syntax highlighting for
593enabled_languages = [
594    "rust",
595    "python",
596    "javascript",
597    "typescript",
598    "go",
599    "java",
600]
601
602# Timeout for syntax highlighting operations (milliseconds)
603highlight_timeout_ms = 1000
604
605# Automation features - Full-auto mode settings
606[automation.full_auto]
607# Enable full automation mode (DANGEROUS - requires careful oversight)
608enabled = false
609
610# Maximum number of turns before asking for human input
611max_turns = 30
612
613# Tools allowed in full automation mode
614allowed_tools = [
615    "write_file",
616    "read_file",
617    "list_files",
618    "grep_file",
619]
620
621# Require profile acknowledgment before using full auto
622require_profile_ack = true
623
624# Path to full auto profile configuration
625profile_path = "automation/full_auto_profile.toml"
626
627# Prompt caching - Cache model responses for efficiency
628[prompt_cache]
629# Enable prompt caching (reduces API calls for repeated prompts)
630enabled = false
631
632# Directory for cache storage
633cache_dir = "~/.vtcode/cache/prompts"
634
635# Maximum number of cache entries to keep
636max_entries = 1000
637
638# Maximum age of cache entries (in days)
639max_age_days = 30
640
641# Enable automatic cache cleanup
642enable_auto_cleanup = true
643
644# Minimum quality threshold to keep cache entries
645min_quality_threshold = 0.7
646
647# Prompt cache configuration for OpenAI
648    [prompt_cache.providers.openai]
649    enabled = true
650    min_prefix_tokens = 1024
651    idle_expiration_seconds = 3600
652    surface_metrics = true
653    # Optional: server-side prompt cache retention for OpenAI Responses API
654    # Example: "24h" (leave commented out for default behavior)
655    # prompt_cache_retention = "24h"
656
657# Prompt cache configuration for Anthropic
658[prompt_cache.providers.anthropic]
659enabled = true
660default_ttl_seconds = 300
661extended_ttl_seconds = 3600
662max_breakpoints = 4
663cache_system_messages = true
664cache_user_messages = true
665
666# Prompt cache configuration for Gemini
667[prompt_cache.providers.gemini]
668enabled = true
669mode = "implicit"
670min_prefix_tokens = 1024
671explicit_ttl_seconds = 3600
672
673# Prompt cache configuration for OpenRouter
674[prompt_cache.providers.openrouter]
675enabled = true
676propagate_provider_capabilities = true
677report_savings = true
678
679# Prompt cache configuration for Moonshot
680[prompt_cache.providers.moonshot]
681enabled = true
682
683# Prompt cache configuration for xAI
684[prompt_cache.providers.xai]
685enabled = true
686
687# Prompt cache configuration for DeepSeek
688[prompt_cache.providers.deepseek]
689enabled = true
690surface_metrics = true
691
692# Prompt cache configuration for Z.AI
693[prompt_cache.providers.zai]
694enabled = false
695
696# Model Context Protocol (MCP) - Connect external tools and services
697[mcp]
698# Enable Model Context Protocol (may impact startup time if services unavailable)
699enabled = true
700max_concurrent_connections = 5
701request_timeout_seconds = 30
702retry_attempts = 3
703
704# MCP UI configuration
705[mcp.ui]
706mode = "compact"
707max_events = 50
708show_provider_names = true
709
710# MCP renderer profiles for different services
711[mcp.ui.renderers]
712sequential-thinking = "sequential-thinking"
713context7 = "context7"
714
715# MCP provider configuration - External services that connect via MCP
716[[mcp.providers]]
717name = "time"
718command = "uvx"
719args = ["mcp-server-time"]
720enabled = true
721max_concurrent_requests = 3
722[mcp.providers.env]
723
724# Agent Client Protocol (ACP) - IDE integration
725[acp]
726enabled = true
727
728[acp.zed]
729enabled = true
730transport = "stdio"
731# workspace_trust controls ACP trust mode: "tools_policy" (prompts) or "full_auto" (no prompts)
732workspace_trust = "full_auto"
733
734[acp.zed.tools]
735read_file = true
736list_files = true"#.to_string()
737    }
738
739    #[cfg(feature = "bootstrap")]
740    fn default_vtcode_gitignore() -> String {
741        r#"# Security-focused exclusions
742.env, .env.local, secrets/, .aws/, .ssh/
743
744# Development artifacts
745target/, build/, dist/, node_modules/, vendor/
746
747# Database files
748*.db, *.sqlite, *.sqlite3
749
750# Binary files
751*.exe, *.dll, *.so, *.dylib, *.bin
752
753# IDE files (comprehensive)
754.vscode/, .idea/, *.swp, *.swo
755"#
756        .to_string()
757    }
758
759    #[cfg(feature = "bootstrap")]
760    /// Create sample configuration file
761    pub fn create_sample_config<P: AsRef<Path>>(output: P) -> Result<()> {
762        let output = output.as_ref();
763        let config_content = Self::default_vtcode_toml_template();
764
765        fs::write(output, config_content)
766            .with_context(|| format!("Failed to write config file: {}", output.display()))?;
767
768        Ok(())
769    }
770}