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