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