vtcode_config/
context.rs

1use crate::constants::context as context_defaults;
2use anyhow::{Context, Result, ensure};
3use serde::{Deserialize, Serialize};
4
5#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
6#[derive(Debug, Clone, Deserialize, Serialize)]
7pub struct LedgerConfig {
8    #[serde(default = "default_enabled")]
9    pub enabled: bool,
10    #[serde(default = "default_max_entries")]
11    pub max_entries: usize,
12    /// Inject ledger into the system prompt each turn
13    #[serde(default = "default_include_in_prompt")]
14    pub include_in_prompt: bool,
15    /// Preserve ledger entries during context compression
16    #[serde(default = "default_preserve_in_compression")]
17    pub preserve_in_compression: bool,
18}
19
20impl Default for LedgerConfig {
21    fn default() -> Self {
22        Self {
23            enabled: default_enabled(),
24            max_entries: default_max_entries(),
25            include_in_prompt: default_include_in_prompt(),
26            preserve_in_compression: default_preserve_in_compression(),
27        }
28    }
29}
30
31impl LedgerConfig {
32    pub fn validate(&self) -> Result<()> {
33        ensure!(
34            self.max_entries > 0,
35            "Ledger max_entries must be greater than zero"
36        );
37        Ok(())
38    }
39}
40
41fn default_enabled() -> bool {
42    true
43}
44fn default_max_entries() -> usize {
45    12
46}
47fn default_include_in_prompt() -> bool {
48    true
49}
50fn default_preserve_in_compression() -> bool {
51    true
52}
53
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55#[derive(Debug, Clone, Deserialize, Serialize)]
56pub struct TokenBudgetConfig {
57    /// Enable token budget tracking
58    #[serde(default = "default_token_budget_enabled")]
59    pub enabled: bool,
60    /// Model name for tokenizer selection
61    #[serde(default = "default_token_budget_model")]
62    pub model: String,
63    /// Optional override for tokenizer identifier or file path
64    #[serde(default)]
65    pub tokenizer: Option<String>,
66    /// Warning threshold (0.0-1.0)
67    #[serde(default = "default_warning_threshold")]
68    pub warning_threshold: f64,
69    /// Compaction threshold (0.0-1.0)
70    #[serde(default = "default_compaction_threshold")]
71    pub compaction_threshold: f64,
72    /// Enable detailed component tracking
73    #[serde(default = "default_detailed_tracking")]
74    pub detailed_tracking: bool,
75}
76
77impl Default for TokenBudgetConfig {
78    fn default() -> Self {
79        Self {
80            enabled: default_token_budget_enabled(),
81            model: default_token_budget_model(),
82            tokenizer: None,
83            warning_threshold: default_warning_threshold(),
84            compaction_threshold: default_compaction_threshold(),
85            detailed_tracking: default_detailed_tracking(),
86        }
87    }
88}
89
90impl TokenBudgetConfig {
91    pub fn validate(&self) -> Result<()> {
92        ensure!(
93            (0.0..=1.0).contains(&self.warning_threshold),
94            "Token budget warning_threshold must be between 0.0 and 1.0"
95        );
96        ensure!(
97            (0.0..=1.0).contains(&self.compaction_threshold),
98            "Token budget compaction_threshold must be between 0.0 and 1.0"
99        );
100        ensure!(
101            self.warning_threshold <= self.compaction_threshold,
102            "Token budget warning_threshold must be less than or equal to compaction_threshold"
103        );
104
105        if self.enabled {
106            ensure!(
107                !self.model.trim().is_empty(),
108                "Token budget model must be provided when token budgeting is enabled"
109            );
110            if let Some(tokenizer) = &self.tokenizer {
111                ensure!(
112                    !tokenizer.trim().is_empty(),
113                    "Token budget tokenizer override cannot be empty"
114                );
115            }
116        }
117
118        Ok(())
119    }
120}
121
122fn default_token_budget_enabled() -> bool {
123    true
124}
125fn default_token_budget_model() -> String {
126    "gpt-5-nano".to_string()
127}
128fn default_warning_threshold() -> f64 {
129    0.75
130}
131fn default_compaction_threshold() -> f64 {
132    0.85
133}
134fn default_detailed_tracking() -> bool {
135    false
136}
137
138#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
139#[derive(Debug, Clone, Deserialize, Serialize)]
140pub struct ContextCurationConfig {
141    /// Enable dynamic context curation
142    #[serde(default = "default_curation_enabled")]
143    pub enabled: bool,
144    /// Maximum tokens per turn
145    #[serde(default = "default_max_tokens_per_turn")]
146    pub max_tokens_per_turn: usize,
147    /// Number of recent messages to always include
148    #[serde(default = "default_preserve_recent_messages")]
149    pub preserve_recent_messages: usize,
150    /// Maximum tool descriptions to include
151    #[serde(default = "default_max_tool_descriptions")]
152    pub max_tool_descriptions: usize,
153    /// Include decision ledger summary
154    #[serde(default = "default_include_ledger")]
155    pub include_ledger: bool,
156    /// Maximum ledger entries
157    #[serde(default = "default_ledger_max_entries")]
158    pub ledger_max_entries: usize,
159    /// Include recent errors
160    #[serde(default = "default_include_recent_errors")]
161    pub include_recent_errors: bool,
162    /// Maximum recent errors to include
163    #[serde(default = "default_max_recent_errors")]
164    pub max_recent_errors: usize,
165}
166
167impl Default for ContextCurationConfig {
168    fn default() -> Self {
169        Self {
170            enabled: default_curation_enabled(),
171            max_tokens_per_turn: default_max_tokens_per_turn(),
172            preserve_recent_messages: default_preserve_recent_messages(),
173            max_tool_descriptions: default_max_tool_descriptions(),
174            include_ledger: default_include_ledger(),
175            ledger_max_entries: default_ledger_max_entries(),
176            include_recent_errors: default_include_recent_errors(),
177            max_recent_errors: default_max_recent_errors(),
178        }
179    }
180}
181
182impl ContextCurationConfig {
183    pub fn validate(&self) -> Result<()> {
184        ensure!(
185            self.max_tokens_per_turn > 0,
186            "Context curation max_tokens_per_turn must be greater than zero"
187        );
188
189        if self.include_ledger {
190            ensure!(
191                self.ledger_max_entries > 0,
192                "Context curation ledger_max_entries must be greater than zero when ledger inclusion is enabled"
193            );
194        }
195
196        if self.include_recent_errors {
197            ensure!(
198                self.max_recent_errors > 0,
199                "Context curation max_recent_errors must be greater than zero when recent errors are included"
200            );
201        }
202
203        ensure!(
204            self.max_tool_descriptions > 0,
205            "Context curation max_tool_descriptions must be greater than zero"
206        );
207
208        Ok(())
209    }
210}
211
212fn default_curation_enabled() -> bool {
213    true
214}
215fn default_max_tokens_per_turn() -> usize {
216    100_000
217}
218fn default_preserve_recent_messages() -> usize {
219    5
220}
221fn default_max_tool_descriptions() -> usize {
222    10
223}
224fn default_include_ledger() -> bool {
225    true
226}
227fn default_ledger_max_entries() -> usize {
228    12
229}
230fn default_include_recent_errors() -> bool {
231    true
232}
233fn default_max_recent_errors() -> usize {
234    3
235}
236
237#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
238#[derive(Debug, Clone, Deserialize, Serialize)]
239pub struct ContextFeaturesConfig {
240    #[serde(default)]
241    pub ledger: LedgerConfig,
242    #[serde(default)]
243    pub token_budget: TokenBudgetConfig,
244    #[serde(default)]
245    pub curation: ContextCurationConfig,
246    #[serde(default = "default_max_context_tokens")]
247    pub max_context_tokens: usize,
248    #[serde(default = "default_trim_to_percent")]
249    pub trim_to_percent: u8,
250    #[serde(default = "default_preserve_recent_turns")]
251    pub preserve_recent_turns: usize,
252}
253
254impl Default for ContextFeaturesConfig {
255    fn default() -> Self {
256        Self {
257            ledger: LedgerConfig::default(),
258            token_budget: TokenBudgetConfig::default(),
259            curation: ContextCurationConfig::default(),
260            max_context_tokens: default_max_context_tokens(),
261            trim_to_percent: default_trim_to_percent(),
262            preserve_recent_turns: default_preserve_recent_turns(),
263        }
264    }
265}
266
267impl ContextFeaturesConfig {
268    pub fn validate(&self) -> Result<()> {
269        self.ledger
270            .validate()
271            .context("Invalid ledger configuration")?;
272        self.token_budget
273            .validate()
274            .context("Invalid token_budget configuration")?;
275        self.curation
276            .validate()
277            .context("Invalid context curation configuration")?;
278
279        ensure!(
280            self.max_context_tokens > 0,
281            "Context features max_context_tokens must be greater than zero"
282        );
283        ensure!(
284            (1..=100).contains(&self.trim_to_percent),
285            "Context features trim_to_percent must be between 1 and 100"
286        );
287        ensure!(
288            self.preserve_recent_turns > 0,
289            "Context features preserve_recent_turns must be greater than zero"
290        );
291
292        Ok(())
293    }
294}
295
296fn default_max_context_tokens() -> usize {
297    context_defaults::DEFAULT_MAX_TOKENS
298}
299
300fn default_trim_to_percent() -> u8 {
301    context_defaults::DEFAULT_TRIM_TO_PERCENT
302}
303
304fn default_preserve_recent_turns() -> usize {
305    context_defaults::DEFAULT_PRESERVE_RECENT_TURNS
306}