vtcode_config/core/
agent.rs

1use crate::constants::{defaults, instructions, project_doc, prompts};
2use crate::types::{ReasoningEffortLevel, UiSurfacePreference};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6const DEFAULT_CHECKPOINTS_ENABLED: bool = true;
7const DEFAULT_MAX_SNAPSHOTS: usize = 50;
8const DEFAULT_MAX_AGE_DAYS: u64 = 30;
9
10/// Agent-wide configuration
11#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
12#[derive(Debug, Clone, Deserialize, Serialize)]
13pub struct AgentConfig {
14    /// AI provider for single agent mode (gemini, openai, anthropic, openrouter, xai, zai)
15    #[serde(default = "default_provider")]
16    pub provider: String,
17
18    /// Environment variable that stores the API key for the active provider
19    #[serde(default = "default_api_key_env")]
20    pub api_key_env: String,
21
22    /// Default model to use
23    #[serde(default = "default_model")]
24    pub default_model: String,
25
26    /// UI theme identifier controlling ANSI styling
27    #[serde(default = "default_theme")]
28    pub theme: String,
29
30    /// Enable TODO planning workflow integrations (update_plan tool, onboarding hints)
31    #[serde(default = "default_todo_planning_mode")]
32    pub todo_planning_mode: bool,
33
34    /// Preferred rendering surface for the interactive chat UI (auto, alternate, inline)
35    #[serde(default)]
36    pub ui_surface: UiSurfacePreference,
37
38    /// Maximum number of conversation turns before auto-termination
39    #[serde(default = "default_max_conversation_turns")]
40    pub max_conversation_turns: usize,
41
42    /// Reasoning effort level for models that support it (low, medium, high)
43    /// Applies to: Claude, GPT-5, Gemini, Qwen3, DeepSeek with reasoning capability
44    #[serde(default = "default_reasoning_effort")]
45    pub reasoning_effort: ReasoningEffortLevel,
46
47    /// Enable an extra self-review pass to refine final responses
48    #[serde(default = "default_enable_self_review")]
49    pub enable_self_review: bool,
50
51    /// Maximum number of self-review passes
52    #[serde(default = "default_max_review_passes")]
53    pub max_review_passes: usize,
54
55    /// Enable prompt refinement pass before sending to LLM
56    #[serde(default = "default_refine_prompts_enabled")]
57    pub refine_prompts_enabled: bool,
58
59    /// Max refinement passes for prompt writing
60    #[serde(default = "default_refine_max_passes")]
61    pub refine_prompts_max_passes: usize,
62
63    /// Optional model override for the refiner (empty = auto pick efficient sibling)
64    #[serde(default)]
65    pub refine_prompts_model: String,
66
67    /// Session onboarding and welcome message configuration
68    #[serde(default)]
69    pub onboarding: AgentOnboardingConfig,
70
71    /// Maximum bytes of AGENTS.md content to load from project hierarchy
72    #[serde(default = "default_project_doc_max_bytes")]
73    pub project_doc_max_bytes: usize,
74
75    /// Maximum bytes of instruction content to load from AGENTS.md hierarchy
76    #[serde(
77        default = "default_instruction_max_bytes",
78        alias = "rule_doc_max_bytes"
79    )]
80    pub instruction_max_bytes: usize,
81
82    /// Additional instruction files or globs to merge into the hierarchy
83    #[serde(default, alias = "instruction_paths", alias = "instructions")]
84    pub instruction_files: Vec<String>,
85
86    /// Custom prompt configuration for slash command shortcuts
87    #[serde(default)]
88    pub custom_prompts: AgentCustomPromptsConfig,
89
90    /// Provider-specific API keys captured from interactive configuration flows
91    #[serde(default)]
92    pub custom_api_keys: BTreeMap<String, String>,
93
94    /// Checkpointing configuration for automatic turn snapshots
95    #[serde(default)]
96    pub checkpointing: AgentCheckpointingConfig,
97}
98
99impl Default for AgentConfig {
100    fn default() -> Self {
101        Self {
102            provider: default_provider(),
103            api_key_env: default_api_key_env(),
104            default_model: default_model(),
105            theme: default_theme(),
106            todo_planning_mode: default_todo_planning_mode(),
107            ui_surface: UiSurfacePreference::default(),
108            max_conversation_turns: default_max_conversation_turns(),
109            reasoning_effort: default_reasoning_effort(),
110            enable_self_review: default_enable_self_review(),
111            max_review_passes: default_max_review_passes(),
112            refine_prompts_enabled: default_refine_prompts_enabled(),
113            refine_prompts_max_passes: default_refine_max_passes(),
114            refine_prompts_model: String::new(),
115            onboarding: AgentOnboardingConfig::default(),
116            project_doc_max_bytes: default_project_doc_max_bytes(),
117            instruction_max_bytes: default_instruction_max_bytes(),
118            instruction_files: Vec::new(),
119            custom_prompts: AgentCustomPromptsConfig::default(),
120            custom_api_keys: BTreeMap::new(),
121            checkpointing: AgentCheckpointingConfig::default(),
122        }
123    }
124}
125
126fn default_provider() -> String {
127    defaults::DEFAULT_PROVIDER.to_string()
128}
129
130fn default_api_key_env() -> String {
131    defaults::DEFAULT_API_KEY_ENV.to_string()
132}
133fn default_model() -> String {
134    defaults::DEFAULT_MODEL.to_string()
135}
136fn default_theme() -> String {
137    defaults::DEFAULT_THEME.to_string()
138}
139
140fn default_todo_planning_mode() -> bool {
141    true
142}
143fn default_max_conversation_turns() -> usize {
144    150
145}
146fn default_reasoning_effort() -> ReasoningEffortLevel {
147    ReasoningEffortLevel::default()
148}
149
150fn default_enable_self_review() -> bool {
151    false
152}
153
154fn default_max_review_passes() -> usize {
155    1
156}
157
158fn default_refine_prompts_enabled() -> bool {
159    false
160}
161
162fn default_refine_max_passes() -> usize {
163    1
164}
165
166fn default_project_doc_max_bytes() -> usize {
167    project_doc::DEFAULT_MAX_BYTES
168}
169
170fn default_instruction_max_bytes() -> usize {
171    instructions::DEFAULT_MAX_BYTES
172}
173
174#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
175#[derive(Debug, Clone, Deserialize, Serialize)]
176pub struct AgentCustomPromptsConfig {
177    /// Master switch for custom prompt support
178    #[serde(default = "default_custom_prompts_enabled")]
179    pub enabled: bool,
180
181    /// Primary directory for prompt markdown files
182    #[serde(default = "default_custom_prompts_directory")]
183    pub directory: String,
184
185    /// Additional directories to search for prompts
186    #[serde(default)]
187    pub extra_directories: Vec<String>,
188
189    /// Maximum file size (KB) to load for a single prompt
190    #[serde(default = "default_custom_prompts_max_file_size_kb")]
191    pub max_file_size_kb: usize,
192}
193
194impl Default for AgentCustomPromptsConfig {
195    fn default() -> Self {
196        Self {
197            enabled: default_custom_prompts_enabled(),
198            directory: default_custom_prompts_directory(),
199            extra_directories: Vec::new(),
200            max_file_size_kb: default_custom_prompts_max_file_size_kb(),
201        }
202    }
203}
204
205fn default_custom_prompts_enabled() -> bool {
206    true
207}
208
209fn default_custom_prompts_directory() -> String {
210    prompts::DEFAULT_CUSTOM_PROMPTS_DIR.to_string()
211}
212
213fn default_custom_prompts_max_file_size_kb() -> usize {
214    prompts::DEFAULT_CUSTOM_PROMPT_MAX_FILE_SIZE_KB
215}
216
217#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
218#[derive(Debug, Clone, Deserialize, Serialize)]
219pub struct AgentCheckpointingConfig {
220    /// Enable automatic checkpoints after each successful turn
221    #[serde(default = "default_checkpointing_enabled")]
222    pub enabled: bool,
223
224    /// Optional custom directory for storing checkpoints (relative to workspace or absolute)
225    #[serde(default)]
226    pub storage_dir: Option<String>,
227
228    /// Maximum number of checkpoints to retain on disk
229    #[serde(default = "default_checkpointing_max_snapshots")]
230    pub max_snapshots: usize,
231
232    /// Maximum age in days before checkpoints are removed automatically (None disables)
233    #[serde(default = "default_checkpointing_max_age_days")]
234    pub max_age_days: Option<u64>,
235}
236
237impl Default for AgentCheckpointingConfig {
238    fn default() -> Self {
239        Self {
240            enabled: default_checkpointing_enabled(),
241            storage_dir: None,
242            max_snapshots: default_checkpointing_max_snapshots(),
243            max_age_days: default_checkpointing_max_age_days(),
244        }
245    }
246}
247
248fn default_checkpointing_enabled() -> bool {
249    DEFAULT_CHECKPOINTS_ENABLED
250}
251
252fn default_checkpointing_max_snapshots() -> usize {
253    DEFAULT_MAX_SNAPSHOTS
254}
255
256fn default_checkpointing_max_age_days() -> Option<u64> {
257    Some(DEFAULT_MAX_AGE_DAYS)
258}
259
260#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
261#[derive(Debug, Clone, Deserialize, Serialize)]
262pub struct AgentOnboardingConfig {
263    /// Toggle onboarding message rendering
264    #[serde(default = "default_onboarding_enabled")]
265    pub enabled: bool,
266
267    /// Introductory text shown at session start
268    #[serde(default = "default_intro_text")]
269    pub intro_text: String,
270
271    /// Whether to include project overview in onboarding message
272    #[serde(default = "default_show_project_overview")]
273    pub include_project_overview: bool,
274
275    /// Whether to include language summary in onboarding message
276    #[serde(default = "default_show_language_summary")]
277    pub include_language_summary: bool,
278
279    /// Whether to include AGENTS.md highlights in onboarding message
280    #[serde(default = "default_show_guideline_highlights")]
281    pub include_guideline_highlights: bool,
282
283    /// Whether to surface usage tips inside the welcome text banner
284    #[serde(default = "default_show_usage_tips_in_welcome")]
285    pub include_usage_tips_in_welcome: bool,
286
287    /// Whether to surface suggested actions inside the welcome text banner
288    #[serde(default = "default_show_recommended_actions_in_welcome")]
289    pub include_recommended_actions_in_welcome: bool,
290
291    /// Maximum number of guideline bullets to surface
292    #[serde(default = "default_guideline_highlight_limit")]
293    pub guideline_highlight_limit: usize,
294
295    /// Tips for collaborating with the agent effectively
296    #[serde(default = "default_usage_tips")]
297    pub usage_tips: Vec<String>,
298
299    /// Recommended follow-up actions to display
300    #[serde(default = "default_recommended_actions")]
301    pub recommended_actions: Vec<String>,
302
303    /// Placeholder suggestion for the chat input bar
304    #[serde(default)]
305    pub chat_placeholder: Option<String>,
306}
307
308impl Default for AgentOnboardingConfig {
309    fn default() -> Self {
310        Self {
311            enabled: default_onboarding_enabled(),
312            intro_text: default_intro_text(),
313            include_project_overview: default_show_project_overview(),
314            include_language_summary: default_show_language_summary(),
315            include_guideline_highlights: default_show_guideline_highlights(),
316            include_usage_tips_in_welcome: default_show_usage_tips_in_welcome(),
317            include_recommended_actions_in_welcome: default_show_recommended_actions_in_welcome(),
318            guideline_highlight_limit: default_guideline_highlight_limit(),
319            usage_tips: default_usage_tips(),
320            recommended_actions: default_recommended_actions(),
321            chat_placeholder: None,
322        }
323    }
324}
325
326fn default_onboarding_enabled() -> bool {
327    true
328}
329
330fn default_intro_text() -> String {
331    "Let's get oriented. I preloaded workspace context so we can move fast.".to_string()
332}
333
334fn default_show_project_overview() -> bool {
335    true
336}
337
338fn default_show_language_summary() -> bool {
339    false
340}
341
342fn default_show_guideline_highlights() -> bool {
343    true
344}
345
346fn default_show_usage_tips_in_welcome() -> bool {
347    false
348}
349
350fn default_show_recommended_actions_in_welcome() -> bool {
351    false
352}
353
354fn default_guideline_highlight_limit() -> usize {
355    3
356}
357
358fn default_usage_tips() -> Vec<String> {
359    vec![
360        "Describe your current coding goal or ask for a quick status overview.".to_string(),
361        "Reference AGENTS.md guidelines when proposing changes.".to_string(),
362        "Draft or refresh your TODO list with update_plan before coding.".to_string(),
363        "Prefer asking for targeted file reads or diffs before editing.".to_string(),
364    ]
365}
366
367fn default_recommended_actions() -> Vec<String> {
368    vec![
369        "Start the session by outlining a 3–6 step TODO plan via update_plan.".to_string(),
370        "Review the highlighted guidelines and share the task you want to tackle.".to_string(),
371        "Ask for a workspace tour if you need more context.".to_string(),
372    ]
373}