Skip to main content

vtcode_core/core/agent/
session_config.rs

1use std::path::Path;
2
3use anyhow::Result;
4use hashbrown::HashMap;
5
6use crate::config::VTCodeConfig;
7use crate::config::loader::ConfigManager;
8use crate::config::loader::layers::ConfigLayerMetadata;
9use crate::core::agent::features::FeatureSet;
10
11/// Immutable session-scoped configuration snapshot.
12///
13/// This captures the resolved effective configuration and the winning layer for
14/// each config path at session start so downstream runtime code can treat
15/// configuration as frozen for the life of the session.
16#[derive(Debug, Clone)]
17pub struct ResolvedSessionConfig {
18    effective: VTCodeConfig,
19    features: FeatureSet,
20    origins: HashMap<String, ConfigLayerMetadata>,
21}
22
23impl ResolvedSessionConfig {
24    /// Build a session snapshot from an already-loaded configuration manager.
25    pub fn from_manager(manager: &ConfigManager) -> Self {
26        let (_, origins) = manager.layer_stack().effective_config_with_origins();
27        let effective = manager.config().clone();
28        Self {
29            features: FeatureSet::from_config(Some(&effective)),
30            effective,
31            origins,
32        }
33    }
34
35    /// Build a session snapshot from a raw config when layer metadata is not available.
36    pub fn from_config(config: VTCodeConfig) -> Self {
37        let features = FeatureSet::from_config(Some(&config));
38        Self {
39            effective: config,
40            features,
41            origins: HashMap::new(),
42        }
43    }
44
45    /// Load and resolve a session snapshot for the provided workspace.
46    pub fn load_from_workspace(workspace: impl AsRef<Path>) -> Result<Self> {
47        let manager = ConfigManager::load_from_workspace(workspace)?;
48        Ok(Self::from_manager(&manager))
49    }
50
51    /// Return the effective frozen configuration.
52    pub fn effective(&self) -> &VTCodeConfig {
53        &self.effective
54    }
55
56    /// Return the origin metadata map for resolved config paths.
57    pub fn origins(&self) -> &HashMap<String, ConfigLayerMetadata> {
58        &self.origins
59    }
60
61    /// Return the immutable session-scoped feature flags.
62    pub fn features(&self) -> &FeatureSet {
63        &self.features
64    }
65
66    /// Return the winning layer metadata for a config path, if present.
67    pub fn origin_for(&self, path: &str) -> Option<&ConfigLayerMetadata> {
68        self.origins.get(path)
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    use super::ResolvedSessionConfig;
75    use anyhow::Result;
76    use std::fs;
77    use tempfile::TempDir;
78
79    #[test]
80    fn captures_origin_metadata_from_workspace_config() -> Result<()> {
81        let temp = TempDir::new()?;
82        fs::write(
83            temp.path().join("vtcode.toml"),
84            "[agent]\nprovider = \"openai\"\n",
85        )?;
86
87        let snapshot = ResolvedSessionConfig::load_from_workspace(temp.path())?;
88
89        assert_eq!(snapshot.effective().agent.provider, "openai");
90        assert!(
91            snapshot.origin_for("agent.provider").is_some(),
92            "expected origin metadata for agent.provider"
93        );
94
95        Ok(())
96    }
97
98    #[test]
99    fn snapshot_is_immutable_after_disk_changes() -> Result<()> {
100        let temp = TempDir::new()?;
101        let config_path = temp.path().join("vtcode.toml");
102        fs::write(&config_path, "[agent]\nprovider = \"openai\"\n")?;
103
104        let snapshot = ResolvedSessionConfig::load_from_workspace(temp.path())?;
105        fs::write(&config_path, "[agent]\nprovider = \"anthropic\"\n")?;
106
107        assert_eq!(snapshot.effective().agent.provider, "openai");
108
109        let refreshed = ResolvedSessionConfig::load_from_workspace(temp.path())?;
110        assert_eq!(refreshed.effective().agent.provider, "anthropic");
111
112        Ok(())
113    }
114}