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    /// Alert threshold (0.0-1.0)
70    #[serde(default = "default_alert_threshold")]
71    pub alert_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            alert_threshold: default_alert_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.alert_threshold),
98            "Token budget alert_threshold must be between 0.0 and 1.0"
99        );
100        ensure!(
101            self.warning_threshold <= self.alert_threshold,
102            "Token budget warning_threshold must be less than or equal to alert_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_alert_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 ContextFeaturesConfig {
141    #[serde(default)]
142    pub ledger: LedgerConfig,
143    #[serde(default)]
144    pub token_budget: TokenBudgetConfig,
145    #[serde(default = "default_max_context_tokens")]
146    pub max_context_tokens: usize,
147    #[serde(default = "default_trim_to_percent")]
148    pub trim_to_percent: u8,
149    #[serde(default = "default_preserve_recent_turns")]
150    pub preserve_recent_turns: usize,
151    #[serde(default = "default_semantic_compression_enabled")]
152    pub semantic_compression: bool,
153    #[serde(default = "default_tool_aware_retention_enabled")]
154    pub tool_aware_retention: bool,
155    #[serde(default = "default_max_structural_depth")]
156    pub max_structural_depth: usize,
157    #[serde(default = "default_preserve_recent_tools")]
158    pub preserve_recent_tools: usize,
159}
160
161impl Default for ContextFeaturesConfig {
162    fn default() -> Self {
163        Self {
164            ledger: LedgerConfig::default(),
165            token_budget: TokenBudgetConfig::default(),
166            max_context_tokens: default_max_context_tokens(),
167            trim_to_percent: default_trim_to_percent(),
168            preserve_recent_turns: default_preserve_recent_turns(),
169            semantic_compression: default_semantic_compression_enabled(),
170            tool_aware_retention: default_tool_aware_retention_enabled(),
171            max_structural_depth: default_max_structural_depth(),
172            preserve_recent_tools: default_preserve_recent_tools(),
173        }
174    }
175}
176
177impl ContextFeaturesConfig {
178    pub fn validate(&self) -> Result<()> {
179        self.ledger
180            .validate()
181            .context("Invalid ledger configuration")?;
182        self.token_budget
183            .validate()
184            .context("Invalid token_budget configuration")?;
185
186        ensure!(
187            self.max_context_tokens > 0,
188            "Context features max_context_tokens must be greater than zero"
189        );
190        ensure!(
191            (1..=100).contains(&self.trim_to_percent),
192            "Context features trim_to_percent must be between 1 and 100"
193        );
194        ensure!(
195            self.preserve_recent_turns > 0,
196            "Context features preserve_recent_turns must be greater than zero"
197        );
198
199        ensure!(
200            (context_defaults::MIN_STRUCTURAL_DEPTH..=context_defaults::MAX_STRUCTURAL_DEPTH)
201                .contains(&self.max_structural_depth),
202            "Context features max_structural_depth must be between {} and {}",
203            context_defaults::MIN_STRUCTURAL_DEPTH,
204            context_defaults::MAX_STRUCTURAL_DEPTH,
205        );
206
207        ensure!(
208            (context_defaults::MIN_PRESERVE_RECENT_TOOLS
209                ..=context_defaults::MAX_PRESERVE_RECENT_TOOLS)
210                .contains(&self.preserve_recent_tools),
211            "Context features preserve_recent_tools must be between {} and {}",
212            context_defaults::MIN_PRESERVE_RECENT_TOOLS,
213            context_defaults::MAX_PRESERVE_RECENT_TOOLS,
214        );
215
216        Ok(())
217    }
218}
219
220fn default_max_context_tokens() -> usize {
221    context_defaults::DEFAULT_MAX_TOKENS
222}
223
224fn default_trim_to_percent() -> u8 {
225    context_defaults::DEFAULT_TRIM_TO_PERCENT
226}
227
228fn default_preserve_recent_turns() -> usize {
229    context_defaults::DEFAULT_PRESERVE_RECENT_TURNS
230}
231
232fn default_semantic_compression_enabled() -> bool {
233    context_defaults::DEFAULT_SEMANTIC_COMPRESSION_ENABLED
234}
235
236fn default_tool_aware_retention_enabled() -> bool {
237    context_defaults::DEFAULT_TOOL_AWARE_RETENTION_ENABLED
238}
239
240fn default_max_structural_depth() -> usize {
241    context_defaults::DEFAULT_MAX_STRUCTURAL_DEPTH
242}
243
244fn default_preserve_recent_tools() -> usize {
245    context_defaults::DEFAULT_PRESERVE_RECENT_TOOLS
246}