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