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#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
29#[derive(Debug, Clone, Deserialize, Serialize, Default)]
30pub struct ProviderConfig {
31 #[serde(default)]
33 pub anthropic: AnthropicConfig,
34}
35
36#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
38#[derive(Debug, Clone, Deserialize, Serialize, Default)]
39pub struct VTCodeConfig {
40 #[serde(default)]
42 pub agent: AgentConfig,
43
44 #[serde(default)]
46 pub auth: AuthConfig,
47
48 #[serde(default)]
50 pub tools: ToolsConfig,
51
52 #[serde(default)]
54 pub commands: CommandsConfig,
55
56 #[serde(default)]
58 pub permissions: PermissionsConfig,
59
60 #[serde(default)]
62 pub security: SecurityConfig,
63
64 #[serde(default)]
66 pub sandbox: SandboxConfig,
67
68 #[serde(default)]
70 pub ui: UiConfig,
71
72 #[serde(default)]
74 pub chat: ChatConfig,
75
76 #[serde(default)]
78 pub pty: PtyConfig,
79
80 #[serde(default)]
82 pub debug: DebugConfig,
83
84 #[serde(default)]
86 pub context: ContextFeaturesConfig,
87
88 #[serde(default)]
90 pub telemetry: TelemetryConfig,
91
92 #[serde(default)]
94 pub optimization: OptimizationConfig,
95
96 #[serde(default)]
98 pub syntax_highlighting: SyntaxHighlightingConfig,
99
100 #[serde(default)]
102 pub timeouts: TimeoutsConfig,
103
104 #[serde(default)]
106 pub automation: AutomationConfig,
107
108 #[serde(default)]
110 pub prompt_cache: PromptCachingConfig,
111
112 #[serde(default)]
114 pub mcp: McpClientConfig,
115
116 #[serde(default)]
118 pub acp: AgentClientProtocolConfig,
119
120 #[serde(default)]
122 pub hooks: HooksConfig,
123
124 #[serde(default)]
126 pub model: ModelConfig,
127
128 #[serde(default)]
130 pub provider: ProviderConfig,
131
132 #[serde(default)]
134 pub skills: SkillsConfig,
135
136 #[serde(default)]
138 pub subagents: SubagentsConfig,
139
140 #[serde(default)]
142 pub agent_teams: AgentTeamsConfig,
143
144 #[serde(default)]
146 pub output_style: OutputStyleConfig,
147
148 #[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 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 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 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 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 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}