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