Download crates/forge_infra/src/env.rs from SaylorTwift/forgecode: direct link, hf CLI and curl.
- Browser
- Download file 9.82 kB
-
https://huggingface.co/SaylorTwift/forgecode/resolve/main/crates/forge_infra/src/env.rs
- Command line
-
hf download hf://SaylorTwift/forgecode/crates/forge_infra/src/env.rs
-
curl -L -o env.rs https://huggingface.co/SaylorTwift/forgecode/resolve/main/crates/forge_infra/src/env.rs
9.82 kB
| use std::collections::BTreeMap; | |
| use std::path::PathBuf; | |
| use std::sync::Arc; | |
| use forge_app::EnvironmentInfra; | |
| use forge_config::{ConfigReader, ForgeConfig, ModelConfig}; | |
| use forge_domain::{ConfigOperation, Environment}; | |
| use tracing::debug; | |
| /// Builds a [`forge_domain::Environment`] from runtime context only. | |
| /// | |
| /// Only the five fields that cannot be sourced from [`ForgeConfig`] are set | |
| /// here: `os`, `cwd`, `home`, `shell`, and `base_path`. All configuration | |
| /// values are now accessed through `EnvironmentInfra::get_config()`. | |
| pub fn to_environment(cwd: PathBuf) -> Environment { | |
| Environment { | |
| os: std::env::consts::OS.to_string(), | |
| cwd, | |
| home: dirs::home_dir(), | |
| shell: if cfg!(target_os = "windows") { | |
| std::env::var("COMSPEC").unwrap_or_else(|_| "cmd.exe".to_string()) | |
| } else { | |
| std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string()) | |
| }, | |
| base_path: ConfigReader::base_path(), | |
| } | |
| } | |
| /// Applies a single [`ConfigOperation`] directly to a [`ForgeConfig`]. | |
| /// | |
| /// Used by [`ForgeEnvironmentInfra::update_environment`] to mutate the | |
| /// persisted config without an intermediate `Environment` round-trip. | |
| fn apply_config_op(fc: &mut ForgeConfig, op: ConfigOperation) { | |
| match op { | |
| ConfigOperation::SetSessionConfig(mc) => { | |
| let pid_str = mc.provider.as_ref().to_string(); | |
| let mid_str = mc.model.to_string(); | |
| fc.session = Some(ModelConfig { provider_id: pid_str, model_id: mid_str }); | |
| } | |
| ConfigOperation::SetCommitConfig(mc) => { | |
| fc.commit = mc.map(|m| ModelConfig { | |
| provider_id: m.provider.as_ref().to_string(), | |
| model_id: m.model.to_string(), | |
| }); | |
| } | |
| ConfigOperation::SetSuggestConfig(mc) => { | |
| fc.suggest = Some(ModelConfig { | |
| provider_id: mc.provider.as_ref().to_string(), | |
| model_id: mc.model.to_string(), | |
| }); | |
| } | |
| ConfigOperation::SetReasoningEffort(effort) => { | |
| let config_effort = match effort { | |
| forge_domain::Effort::None => forge_config::Effort::None, | |
| forge_domain::Effort::Minimal => forge_config::Effort::Minimal, | |
| forge_domain::Effort::Low => forge_config::Effort::Low, | |
| forge_domain::Effort::Medium => forge_config::Effort::Medium, | |
| forge_domain::Effort::High => forge_config::Effort::High, | |
| forge_domain::Effort::XHigh => forge_config::Effort::XHigh, | |
| forge_domain::Effort::Max => forge_config::Effort::Max, | |
| }; | |
| let reasoning = fc | |
| .reasoning | |
| .get_or_insert_with(forge_config::ReasoningConfig::default); | |
| reasoning.effort = Some(config_effort); | |
| } | |
| } | |
| } | |
| /// Infrastructure implementation for managing application configuration with | |
| /// caching support. | |
| /// | |
| /// Uses [`ForgeConfig::read`] and [`ForgeConfig::write`] for all file I/O and | |
| /// maintains an in-memory cache to reduce disk access. Also handles | |
| /// environment variable discovery via `.env` files and OS APIs. | |
| pub struct ForgeEnvironmentInfra { | |
| cwd: PathBuf, | |
| cache: Arc<std::sync::Mutex<Option<ForgeConfig>>>, | |
| } | |
| impl ForgeEnvironmentInfra { | |
| /// Creates a new [`ForgeEnvironmentInfra`] with the given pre-read config. | |
| /// | |
| /// The cache is pre-seeded with `config` so no disk I/O occurs on the | |
| /// first [`EnvironmentInfra::get_config`] call. | |
| /// | |
| /// # Arguments | |
| /// * `cwd` - The working directory path; used to resolve `.env` files | |
| /// * `config` - The pre-read [`ForgeConfig`] to seed the in-memory cache | |
| pub fn new(cwd: PathBuf, config: ForgeConfig) -> Self { | |
| Self { cwd, cache: Arc::new(std::sync::Mutex::new(Some(config))) } | |
| } | |
| /// Returns the cached [`ForgeConfig`], re-reading from disk if the cache | |
| /// has been invalidated by [`Self::update_environment`]. | |
| /// | |
| /// # Errors | |
| /// | |
| /// Returns an error if the cache is empty and the disk read fails. | |
| pub fn cached_config(&self) -> anyhow::Result<ForgeConfig> { | |
| let mut cache = self.cache.lock().expect("cache mutex poisoned"); | |
| if let Some(ref config) = *cache { | |
| Ok(config.clone()) | |
| } else { | |
| let config = ConfigReader::default() | |
| .read_defaults() | |
| .read_global() | |
| .read_env() | |
| .build()?; | |
| *cache = Some(config.clone()); | |
| Ok(config) | |
| } | |
| } | |
| } | |
| impl EnvironmentInfra for ForgeEnvironmentInfra { | |
| type Config = ForgeConfig; | |
| fn get_env_var(&self, key: &str) -> Option<String> { | |
| std::env::var(key).ok() | |
| } | |
| fn get_env_vars(&self) -> BTreeMap<String, String> { | |
| std::env::vars().collect() | |
| } | |
| fn get_environment(&self) -> Environment { | |
| to_environment(self.cwd.clone()) | |
| } | |
| fn get_config(&self) -> anyhow::Result<ForgeConfig> { | |
| self.cached_config() | |
| } | |
| async fn update_environment(&self, ops: Vec<ConfigOperation>) -> anyhow::Result<()> { | |
| // Load the global config (with defaults applied) for the update round-trip | |
| let mut fc = ConfigReader::default() | |
| .read_defaults() | |
| .read_global() | |
| .build()?; | |
| debug!(config = ?fc, ?ops, "applying app config operations"); | |
| for op in ops { | |
| apply_config_op(&mut fc, op); | |
| } | |
| fc.write()?; | |
| debug!(config = ?fc, "written .forge.toml"); | |
| // Reset cache so next get_config() re-reads the updated values from disk | |
| *self.cache.lock().expect("cache mutex poisoned") = None; | |
| Ok(()) | |
| } | |
| } | |
| mod tests { | |
| use std::path::PathBuf; | |
| use forge_config::ForgeConfig; | |
| use pretty_assertions::assert_eq; | |
| use super::*; | |
| fn test_to_environment_sets_cwd() { | |
| let fixture_cwd = PathBuf::from("/test/cwd"); | |
| let actual = to_environment(fixture_cwd.clone()); | |
| assert_eq!(actual.cwd, fixture_cwd); | |
| } | |
| fn test_to_environment_base_path_is_stable_after_env_var_change() { | |
| let fixture_cwd = PathBuf::from("/any/cwd"); | |
| let expected = to_environment(fixture_cwd.clone()).base_path; | |
| let previous = std::env::var("FORGE_CONFIG").ok(); | |
| unsafe { std::env::set_var("FORGE_CONFIG", "/custom/config/dir") }; | |
| let actual = to_environment(fixture_cwd).base_path; | |
| if let Some(value) = previous { | |
| unsafe { std::env::set_var("FORGE_CONFIG", value) }; | |
| } else { | |
| unsafe { std::env::remove_var("FORGE_CONFIG") }; | |
| } | |
| assert_eq!(actual, expected); | |
| } | |
| fn test_to_environment_falls_back_to_home_dir_when_env_var_absent() { | |
| let actual = to_environment(PathBuf::from("/any/cwd")); | |
| // Without FORGE_CONFIG the base_path must be either ".forge" (new default) | |
| // or "forge" (legacy fallback when ~/forge exists on this machine). | |
| let name = actual.base_path.file_name().unwrap(); | |
| assert!( | |
| name == ".forge" || name == "forge", | |
| "Expected base_path to end with '.forge' or 'forge', got: {:?}", | |
| name | |
| ); | |
| } | |
| fn test_apply_config_op_set_model() { | |
| use forge_domain::{ModelConfig as DomainModelConfig, ModelId, ProviderId}; | |
| let mut fixture = ForgeConfig::default(); | |
| apply_config_op( | |
| &mut fixture, | |
| ConfigOperation::SetSessionConfig(DomainModelConfig::new( | |
| ProviderId::ANTHROPIC, | |
| ModelId::new("claude-3-5-sonnet"), | |
| )), | |
| ); | |
| let actual_provider = fixture.session.as_ref().map(|s| s.provider_id.as_str()); | |
| let actual_model = fixture.session.as_ref().map(|s| s.model_id.as_str()); | |
| assert_eq!(actual_provider, Some("anthropic")); | |
| assert_eq!(actual_model, Some("claude-3-5-sonnet")); | |
| } | |
| fn test_apply_config_op_set_session_config_replaces_existing() { | |
| use forge_config::ModelConfig as ForgeCfgModelConfig; | |
| use forge_domain::{ModelConfig as DomainModelConfig, ModelId, ProviderId}; | |
| let mut fixture = ForgeConfig { | |
| session: Some(ForgeCfgModelConfig { | |
| provider_id: "openai".to_string(), | |
| model_id: "gpt-4".to_string(), | |
| }), | |
| ..Default::default() | |
| }; | |
| apply_config_op( | |
| &mut fixture, | |
| ConfigOperation::SetSessionConfig(DomainModelConfig::new( | |
| ProviderId::ANTHROPIC, | |
| ModelId::new("claude-3-5-sonnet-20241022"), | |
| )), | |
| ); | |
| let actual_provider = fixture.session.as_ref().map(|s| s.provider_id.as_str()); | |
| let actual_model = fixture.session.as_ref().map(|s| s.model_id.as_str()); | |
| assert_eq!(actual_provider, Some("anthropic")); | |
| assert_eq!(actual_model, Some("claude-3-5-sonnet-20241022")); | |
| } | |
| fn test_apply_config_op_set_session_config_creates_new_session() { | |
| use forge_domain::{ModelConfig as DomainModelConfig, ModelId, ProviderId}; | |
| let mut fixture = ForgeConfig::default(); | |
| apply_config_op( | |
| &mut fixture, | |
| ConfigOperation::SetSessionConfig(DomainModelConfig::new( | |
| ProviderId::ANTHROPIC, | |
| ModelId::new("claude-3-5-sonnet-20241022"), | |
| )), | |
| ); | |
| let actual_provider = fixture.session.as_ref().map(|s| s.provider_id.as_str()); | |
| let actual_model = fixture.session.as_ref().map(|s| s.model_id.as_str()); | |
| assert_eq!(actual_provider, Some("anthropic")); | |
| assert_eq!(actual_model, Some("claude-3-5-sonnet-20241022")); | |
| } | |
| } | |