1use anyhow::{Context, Result};
2use serde::{Deserialize, Serialize};
3use std::fs;
4use std::path::Path;
5
6use crate::acp::AgentClientProtocolConfig;
7use crate::codex::{FileOpener, HistoryConfig, TuiConfig};
8use crate::context::ContextFeaturesConfig;
9use crate::core::{
10 AgentConfig, AnthropicConfig, AuthConfig, AutomationConfig, CommandsConfig,
11 CustomProviderConfig, DotfileProtectionConfig, ModelConfig, OpenAIConfig, PermissionsConfig,
12 PromptCachingConfig, SandboxConfig, SecurityConfig, SkillsConfig, ToolsConfig,
13};
14use crate::debug::DebugConfig;
15use crate::defaults::{self, ConfigDefaultsProvider};
16use crate::hooks::HooksConfig;
17use crate::ide_context::IdeContextConfig;
18use crate::mcp::McpClientConfig;
19use crate::optimization::OptimizationConfig;
20use crate::output_styles::OutputStyleConfig;
21use crate::root::{ChatConfig, PtyConfig, UiConfig};
22use crate::subagents::SubagentRuntimeLimits;
23use crate::telemetry::TelemetryConfig;
24use crate::timeouts::TimeoutsConfig;
25
26use crate::loader::syntax_highlighting::SyntaxHighlightingConfig;
27
28#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
30#[derive(Debug, Clone, Deserialize, Serialize, Default)]
31pub struct ProviderConfig {
32 #[serde(default)]
34 pub openai: OpenAIConfig,
35
36 #[serde(default)]
38 pub anthropic: AnthropicConfig,
39}
40
41#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
43#[derive(Debug, Clone, Deserialize, Serialize, Default)]
44pub struct VTCodeConfig {
45 #[serde(default)]
47 pub file_opener: FileOpener,
48
49 #[serde(default)]
51 pub project_doc_max_bytes: Option<usize>,
52
53 #[serde(default)]
55 pub project_doc_fallback_filenames: Vec<String>,
56
57 #[serde(default)]
59 pub notify: Vec<String>,
60
61 #[serde(default)]
63 pub history: HistoryConfig,
64
65 #[serde(default)]
67 pub tui: TuiConfig,
68
69 #[serde(default)]
71 pub agent: AgentConfig,
72
73 #[serde(default)]
75 pub auth: AuthConfig,
76
77 #[serde(default)]
79 pub tools: ToolsConfig,
80
81 #[serde(default)]
83 pub commands: CommandsConfig,
84
85 #[serde(default)]
87 pub permissions: PermissionsConfig,
88
89 #[serde(default)]
91 pub security: SecurityConfig,
92
93 #[serde(default)]
95 pub sandbox: SandboxConfig,
96
97 #[serde(default)]
99 pub ui: UiConfig,
100
101 #[serde(default)]
103 pub chat: ChatConfig,
104
105 #[serde(default)]
107 pub pty: PtyConfig,
108
109 #[serde(default)]
111 pub debug: DebugConfig,
112
113 #[serde(default)]
115 pub context: ContextFeaturesConfig,
116
117 #[serde(default)]
119 pub telemetry: TelemetryConfig,
120
121 #[serde(default)]
123 pub optimization: OptimizationConfig,
124
125 #[serde(default)]
127 pub syntax_highlighting: SyntaxHighlightingConfig,
128
129 #[serde(default)]
131 pub timeouts: TimeoutsConfig,
132
133 #[serde(default)]
135 pub automation: AutomationConfig,
136
137 #[serde(default)]
139 pub subagents: SubagentRuntimeLimits,
140
141 #[serde(default)]
143 pub prompt_cache: PromptCachingConfig,
144
145 #[serde(default)]
147 pub mcp: McpClientConfig,
148
149 #[serde(default)]
151 pub acp: AgentClientProtocolConfig,
152
153 #[serde(default)]
155 pub ide_context: IdeContextConfig,
156
157 #[serde(default)]
159 pub hooks: HooksConfig,
160
161 #[serde(default)]
163 pub model: ModelConfig,
164
165 #[serde(default)]
167 pub provider: ProviderConfig,
168
169 #[serde(default)]
171 pub skills: SkillsConfig,
172
173 #[serde(default)]
177 pub custom_providers: Vec<CustomProviderConfig>,
178
179 #[serde(default)]
181 pub output_style: OutputStyleConfig,
182
183 #[serde(default)]
185 pub dotfile_protection: DotfileProtectionConfig,
186}
187
188impl VTCodeConfig {
189 pub fn apply_compat_defaults(&mut self) {
190 if let Some(max_bytes) = self.project_doc_max_bytes {
191 self.agent.project_doc_max_bytes = max_bytes;
192 }
193
194 if !self.project_doc_fallback_filenames.is_empty() {
195 self.agent.project_doc_fallback_filenames = self.project_doc_fallback_filenames.clone();
196 }
197 }
198
199 pub fn validate(&self) -> Result<()> {
200 self.syntax_highlighting
201 .validate()
202 .context("Invalid syntax_highlighting configuration")?;
203
204 self.context
205 .validate()
206 .context("Invalid context configuration")?;
207
208 self.hooks
209 .validate()
210 .context("Invalid hooks configuration")?;
211
212 self.timeouts
213 .validate()
214 .context("Invalid timeouts configuration")?;
215
216 self.prompt_cache
217 .validate()
218 .context("Invalid prompt_cache configuration")?;
219
220 self.agent
221 .validate_llm_params()
222 .map_err(anyhow::Error::msg)
223 .context("Invalid agent configuration")?;
224
225 self.ui
226 .keyboard_protocol
227 .validate()
228 .context("Invalid keyboard_protocol configuration")?;
229
230 self.pty.validate().context("Invalid pty configuration")?;
231
232 let mut seen_names = std::collections::HashSet::new();
234 for cp in &self.custom_providers {
235 cp.validate()
236 .map_err(|msg| anyhow::anyhow!(msg))
237 .context("Invalid custom_providers configuration")?;
238 if !seen_names.insert(cp.name.to_lowercase()) {
239 anyhow::bail!("custom_providers: duplicate name `{}`", cp.name);
240 }
241 }
242
243 Ok(())
244 }
245
246 pub fn custom_provider(&self, name: &str) -> Option<&CustomProviderConfig> {
248 let lower = name.to_lowercase();
249 self.custom_providers
250 .iter()
251 .find(|cp| cp.name.to_lowercase() == lower)
252 }
253
254 pub fn provider_display_name(&self, provider_key: &str) -> String {
257 if let Some(cp) = self.custom_provider(provider_key) {
258 cp.display_name.clone()
259 } else if let Ok(p) = std::str::FromStr::from_str(provider_key) {
260 let p: crate::models::Provider = p;
261 p.label().to_string()
262 } else {
263 provider_key.to_string()
264 }
265 }
266
267 #[cfg(feature = "bootstrap")]
268 pub fn bootstrap_project<P: AsRef<Path>>(workspace: P, force: bool) -> Result<Vec<String>> {
270 Self::bootstrap_project_with_options(workspace, force, false)
271 }
272
273 #[cfg(feature = "bootstrap")]
274 pub fn bootstrap_project_with_options<P: AsRef<Path>>(
276 workspace: P,
277 force: bool,
278 use_home_dir: bool,
279 ) -> Result<Vec<String>> {
280 let workspace = workspace.as_ref().to_path_buf();
281 defaults::with_config_defaults(|provider| {
282 Self::bootstrap_project_with_provider(&workspace, force, use_home_dir, provider)
283 })
284 }
285
286 #[cfg(feature = "bootstrap")]
287 pub fn bootstrap_project_with_provider<P: AsRef<Path>>(
289 workspace: P,
290 force: bool,
291 use_home_dir: bool,
292 defaults_provider: &dyn ConfigDefaultsProvider,
293 ) -> Result<Vec<String>> {
294 let workspace = workspace.as_ref();
295 let config_file_name = defaults_provider.config_file_name().to_string();
296 let (config_path, gitignore_path) = crate::loader::bootstrap::determine_bootstrap_targets(
297 workspace,
298 use_home_dir,
299 &config_file_name,
300 defaults_provider,
301 )?;
302 let rules_readme_path = workspace.join(".vtcode").join("rules").join("README.md");
303
304 crate::loader::bootstrap::ensure_parent_dir(&config_path)?;
305 crate::loader::bootstrap::ensure_parent_dir(&gitignore_path)?;
306 crate::loader::bootstrap::ensure_parent_dir(&rules_readme_path)?;
307
308 let mut created_files = Vec::new();
309
310 if !config_path.exists() || force {
311 let config_content = Self::default_vtcode_toml_template();
312
313 fs::write(&config_path, config_content).with_context(|| {
314 format!("Failed to write config file: {}", config_path.display())
315 })?;
316
317 if let Some(file_name) = config_path.file_name().and_then(|name| name.to_str()) {
318 created_files.push(file_name.to_string());
319 }
320 }
321
322 if !gitignore_path.exists() || force {
323 let gitignore_content = Self::default_vtcode_gitignore();
324 fs::write(&gitignore_path, gitignore_content).with_context(|| {
325 format!(
326 "Failed to write gitignore file: {}",
327 gitignore_path.display()
328 )
329 })?;
330
331 if let Some(file_name) = gitignore_path.file_name().and_then(|name| name.to_str()) {
332 created_files.push(file_name.to_string());
333 }
334 }
335
336 if !rules_readme_path.exists() || force {
337 let rules_readme = Self::default_rules_readme_template();
338 fs::write(&rules_readme_path, rules_readme).with_context(|| {
339 format!(
340 "Failed to write rules README: {}",
341 rules_readme_path.display()
342 )
343 })?;
344 created_files.push(".vtcode/rules/README.md".to_string());
345 }
346
347 Ok(created_files)
348 }
349
350 #[cfg(feature = "bootstrap")]
351 fn default_vtcode_toml_template() -> String {
353 r#"# VT Code Configuration File (Example)
354# Getting-started reference; see docs/config/CONFIGURATION_PRECEDENCE.md for override order.
355# Copy this file to vtcode.toml and customize as needed.
356
357# Clickable file citation URI scheme ("vscode", "cursor", "windsurf", "vscode-insiders", "none")
358file_opener = "none"
359
360# Additional fallback filenames to use when AGENTS.md is absent
361project_doc_fallback_filenames = []
362
363# Optional external command invoked after each completed agent turn
364notify = []
365
366# User-defined OpenAI-compatible providers
367custom_providers = []
368
369# [[custom_providers]]
370# name = "mycorp"
371# display_name = "MyCorporateName"
372# base_url = "https://llm.corp.example/v1"
373# api_key_env = "MYCORP_API_KEY"
374# model = "gpt-5-mini"
375
376[history]
377# Persist local session transcripts to disk
378persistence = "file"
379
380# Optional max size budget for each persisted session snapshot
381# max_bytes = 104857600
382
383[tui]
384# Enable all built-in TUI notifications, disable them, or restrict to specific event types.
385# notifications = true
386# notifications = ["agent-turn-complete", "approval-requested"]
387
388# Notification transport: "auto", "osc9", or "bel"
389# notification_method = "auto"
390
391# Set to false to reduce shimmer/animation effects
392# animations = true
393
394# Alternate-screen override: "always" or "never"
395# alternate_screen = "never"
396
397# Show onboarding hints on the welcome screen
398# show_tooltips = true
399
400# Core agent behavior; see docs/config/CONFIGURATION_PRECEDENCE.md.
401[agent]
402# Primary LLM provider to use (e.g., "openai", "gemini", "anthropic", "openrouter")
403provider = "openai"
404
405# Environment variable containing the API key for the provider
406api_key_env = "OPENAI_API_KEY"
407
408# Default model to use when no specific model is specified
409default_model = "gpt-5.4"
410
411# Visual theme for the terminal interface
412theme = "ciapre-dark"
413
414# Enable TODO planning helper mode for structured task management
415todo_planning_mode = true
416
417# UI surface to use ("auto", "alternate", "inline")
418ui_surface = "auto"
419
420# Maximum number of conversation turns before rotating context (affects memory usage)
421# Lower values reduce memory footprint but may lose context; higher values preserve context but use more memory
422max_conversation_turns = 150
423
424# Reasoning effort level ("none", "minimal", "low", "medium", "high", "xhigh") - affects model usage and response speed
425reasoning_effort = "none"
426
427# Temperature for main model responses (0.0-1.0)
428temperature = 0.7
429
430# Enable self-review loop to check and improve responses (increases API calls)
431enable_self_review = false
432
433# Maximum number of review passes when self-review is enabled
434max_review_passes = 1
435
436# Enable prompt refinement loop for improved prompt quality (increases processing time)
437refine_prompts_enabled = false
438
439# Maximum passes for prompt refinement when enabled
440refine_prompts_max_passes = 1
441
442# Optional alternate model for refinement (leave empty to use default)
443refine_prompts_model = ""
444
445# Maximum size of project documentation to include in context (in bytes)
446project_doc_max_bytes = 16384
447
448# Maximum size of instruction files to process (in bytes)
449instruction_max_bytes = 16384
450
451# List of additional instruction files to include in context
452instruction_files = []
453
454# Instruction files or globs to exclude from AGENTS/rules discovery
455instruction_excludes = []
456
457# Maximum recursive @import depth for instruction and rule files
458instruction_import_max_depth = 5
459
460# Durable per-repository memory for main sessions
461[agent.persistent_memory]
462enabled = false
463auto_write = true
464# directory_override = "/absolute/user-local/path"
465startup_line_limit = 200
466startup_byte_limit = 25600
467
468# Lightweight model helpers for lower-cost side tasks
469[agent.small_model]
470enabled = true
471model = ""
472temperature = 0.3
473use_for_large_reads = true
474use_for_web_summary = true
475use_for_git_history = true
476use_for_memory = true
477
478# Default editing mode on startup: "edit" or "plan"
479# "edit" - Full tool access for file modifications and command execution (default)
480# "plan" - Read-only mode that produces implementation plans without making changes
481# Toggle during session with Shift+Tab or /plan command
482default_editing_mode = "edit"
483
484# Inline prompt suggestions for the chat composer
485[agent.prompt_suggestions]
486# Enable Alt+P ghost-text suggestions in the composer
487enabled = true
488
489# Lightweight model for prompt suggestions (leave empty to auto-pick)
490model = ""
491
492# Lower values keep suggestions stable and completion-like
493temperature = 0.3
494
495# Show a one-time note that LLM-backed suggestions can consume tokens
496show_cost_notice = true
497
498# Onboarding configuration - Customize the startup experience
499[agent.onboarding]
500# Enable the onboarding welcome message on startup
501enabled = true
502
503# Custom introduction text shown on startup
504intro_text = "Let's get oriented. I preloaded workspace context so we can move fast."
505
506# Include project overview information in welcome
507include_project_overview = true
508
509# Include language summary information in welcome
510include_language_summary = false
511
512# Include key guideline highlights from AGENTS.md
513include_guideline_highlights = true
514
515# Include usage tips in the welcome message
516include_usage_tips_in_welcome = false
517
518# Include recommended actions in the welcome message
519include_recommended_actions_in_welcome = false
520
521# Maximum number of guideline highlights to show
522guideline_highlight_limit = 3
523
524# List of usage tips shown during onboarding
525usage_tips = [
526 "Describe your current coding goal or ask for a quick status overview.",
527 "Reference AGENTS.md guidelines when proposing changes.",
528 "Prefer asking for targeted file reads or diffs before editing.",
529]
530
531# List of recommended actions shown during onboarding
532recommended_actions = [
533 "Review the highlighted guidelines and share the task you want to tackle.",
534 "Ask for a workspace tour if you need more context.",
535]
536
537# Checkpointing configuration for session persistence
538[agent.checkpointing]
539# Enable automatic session checkpointing
540enabled = true
541
542# Maximum number of checkpoints to keep on disk
543max_snapshots = 50
544
545# Maximum age of checkpoints to keep (in days)
546max_age_days = 30
547
548# Tool security configuration
549[tools]
550# Default policy when no specific policy is defined ("allow", "prompt", "deny")
551# "allow" - Execute without confirmation
552# "prompt" - Ask for confirmation
553# "deny" - Block the tool
554default_policy = "prompt"
555
556# Maximum number of tool loops allowed per turn
557# Set to 0 to disable the limit and let other turn safeguards govern termination.
558max_tool_loops = 0
559
560# Maximum number of repeated identical tool calls (prevents stuck loops)
561max_repeated_tool_calls = 2
562
563# Maximum consecutive blocked tool calls before force-breaking the turn
564# Helps prevent high-CPU churn when calls are repeatedly denied/blocked
565max_consecutive_blocked_tool_calls_per_turn = 8
566
567# Maximum sequential spool-chunk reads per turn before nudging targeted extraction/summarization
568max_sequential_spool_chunk_reads = 6
569
570# Specific tool policies - Override default policy for individual tools
571[tools.policies]
572apply_patch = "prompt" # Apply code patches (requires confirmation)
573request_user_input = "allow" # Ask focused user questions when the task requires it
574task_tracker = "prompt" # Create or update explicit task plans
575unified_exec = "prompt" # Run commands; pipe-first by default, set tty=true for PTY/interactive sessions
576unified_file = "allow" # Canonical file read/write/edit/move/copy/delete surface
577unified_search = "allow" # Canonical search/list/intelligence/error surface
578
579# Command security - Define safe and dangerous command patterns
580[commands]
581# Commands that are always allowed without confirmation
582allow_list = [
583 "ls", # List directory contents
584 "pwd", # Print working directory
585 "git status", # Show git status
586 "git diff", # Show git differences
587 "cargo check", # Check Rust code
588 "echo", # Print text
589]
590
591# Commands that are never allowed
592deny_list = [
593 "rm -rf /", # Delete root directory (dangerous)
594 "rm -rf ~", # Delete home directory (dangerous)
595 "shutdown", # Shut down system (dangerous)
596 "reboot", # Reboot system (dangerous)
597 "sudo *", # Any sudo command (dangerous)
598 ":(){ :|:& };:", # Fork bomb (dangerous)
599]
600
601# Command patterns that are allowed (supports glob patterns)
602allow_glob = [
603 "git *", # All git commands
604 "cargo *", # All cargo commands
605 "python -m *", # Python module commands
606]
607
608# Command patterns that are denied (supports glob patterns)
609deny_glob = [
610 "rm *", # All rm commands
611 "sudo *", # All sudo commands
612 "chmod *", # All chmod commands
613 "chown *", # All chown commands
614 "kubectl *", # All kubectl commands (admin access)
615]
616
617# Regular expression patterns for allowed commands (if needed)
618allow_regex = []
619
620# Regular expression patterns for denied commands (if needed)
621deny_regex = []
622
623# Security configuration - Safety settings for automated operations
624[security]
625# Require human confirmation for potentially dangerous actions
626human_in_the_loop = true
627
628# Require explicit write tool usage for claims about file modifications
629require_write_tool_for_claims = true
630
631# Auto-apply patches without prompting (DANGEROUS - disable for safety)
632auto_apply_detected_patches = false
633
634# UI configuration - Terminal and display settings
635[ui]
636# Tool output display mode
637# "compact" - Concise tool output
638# "full" - Detailed tool output
639tool_output_mode = "compact"
640
641# Maximum number of lines to display in tool output (prevents transcript flooding)
642# Lines beyond this limit are truncated to a tail preview
643tool_output_max_lines = 600
644
645# Maximum bytes threshold for spooling tool output to disk
646# Output exceeding this size is written to .vtcode/tool-output/*.log
647tool_output_spool_bytes = 200000
648
649# Optional custom directory for spooled tool output logs
650# If not set, defaults to .vtcode/tool-output/
651# tool_output_spool_dir = "/path/to/custom/spool/dir"
652
653# Allow ANSI escape sequences in tool output (enables colors but may cause layout issues)
654allow_tool_ansi = false
655
656# Number of rows to allocate for inline UI viewport
657inline_viewport_rows = 16
658
659# Show elapsed time divider after each completed turn
660show_turn_timer = false
661
662# Show warning/error/fatal diagnostic lines directly in transcript
663# Effective in debug/development builds only
664show_diagnostics_in_transcript = false
665
666# Show timeline navigation panel
667show_timeline_pane = false
668
669# Runtime notification preferences
670[ui.notifications]
671# Master toggle for terminal/desktop notifications
672enabled = true
673
674# Delivery mode: "terminal", "hybrid", or "desktop"
675delivery_mode = "hybrid"
676
677# Suppress notifications while terminal is focused
678suppress_when_focused = true
679
680# Failure/error notifications
681command_failure = false
682tool_failure = false
683error = true
684
685# Completion notifications
686# Legacy master toggle (fallback for split settings when unset)
687completion = true
688completion_success = false
689completion_failure = true
690
691# Human approval/interaction notifications
692hitl = true
693policy_approval = true
694request = false
695
696# Success notifications for tool call results
697tool_success = false
698
699# Repeated notification suppression
700repeat_window_seconds = 30
701max_identical_in_window = 1
702
703# Status line configuration
704[ui.status_line]
705# Status line mode ("auto", "command", "hidden")
706mode = "auto"
707
708# How often to refresh status line (milliseconds)
709refresh_interval_ms = 2000
710
711# Timeout for command execution in status line (milliseconds)
712command_timeout_ms = 200
713
714# Enable Vim-style prompt editing in interactive mode
715vim_mode = false
716
717# PTY (Pseudo Terminal) configuration - For interactive command execution
718[pty]
719# Enable PTY support for interactive commands
720enabled = true
721
722# Default number of terminal rows for PTY sessions
723default_rows = 24
724
725# Default number of terminal columns for PTY sessions
726default_cols = 80
727
728# Maximum number of concurrent PTY sessions
729max_sessions = 10
730
731# Command timeout in seconds (prevents hanging commands)
732command_timeout_seconds = 300
733
734# Number of recent lines to show in PTY output
735stdout_tail_lines = 20
736
737# Total lines to keep in PTY scrollback buffer
738scrollback_lines = 400
739
740# Terminal emulation backend for PTY snapshots
741emulation_backend = "legacy_vt100"
742
743# Optional preferred shell for PTY sessions (falls back to $SHELL when unset)
744# preferred_shell = "/bin/zsh"
745
746# Route shell execution through zsh EXEC_WRAPPER intercept hooks (feature-gated)
747shell_zsh_fork = false
748
749# Absolute path to patched zsh used when shell_zsh_fork is enabled
750# zsh_path = "/usr/local/bin/zsh"
751
752# Context management configuration - Controls conversation memory
753[context]
754# Maximum number of tokens to keep in context (affects model cost and performance)
755# Higher values preserve more context but cost more and may hit token limits
756max_context_tokens = 90000
757
758# Percentage to trim context to when it gets too large
759trim_to_percent = 60
760
761# Number of recent conversation turns to always preserve
762preserve_recent_turns = 6
763
764# Decision ledger configuration - Track important decisions
765[context.ledger]
766# Enable decision tracking and persistence
767enabled = true
768
769# Maximum number of decisions to keep in ledger
770max_entries = 12
771
772# Include ledger summary in model prompts
773include_in_prompt = true
774
775# Preserve ledger during context compression
776preserve_in_compression = true
777
778# AI model routing - Intelligent model selection
779# Telemetry and analytics
780[telemetry]
781# Enable trajectory logging for usage analysis
782trajectory_enabled = true
783
784# Syntax highlighting configuration
785[syntax_highlighting]
786# Enable syntax highlighting for code in tool output
787enabled = true
788
789# Theme for syntax highlighting
790theme = "base16-ocean.dark"
791
792# Cache syntax highlighting themes for performance
793cache_themes = true
794
795# Maximum file size for syntax highlighting (in MB)
796max_file_size_mb = 10
797
798# Programming languages to enable syntax highlighting for
799enabled_languages = [
800 "rust",
801 "python",
802 "javascript",
803 "typescript",
804 "go",
805 "java",
806 "bash",
807 "sh",
808 "shell",
809 "zsh",
810 "markdown",
811 "md",
812]
813
814# Timeout for syntax highlighting operations (milliseconds)
815highlight_timeout_ms = 1000
816
817# Automation features - Full-auto mode settings
818[automation.full_auto]
819# Enable full automation mode (DANGEROUS - requires careful oversight)
820enabled = false
821
822# Maximum number of turns before asking for human input
823max_turns = 30
824
825# Tools allowed in full automation mode
826allowed_tools = [
827 "write_file",
828 "read_file",
829 "list_files",
830 "grep_file",
831]
832
833# Require profile acknowledgment before using full auto
834require_profile_ack = true
835
836# Path to full auto profile configuration
837profile_path = "automation/full_auto_profile.toml"
838
839[automation.scheduled_tasks]
840# Enable /loop, cron tools, and durable `vtcode schedule` jobs
841enabled = false
842
843# Prompt caching - Cache model responses for efficiency
844[prompt_cache]
845# Enable prompt caching (reduces API calls for repeated prompts)
846enabled = true
847
848# Directory for cache storage
849cache_dir = "~/.vtcode/cache/prompts"
850
851# Maximum number of cache entries to keep
852max_entries = 1000
853
854# Maximum age of cache entries (in days)
855max_age_days = 30
856
857# Enable automatic cache cleanup
858enable_auto_cleanup = true
859
860# Minimum quality threshold to keep cache entries
861min_quality_threshold = 0.7
862
863# Keep volatile runtime counters at the end of system prompts to improve provider-side prefix cache reuse
864# (enabled by default; disable only if you need legacy prompt layout)
865cache_friendly_prompt_shaping = true
866
867# Prompt cache configuration for OpenAI
868 [prompt_cache.providers.openai]
869 enabled = true
870 min_prefix_tokens = 1024
871 idle_expiration_seconds = 3600
872 surface_metrics = true
873 # Routing key strategy for OpenAI prompt cache locality.
874 # "session" creates one stable key per VT Code conversation.
875 prompt_cache_key_mode = "session"
876 # Optional: server-side prompt cache retention for OpenAI Responses API
877 # Supported values: "in_memory" or "24h" (leave commented out for default behavior)
878 # prompt_cache_retention = "24h"
879
880# Prompt cache configuration for Anthropic
881[prompt_cache.providers.anthropic]
882enabled = true
883default_ttl_seconds = 300
884extended_ttl_seconds = 3600
885max_breakpoints = 4
886cache_system_messages = true
887cache_user_messages = true
888
889# Prompt cache configuration for Gemini
890[prompt_cache.providers.gemini]
891enabled = true
892mode = "implicit"
893min_prefix_tokens = 1024
894explicit_ttl_seconds = 3600
895
896# Prompt cache configuration for OpenRouter
897[prompt_cache.providers.openrouter]
898enabled = true
899propagate_provider_capabilities = true
900report_savings = true
901
902# Prompt cache configuration for Moonshot
903[prompt_cache.providers.moonshot]
904enabled = true
905
906# Prompt cache configuration for DeepSeek
907[prompt_cache.providers.deepseek]
908enabled = true
909surface_metrics = true
910
911# Prompt cache configuration for Z.AI
912[prompt_cache.providers.zai]
913enabled = false
914
915# Model Context Protocol (MCP) - Connect external tools and services
916[mcp]
917# Enable Model Context Protocol (may impact startup time if services unavailable)
918enabled = true
919max_concurrent_connections = 5
920request_timeout_seconds = 30
921retry_attempts = 3
922
923# MCP UI configuration
924[mcp.ui]
925mode = "compact"
926max_events = 50
927show_provider_names = true
928
929# MCP renderer profiles for different services
930[mcp.ui.renderers]
931sequential-thinking = "sequential-thinking"
932context7 = "context7"
933
934# MCP provider configuration - External services that connect via MCP
935[[mcp.providers]]
936name = "time"
937command = "uvx"
938args = ["mcp-server-time"]
939enabled = true
940max_concurrent_requests = 3
941[mcp.providers.env]
942
943# Agent Client Protocol (ACP) - IDE integration
944[acp]
945enabled = true
946
947[acp.zed]
948enabled = true
949transport = "stdio"
950# workspace_trust controls ACP trust mode: "tools_policy" (prompts) or "full_auto" (no prompts)
951workspace_trust = "full_auto"
952
953[acp.zed.tools]
954read_file = true
955list_files = true
956
957# Cross-IDE editor context bridge
958[ide_context]
959enabled = true
960inject_into_prompt = true
961show_in_tui = true
962include_selection_text = true
963provider_mode = "auto"
964
965[ide_context.providers.vscode_compatible]
966enabled = true
967
968[ide_context.providers.zed]
969enabled = true
970
971[ide_context.providers.generic]
972enabled = true"#.to_string()
973 }
974
975 #[cfg(feature = "bootstrap")]
976 fn default_vtcode_gitignore() -> String {
977 r#"# Security-focused exclusions
978.env, .env.local, secrets/, .aws/, .ssh/
979
980# Development artifacts
981target/, build/, dist/, node_modules/, vendor/
982
983# Database files
984*.db, *.sqlite, *.sqlite3
985
986# Binary files
987*.exe, *.dll, *.so, *.dylib, *.bin
988
989# IDE files (comprehensive)
990.vscode/, .idea/, *.swp, *.swo
991"#
992 .to_string()
993 }
994
995 #[cfg(feature = "bootstrap")]
996 fn default_rules_readme_template() -> &'static str {
997 "# VT Code Rules\n\nPlace shared VT Code instruction files here.\n\n- Add always-on rules as `*.md` files anywhere under `.vtcode/rules/`.\n- Add path-scoped rules with YAML frontmatter, for example:\n\n```md\n---\npaths:\n - \"src/**/*.rs\"\n---\n\n# Rust Rules\n- Keep changes surgical.\n```\n"
998 }
999
1000 #[cfg(feature = "bootstrap")]
1001 pub fn create_sample_config<P: AsRef<Path>>(output: P) -> Result<()> {
1003 let output = output.as_ref();
1004 let config_content = Self::default_vtcode_toml_template();
1005
1006 fs::write(output, config_content)
1007 .with_context(|| format!("Failed to write config file: {}", output.display()))?;
1008
1009 Ok(())
1010 }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use super::VTCodeConfig;
1016 use tempfile::tempdir;
1017
1018 #[cfg(feature = "bootstrap")]
1019 #[test]
1020 fn bootstrap_project_creates_rules_readme() {
1021 let workspace = tempdir().expect("workspace");
1022 let created = VTCodeConfig::bootstrap_project(workspace.path(), false)
1023 .expect("bootstrap project should succeed");
1024
1025 assert!(
1026 created
1027 .iter()
1028 .any(|entry| entry == ".vtcode/rules/README.md"),
1029 "created files: {:?}",
1030 created
1031 );
1032 assert!(workspace.path().join(".vtcode/rules/README.md").exists());
1033 }
1034}