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