vtcode_config/core/
agent.rs

1use crate::constants::{defaults, instructions, llm_generation, project_doc, prompts};
2use crate::types::{
3    EditingMode, ReasoningEffortLevel, SystemPromptMode, ToolDocumentationMode,
4    UiSurfacePreference, VerbosityLevel,
5};
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8
9const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
10const DEFAULT_MAX_SNAPSHOTS: usize = 50;
11const DEFAULT_MAX_AGE_DAYS: u64 = 30;
12
13/// Agent-wide configuration
14#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
15#[derive(Debug, Clone, Deserialize, Serialize)]
16pub struct AgentConfig {
17    /// AI provider for single agent mode (gemini, openai, anthropic, openrouter, xai, zai)
18    #[serde(default = "default_provider")]
19    pub provider: String,
20
21    /// Environment variable that stores the API key for the active provider
22    #[serde(default = "default_api_key_env")]
23    pub api_key_env: String,
24
25    /// Default model to use
26    #[serde(default = "default_model")]
27    pub default_model: String,
28
29    /// UI theme identifier controlling ANSI styling
30    #[serde(default = "default_theme")]
31    pub theme: String,
32
33    /// System prompt mode controlling verbosity and token overhead
34    /// Options: minimal (~500-800 tokens), lightweight (~1-2k), default (~6-7k), specialized (~7-8k)
35    /// Inspired by pi-coding-agent: modern models often perform well with minimal prompts
36    #[serde(default)]
37    pub system_prompt_mode: SystemPromptMode,
38
39    /// Tool documentation mode controlling token overhead for tool definitions
40    /// Options: minimal (~800 tokens), progressive (~1.2k), full (~3k current)
41    /// Progressive: signatures upfront, detailed docs on-demand (recommended)
42    /// Minimal: signatures only, pi-coding-agent style (power users)
43    /// Full: all documentation upfront (current behavior, default)
44    #[serde(default)]
45    pub tool_documentation_mode: ToolDocumentationMode,
46
47    /// Enable split tool results for massive token savings (Phase 4)
48    /// When enabled, tools return dual-channel output:
49    /// - llm_content: Concise summary sent to LLM (token-optimized, 53-95% reduction)
50    /// - ui_content: Rich output displayed to user (full details preserved)
51    ///   Applies to: grep_file, list_files, read_file, run_pty_cmd, write_file, edit_file
52    ///   Default: true (opt-out for compatibility), recommended for production use
53    #[serde(default = "default_enable_split_tool_results")]
54    pub enable_split_tool_results: bool,
55
56    #[serde(default = "default_todo_planning_mode")]
57    pub todo_planning_mode: bool,
58
59    /// Preferred rendering surface for the interactive chat UI (auto, alternate, inline)
60    #[serde(default)]
61    pub ui_surface: UiSurfacePreference,
62
63    /// Maximum number of conversation turns before auto-termination
64    #[serde(default = "default_max_conversation_turns")]
65    pub max_conversation_turns: usize,
66
67    /// Reasoning effort level for models that support it (none, low, medium, high)
68    /// Applies to: Claude, GPT-5, GPT-5.1, Gemini, Qwen3, DeepSeek with reasoning capability
69    #[serde(default = "default_reasoning_effort")]
70    pub reasoning_effort: ReasoningEffortLevel,
71
72    /// Verbosity level for output text (low, medium, high)
73    /// Applies to: GPT-5.1 and other models that support verbosity control
74    #[serde(default = "default_verbosity")]
75    pub verbosity: VerbosityLevel,
76
77    /// Temperature for main LLM responses (0.0-1.0)
78    /// Lower values = more deterministic, higher values = more creative
79    /// Recommended: 0.7 for balanced creativity and consistency
80    /// Range: 0.0 (deterministic) to 1.0 (maximum randomness)
81    #[serde(default = "default_temperature")]
82    pub temperature: f32,
83
84    /// Temperature for prompt refinement (0.0-1.0, default: 0.3)
85    /// Lower values ensure prompt refinement is more deterministic/consistent
86    /// Keep lower than main temperature for stable prompt improvement
87    #[serde(default = "default_refine_temperature")]
88    pub refine_temperature: f32,
89
90    /// Enable an extra self-review pass to refine final responses
91    #[serde(default = "default_enable_self_review")]
92    pub enable_self_review: bool,
93
94    /// Maximum number of self-review passes
95    #[serde(default = "default_max_review_passes")]
96    pub max_review_passes: usize,
97
98    /// Enable prompt refinement pass before sending to LLM
99    #[serde(default = "default_refine_prompts_enabled")]
100    pub refine_prompts_enabled: bool,
101
102    /// Max refinement passes for prompt writing
103    #[serde(default = "default_refine_max_passes")]
104    pub refine_prompts_max_passes: usize,
105
106    /// Optional model override for the refiner (empty = auto pick efficient sibling)
107    #[serde(default)]
108    pub refine_prompts_model: String,
109
110    /// Small/lightweight model configuration for efficient operations
111    /// Used for tasks like large file reads, parsing, git history, conversation summarization
112    /// Typically 70-80% cheaper than main model; ~50% of VT Code's calls use this tier
113    #[serde(default)]
114    pub small_model: AgentSmallModelConfig,
115
116    /// Session onboarding and welcome message configuration
117    #[serde(default)]
118    pub onboarding: AgentOnboardingConfig,
119
120    /// Maximum bytes of AGENTS.md content to load from project hierarchy
121    #[serde(default = "default_project_doc_max_bytes")]
122    pub project_doc_max_bytes: usize,
123
124    /// Maximum bytes of instruction content to load from AGENTS.md hierarchy
125    #[serde(
126        default = "default_instruction_max_bytes",
127        alias = "rule_doc_max_bytes"
128    )]
129    pub instruction_max_bytes: usize,
130
131    /// Additional instruction files or globs to merge into the hierarchy
132    #[serde(default, alias = "instruction_paths", alias = "instructions")]
133    pub instruction_files: Vec<String>,
134
135    /// Custom prompt configuration for slash command shortcuts
136    #[serde(default)]
137    pub custom_prompts: AgentCustomPromptsConfig,
138
139    /// Configuration for custom slash commands
140    #[serde(default)]
141    pub custom_slash_commands: AgentCustomSlashCommandsConfig,
142
143    /// Provider-specific API keys captured from interactive configuration flows
144    #[serde(default)]
145    pub custom_api_keys: BTreeMap<String, String>,
146
147    /// Checkpointing configuration for automatic turn snapshots
148    #[serde(default)]
149    pub checkpointing: AgentCheckpointingConfig,
150
151    /// Vibe coding configuration for lazy/vague request support
152    #[serde(default)]
153    pub vibe_coding: AgentVibeCodingConfig,
154
155    /// Maximum number of retries for agent task execution (default: 2)
156    /// When an agent task fails due to retryable errors (timeout, network, 503, etc.),
157    /// it will be retried up to this many times with exponential backoff
158    #[serde(default = "default_max_task_retries")]
159    pub max_task_retries: u32,
160
161    /// Include current date/time in system prompt for temporal awareness
162    /// Helps LLM understand context for time-sensitive tasks (default: true)
163    #[serde(default = "default_include_temporal_context")]
164    pub include_temporal_context: bool,
165
166    /// Use UTC instead of local time for temporal context (default: false)
167    #[serde(default)]
168    pub temporal_context_use_utc: bool,
169
170    /// Include current working directory in system prompt (default: true)
171    #[serde(default = "default_include_working_directory")]
172    pub include_working_directory: bool,
173
174    /// Custom instructions provided by the user via configuration
175    #[serde(default)]
176    pub user_instructions: Option<String>,
177
178    /// Default editing mode: "edit" (default) or "plan"
179    /// In "plan" mode, the agent is read-only and produces implementation plans.
180    /// In "edit" mode, the agent can modify files and execute commands.
181    /// Toggle with Shift+Tab or /plan command during a session.
182    /// Codex-inspired: Encourages structured planning before execution.
183    #[serde(default)]
184    pub default_editing_mode: EditingMode,
185
186    /// Require user confirmation before executing a plan (HITL pattern)
187    /// When true, exiting plan mode shows the implementation blueprint and
188    /// requires explicit user approval before enabling edit tools.
189    /// Options in confirmation dialog: Execute, Edit Plan, Cancel
190    #[serde(default = "default_require_plan_confirmation")]
191    pub require_plan_confirmation: bool,
192
193    /// Enable autonomous mode - auto-approve safe tools with reduced HITL prompts
194    /// When true, the agent operates with fewer confirmation prompts for safe tools
195    /// (read operations, grep_file, list_files, etc.) while still blocking dangerous operations.
196    /// Toggle with /agent command during a session.
197    #[serde(default = "default_autonomous_mode")]
198    pub autonomous_mode: bool,
199
200    /// Circuit breaker configuration for resilient tool execution
201    /// Controls when the agent should pause and ask for user guidance due to repeated failures
202    #[serde(default)]
203    pub circuit_breaker: CircuitBreakerConfig,
204}
205
206#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
207#[derive(Debug, Clone, Deserialize, Serialize)]
208pub struct CircuitBreakerConfig {
209    /// Enable circuit breaker functionality
210    #[serde(default = "default_circuit_breaker_enabled")]
211    pub enabled: bool,
212
213    /// Number of consecutive failures before opening circuit
214    #[serde(default = "default_failure_threshold")]
215    pub failure_threshold: u32,
216
217    /// Pause and ask user when circuit opens (vs auto-backoff)
218    #[serde(default = "default_pause_on_open")]
219    pub pause_on_open: bool,
220
221    /// Number of open circuits before triggering pause
222    #[serde(default = "default_max_open_circuits")]
223    pub max_open_circuits: usize,
224
225    /// Cooldown period between recovery prompts (seconds)
226    #[serde(default = "default_recovery_cooldown")]
227    pub recovery_cooldown: u64,
228}
229
230impl Default for CircuitBreakerConfig {
231    fn default() -> Self {
232        Self {
233            enabled: default_circuit_breaker_enabled(),
234            failure_threshold: default_failure_threshold(),
235            pause_on_open: default_pause_on_open(),
236            max_open_circuits: default_max_open_circuits(),
237            recovery_cooldown: default_recovery_cooldown(),
238        }
239    }
240}
241
242impl Default for AgentConfig {
243    fn default() -> Self {
244        Self {
245            provider: default_provider(),
246            api_key_env: default_api_key_env(),
247            default_model: default_model(),
248            theme: default_theme(),
249            system_prompt_mode: SystemPromptMode::default(),
250            tool_documentation_mode: ToolDocumentationMode::default(),
251            enable_split_tool_results: default_enable_split_tool_results(),
252            todo_planning_mode: default_todo_planning_mode(),
253            ui_surface: UiSurfacePreference::default(),
254            max_conversation_turns: default_max_conversation_turns(),
255            reasoning_effort: default_reasoning_effort(),
256            verbosity: default_verbosity(),
257            temperature: default_temperature(),
258            refine_temperature: default_refine_temperature(),
259            enable_self_review: default_enable_self_review(),
260            max_review_passes: default_max_review_passes(),
261            refine_prompts_enabled: default_refine_prompts_enabled(),
262            refine_prompts_max_passes: default_refine_max_passes(),
263            refine_prompts_model: String::new(),
264            small_model: AgentSmallModelConfig::default(),
265            onboarding: AgentOnboardingConfig::default(),
266            project_doc_max_bytes: default_project_doc_max_bytes(),
267            instruction_max_bytes: default_instruction_max_bytes(),
268            instruction_files: Vec::new(),
269            custom_prompts: AgentCustomPromptsConfig::default(),
270            custom_slash_commands: AgentCustomSlashCommandsConfig::default(),
271            custom_api_keys: BTreeMap::new(),
272            checkpointing: AgentCheckpointingConfig::default(),
273            vibe_coding: AgentVibeCodingConfig::default(),
274            max_task_retries: default_max_task_retries(),
275            include_temporal_context: default_include_temporal_context(),
276            temporal_context_use_utc: false, // Default to local time
277            include_working_directory: default_include_working_directory(),
278            user_instructions: None,
279            default_editing_mode: EditingMode::default(),
280            require_plan_confirmation: default_require_plan_confirmation(),
281            autonomous_mode: default_autonomous_mode(),
282            circuit_breaker: CircuitBreakerConfig::default(),
283        }
284    }
285}
286
287impl AgentConfig {
288    /// Validate LLM generation parameters
289    pub fn validate_llm_params(&self) -> Result<(), String> {
290        // Validate temperature range
291        if !(0.0..=1.0).contains(&self.temperature) {
292            return Err(format!(
293                "temperature must be between 0.0 and 1.0, got {}",
294                self.temperature
295            ));
296        }
297
298        if !(0.0..=1.0).contains(&self.refine_temperature) {
299            return Err(format!(
300                "refine_temperature must be between 0.0 and 1.0, got {}",
301                self.refine_temperature
302            ));
303        }
304
305        Ok(())
306    }
307}
308
309// Optimized: Use inline defaults with constants to reduce function call overhead
310#[inline]
311fn default_provider() -> String {
312    defaults::DEFAULT_PROVIDER.into()
313}
314
315#[inline]
316fn default_api_key_env() -> String {
317    defaults::DEFAULT_API_KEY_ENV.into()
318}
319
320#[inline]
321fn default_model() -> String {
322    defaults::DEFAULT_MODEL.into()
323}
324
325#[inline]
326fn default_theme() -> String {
327    defaults::DEFAULT_THEME.into()
328}
329
330#[inline]
331const fn default_todo_planning_mode() -> bool {
332    true
333}
334
335#[inline]
336const fn default_enable_split_tool_results() -> bool {
337    true // Default: enabled for production use (84% token savings)
338}
339
340#[inline]
341const fn default_max_conversation_turns() -> usize {
342    150
343}
344
345#[inline]
346fn default_reasoning_effort() -> ReasoningEffortLevel {
347    ReasoningEffortLevel::default()
348}
349
350#[inline]
351fn default_verbosity() -> VerbosityLevel {
352    VerbosityLevel::default()
353}
354
355#[inline]
356const fn default_temperature() -> f32 {
357    llm_generation::DEFAULT_TEMPERATURE
358}
359
360#[inline]
361const fn default_refine_temperature() -> f32 {
362    llm_generation::DEFAULT_REFINE_TEMPERATURE
363}
364
365#[inline]
366const fn default_enable_self_review() -> bool {
367    false
368}
369
370#[inline]
371const fn default_max_review_passes() -> usize {
372    1
373}
374
375#[inline]
376const fn default_refine_prompts_enabled() -> bool {
377    false
378}
379
380#[inline]
381const fn default_refine_max_passes() -> usize {
382    1
383}
384
385#[inline]
386const fn default_project_doc_max_bytes() -> usize {
387    project_doc::DEFAULT_MAX_BYTES
388}
389
390#[inline]
391const fn default_instruction_max_bytes() -> usize {
392    instructions::DEFAULT_MAX_BYTES
393}
394
395#[inline]
396const fn default_max_task_retries() -> u32 {
397    2 // Retry twice on transient failures
398}
399
400#[inline]
401const fn default_include_temporal_context() -> bool {
402    true // Enable by default - minimal overhead (~20 tokens)
403}
404
405#[inline]
406const fn default_include_working_directory() -> bool {
407    true // Enable by default - minimal overhead (~10 tokens)
408}
409
410#[inline]
411const fn default_require_plan_confirmation() -> bool {
412    true // Default: require confirmation (HITL pattern)
413}
414
415#[inline]
416const fn default_autonomous_mode() -> bool {
417    false // Default: interactive mode with full HITL
418}
419
420#[inline]
421const fn default_circuit_breaker_enabled() -> bool {
422    true // Default: enabled for resilient execution
423}
424
425#[inline]
426const fn default_failure_threshold() -> u32 {
427    5 // Open circuit after 5 consecutive failures
428}
429
430#[inline]
431const fn default_pause_on_open() -> bool {
432    true // Default: ask user for guidance on circuit breaker
433}
434
435#[inline]
436const fn default_max_open_circuits() -> usize {
437    3 // Pause when 3+ tools have open circuits
438}
439
440#[inline]
441const fn default_recovery_cooldown() -> u64 {
442    60 // Cooldown between recovery prompts (seconds)
443}
444
445#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
446#[derive(Debug, Clone, Deserialize, Serialize)]
447pub struct AgentCustomPromptsConfig {
448    /// Master switch for custom prompt support
449    #[serde(default = "default_custom_prompts_enabled")]
450    pub enabled: bool,
451
452    /// Primary directory for prompt markdown files
453    #[serde(default = "default_custom_prompts_directory")]
454    pub directory: String,
455
456    /// Additional directories to search for prompts
457    #[serde(default)]
458    pub extra_directories: Vec<String>,
459
460    /// Maximum file size (KB) to load for a single prompt
461    #[serde(default = "default_custom_prompts_max_file_size_kb")]
462    pub max_file_size_kb: usize,
463}
464
465impl Default for AgentCustomPromptsConfig {
466    fn default() -> Self {
467        Self {
468            enabled: default_custom_prompts_enabled(),
469            directory: default_custom_prompts_directory(),
470            extra_directories: Vec::new(),
471            max_file_size_kb: default_custom_prompts_max_file_size_kb(),
472        }
473    }
474}
475
476/// Configuration for custom slash commands
477#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
478#[derive(Debug, Clone, Deserialize, Serialize, Default)]
479pub struct AgentCustomSlashCommandsConfig {
480    /// Master switch for custom slash command support
481    #[serde(default = "default_custom_slash_commands_enabled")]
482    pub enabled: bool,
483
484    /// Primary directory for slash command markdown files
485    #[serde(default = "default_custom_slash_commands_directory")]
486    pub directory: String,
487
488    /// Additional directories to search for slash commands
489    #[serde(default)]
490    pub extra_directories: Vec<String>,
491
492    /// Maximum file size (KB) to load for a single slash command
493    #[serde(default = "default_custom_slash_commands_max_file_size_kb")]
494    pub max_file_size_kb: usize,
495}
496
497#[inline]
498const fn default_custom_slash_commands_enabled() -> bool {
499    true
500}
501
502fn default_custom_slash_commands_directory() -> String {
503    crate::constants::prompts::DEFAULT_CUSTOM_SLASH_COMMANDS_DIR.into()
504}
505
506const fn default_custom_slash_commands_max_file_size_kb() -> usize {
507    64 // 64KB default, same as prompts
508}
509
510#[inline]
511const fn default_custom_prompts_enabled() -> bool {
512    true
513}
514
515#[inline]
516fn default_custom_prompts_directory() -> String {
517    prompts::DEFAULT_CUSTOM_PROMPTS_DIR.into()
518}
519
520#[inline]
521const fn default_custom_prompts_max_file_size_kb() -> usize {
522    prompts::DEFAULT_CUSTOM_PROMPT_MAX_FILE_SIZE_KB
523}
524
525#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
526#[derive(Debug, Clone, Deserialize, Serialize)]
527pub struct AgentCheckpointingConfig {
528    /// Enable automatic checkpoints after each successful turn
529    #[serde(default = "default_checkpointing_enabled")]
530    pub enabled: bool,
531
532    /// Optional custom directory for storing checkpoints (relative to workspace or absolute)
533    #[serde(default)]
534    pub storage_dir: Option<String>,
535
536    /// Maximum number of checkpoints to retain on disk
537    #[serde(default = "default_checkpointing_max_snapshots")]
538    pub max_snapshots: usize,
539
540    /// Maximum age in days before checkpoints are removed automatically (None disables)
541    #[serde(default = "default_checkpointing_max_age_days")]
542    pub max_age_days: Option<u64>,
543}
544
545impl Default for AgentCheckpointingConfig {
546    fn default() -> Self {
547        Self {
548            enabled: default_checkpointing_enabled(),
549            storage_dir: None,
550            max_snapshots: default_checkpointing_max_snapshots(),
551            max_age_days: default_checkpointing_max_age_days(),
552        }
553    }
554}
555
556#[inline]
557const fn default_checkpointing_enabled() -> bool {
558    DEFAULT_CHECKPOINTS_ENABLED
559}
560
561#[inline]
562const fn default_checkpointing_max_snapshots() -> usize {
563    DEFAULT_MAX_SNAPSHOTS
564}
565
566#[inline]
567const fn default_checkpointing_max_age_days() -> Option<u64> {
568    Some(DEFAULT_MAX_AGE_DAYS)
569}
570
571#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
572#[derive(Debug, Clone, Deserialize, Serialize)]
573pub struct AgentOnboardingConfig {
574    /// Toggle onboarding message rendering
575    #[serde(default = "default_onboarding_enabled")]
576    pub enabled: bool,
577
578    /// Introductory text shown at session start
579    #[serde(default = "default_intro_text")]
580    pub intro_text: String,
581
582    /// Whether to include project overview in onboarding message
583    #[serde(default = "default_show_project_overview")]
584    pub include_project_overview: bool,
585
586    /// Whether to include language summary in onboarding message
587    #[serde(default = "default_show_language_summary")]
588    pub include_language_summary: bool,
589
590    /// Whether to include AGENTS.md highlights in onboarding message
591    #[serde(default = "default_show_guideline_highlights")]
592    pub include_guideline_highlights: bool,
593
594    /// Whether to surface usage tips inside the welcome text banner
595    #[serde(default = "default_show_usage_tips_in_welcome")]
596    pub include_usage_tips_in_welcome: bool,
597
598    /// Whether to surface suggested actions inside the welcome text banner
599    #[serde(default = "default_show_recommended_actions_in_welcome")]
600    pub include_recommended_actions_in_welcome: bool,
601
602    /// Maximum number of guideline bullets to surface
603    #[serde(default = "default_guideline_highlight_limit")]
604    pub guideline_highlight_limit: usize,
605
606    /// Tips for collaborating with the agent effectively
607    #[serde(default = "default_usage_tips")]
608    pub usage_tips: Vec<String>,
609
610    /// Recommended follow-up actions to display
611    #[serde(default = "default_recommended_actions")]
612    pub recommended_actions: Vec<String>,
613
614    /// Placeholder suggestion for the chat input bar
615    #[serde(default)]
616    pub chat_placeholder: Option<String>,
617}
618
619impl Default for AgentOnboardingConfig {
620    fn default() -> Self {
621        Self {
622            enabled: default_onboarding_enabled(),
623            intro_text: default_intro_text(),
624            include_project_overview: default_show_project_overview(),
625            include_language_summary: default_show_language_summary(),
626            include_guideline_highlights: default_show_guideline_highlights(),
627            include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
628            include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
629            guideline_highlight_limit: default_guideline_highlight_limit(),
630            usage_tips: default_usage_tips(),
631            recommended_actions: default_recommended_actions(),
632            chat_placeholder: None,
633        }
634    }
635}
636
637#[inline]
638const fn default_onboarding_enabled() -> bool {
639    true
640}
641
642const DEFAULT_INTRO_TEXT: &str =
643    "Let's get oriented. I preloaded workspace context so we can move fast.";
644
645#[inline]
646fn default_intro_text() -> String {
647    DEFAULT_INTRO_TEXT.into()
648}
649
650#[inline]
651const fn default_show_project_overview() -> bool {
652    true
653}
654
655#[inline]
656const fn default_show_language_summary() -> bool {
657    false
658}
659
660#[inline]
661const fn default_show_guideline_highlights() -> bool {
662    true
663}
664
665#[inline]
666const fn default_show_usage_tips_in_welcome() -> bool {
667    false
668}
669
670#[inline]
671const fn default_show_recommended_actions_in_welcome() -> bool {
672    false
673}
674
675#[inline]
676const fn default_guideline_highlight_limit() -> usize {
677    3
678}
679
680const DEFAULT_USAGE_TIPS: &[&str] = &[
681    "Describe your current coding goal or ask for a quick status overview.",
682    "Reference AGENTS.md guidelines when proposing changes.",
683    "Prefer asking for targeted file reads or diffs before editing.",
684];
685
686const DEFAULT_RECOMMENDED_ACTIONS: &[&str] = &[
687    "Review the highlighted guidelines and share the task you want to tackle.",
688    "Ask for a workspace tour if you need more context.",
689];
690
691fn default_usage_tips() -> Vec<String> {
692    DEFAULT_USAGE_TIPS.iter().map(|s| (*s).into()).collect()
693}
694
695fn default_recommended_actions() -> Vec<String> {
696    DEFAULT_RECOMMENDED_ACTIONS
697        .iter()
698        .map(|s| (*s).into())
699        .collect()
700}
701
702/// Small/lightweight model configuration for efficient operations
703///
704/// Following VT Code's pattern, use a smaller model (e.g., Haiku, GPT-4 Mini) for 50%+ of calls:
705/// - Large file reads and parsing (>50KB)
706/// - Web page summarization and analysis
707/// - Git history and commit message processing
708/// - One-word processing labels and simple classifications
709///
710/// Typically 70-80% cheaper than the main model while maintaining quality for these tasks.
711#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
712#[derive(Debug, Clone, Deserialize, Serialize)]
713pub struct AgentSmallModelConfig {
714    /// Enable small model tier for efficient operations
715    #[serde(default = "default_small_model_enabled")]
716    pub enabled: bool,
717
718    /// Small model to use (e.g., "claude-3-5-haiku", "gpt-4o-mini", "gemini-2.0-flash")
719    /// Leave empty to auto-select a lightweight sibling of the main model
720    #[serde(default)]
721    pub model: String,
722
723    /// Temperature for small model responses
724    #[serde(default = "default_small_model_temperature")]
725    pub temperature: f32,
726
727    /// Enable small model for large file reads (>50KB)
728    #[serde(default = "default_small_model_for_large_reads")]
729    pub use_for_large_reads: bool,
730
731    /// Enable small model for web content summarization
732    #[serde(default = "default_small_model_for_web_summary")]
733    pub use_for_web_summary: bool,
734
735    /// Enable small model for git history processing
736    #[serde(default = "default_small_model_for_git_history")]
737    pub use_for_git_history: bool,
738}
739
740impl Default for AgentSmallModelConfig {
741    fn default() -> Self {
742        Self {
743            enabled: default_small_model_enabled(),
744            model: String::new(),
745            temperature: default_small_model_temperature(),
746            use_for_large_reads: default_small_model_for_large_reads(),
747            use_for_web_summary: default_small_model_for_web_summary(),
748            use_for_git_history: default_small_model_for_git_history(),
749        }
750    }
751}
752
753#[inline]
754const fn default_small_model_enabled() -> bool {
755    true // Enable by default following VT Code pattern
756}
757
758#[inline]
759const fn default_small_model_temperature() -> f32 {
760    0.3 // More deterministic for parsing/summarization
761}
762
763#[inline]
764const fn default_small_model_for_large_reads() -> bool {
765    true
766}
767
768#[inline]
769const fn default_small_model_for_web_summary() -> bool {
770    true
771}
772
773#[inline]
774const fn default_small_model_for_git_history() -> bool {
775    true
776}
777
778/// Vibe coding configuration for lazy/vague request support
779///
780/// Enables intelligent context gathering and entity resolution to support
781/// casual, imprecise requests like "make it blue" or "decrease by half".
782#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
783#[derive(Debug, Clone, Deserialize, Serialize)]
784pub struct AgentVibeCodingConfig {
785    /// Enable vibe coding support
786    #[serde(default = "default_vibe_coding_enabled")]
787    pub enabled: bool,
788
789    /// Minimum prompt length for refinement (default: 5 chars)
790    #[serde(default = "default_vibe_min_prompt_length")]
791    pub min_prompt_length: usize,
792
793    /// Minimum prompt words for refinement (default: 2 words)
794    #[serde(default = "default_vibe_min_prompt_words")]
795    pub min_prompt_words: usize,
796
797    /// Enable fuzzy entity resolution
798    #[serde(default = "default_vibe_entity_resolution")]
799    pub enable_entity_resolution: bool,
800
801    /// Entity index cache file path (relative to workspace)
802    #[serde(default = "default_vibe_entity_cache")]
803    pub entity_index_cache: String,
804
805    /// Maximum entity matches to return (default: 5)
806    #[serde(default = "default_vibe_max_entity_matches")]
807    pub max_entity_matches: usize,
808
809    /// Track workspace state (file activity, value changes)
810    #[serde(default = "default_vibe_track_workspace")]
811    pub track_workspace_state: bool,
812
813    /// Maximum recent files to track (default: 20)
814    #[serde(default = "default_vibe_max_recent_files")]
815    pub max_recent_files: usize,
816
817    /// Track value history for inference
818    #[serde(default = "default_vibe_track_values")]
819    pub track_value_history: bool,
820
821    /// Enable conversation memory for pronoun resolution
822    #[serde(default = "default_vibe_conversation_memory")]
823    pub enable_conversation_memory: bool,
824
825    /// Maximum conversation turns to remember (default: 50)
826    #[serde(default = "default_vibe_max_memory_turns")]
827    pub max_memory_turns: usize,
828
829    /// Enable pronoun resolution (it, that, this)
830    #[serde(default = "default_vibe_pronoun_resolution")]
831    pub enable_pronoun_resolution: bool,
832
833    /// Enable proactive context gathering
834    #[serde(default = "default_vibe_proactive_context")]
835    pub enable_proactive_context: bool,
836
837    /// Maximum files to gather for context (default: 3)
838    #[serde(default = "default_vibe_max_context_files")]
839    pub max_context_files: usize,
840
841    /// Maximum code snippets per file (default: 20 lines)
842    #[serde(default = "default_vibe_max_snippets_per_file")]
843    pub max_context_snippets_per_file: usize,
844
845    /// Maximum search results to include (default: 5)
846    #[serde(default = "default_vibe_max_search_results")]
847    pub max_search_results: usize,
848
849    /// Enable relative value inference (by half, double, etc.)
850    #[serde(default = "default_vibe_value_inference")]
851    pub enable_relative_value_inference: bool,
852}
853
854impl Default for AgentVibeCodingConfig {
855    fn default() -> Self {
856        Self {
857            enabled: default_vibe_coding_enabled(),
858            min_prompt_length: default_vibe_min_prompt_length(),
859            min_prompt_words: default_vibe_min_prompt_words(),
860            enable_entity_resolution: default_vibe_entity_resolution(),
861            entity_index_cache: default_vibe_entity_cache(),
862            max_entity_matches: default_vibe_max_entity_matches(),
863            track_workspace_state: default_vibe_track_workspace(),
864            max_recent_files: default_vibe_max_recent_files(),
865            track_value_history: default_vibe_track_values(),
866            enable_conversation_memory: default_vibe_conversation_memory(),
867            max_memory_turns: default_vibe_max_memory_turns(),
868            enable_pronoun_resolution: default_vibe_pronoun_resolution(),
869            enable_proactive_context: default_vibe_proactive_context(),
870            max_context_files: default_vibe_max_context_files(),
871            max_context_snippets_per_file: default_vibe_max_snippets_per_file(),
872            max_search_results: default_vibe_max_search_results(),
873            enable_relative_value_inference: default_vibe_value_inference(),
874        }
875    }
876}
877
878// Vibe coding default functions
879#[inline]
880const fn default_vibe_coding_enabled() -> bool {
881    false // Conservative default, opt-in
882}
883
884#[inline]
885const fn default_vibe_min_prompt_length() -> usize {
886    5
887}
888
889#[inline]
890const fn default_vibe_min_prompt_words() -> usize {
891    2
892}
893
894#[inline]
895const fn default_vibe_entity_resolution() -> bool {
896    true
897}
898
899#[inline]
900fn default_vibe_entity_cache() -> String {
901    ".vtcode/entity_index.json".into()
902}
903
904#[inline]
905const fn default_vibe_max_entity_matches() -> usize {
906    5
907}
908
909#[inline]
910const fn default_vibe_track_workspace() -> bool {
911    true
912}
913
914#[inline]
915const fn default_vibe_max_recent_files() -> usize {
916    20
917}
918
919#[inline]
920const fn default_vibe_track_values() -> bool {
921    true
922}
923
924#[inline]
925const fn default_vibe_conversation_memory() -> bool {
926    true
927}
928
929#[inline]
930const fn default_vibe_max_memory_turns() -> usize {
931    50
932}
933
934#[inline]
935const fn default_vibe_pronoun_resolution() -> bool {
936    true
937}
938
939#[inline]
940const fn default_vibe_proactive_context() -> bool {
941    true
942}
943
944#[inline]
945const fn default_vibe_max_context_files() -> usize {
946    3
947}
948
949#[inline]
950const fn default_vibe_max_snippets_per_file() -> usize {
951    20
952}
953
954#[inline]
955const fn default_vibe_max_search_results() -> usize {
956    5
957}
958
959#[inline]
960const fn default_vibe_value_inference() -> bool {
961    true
962}
963
964#[cfg(test)]
965mod tests {
966    use super::*;
967
968    #[test]
969    fn test_editing_mode_config_default() {
970        let config = AgentConfig::default();
971        assert_eq!(config.default_editing_mode, EditingMode::Edit);
972        assert!(config.require_plan_confirmation);
973        assert!(!config.autonomous_mode);
974    }
975}