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