Skip to main content

vtcode_config/core/
agent.rs

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