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