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