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