use std::path::PathBuf;
use std::fs;
use anyhow::{Result, Context};
pub fn load_env_file() -> Result<()> {
if let Ok(current_dir) = std::env::current_dir() {
let user_env_path = current_dir.join(".env");
if user_env_path.exists() {
dotenv::from_path(&user_env_path)?;
return Ok(());
}
}
if let Ok(package_dir) = get_package_directory() {
let package_env_path = package_dir.join(".env");
if package_env_path.exists() {
dotenv::from_path(&package_env_path)?;
}
}
Ok(())
}
fn get_package_directory() -> Result<PathBuf> {
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
return Ok(PathBuf::from(manifest_dir));
}
if let Ok(exe_path) = std::env::current_exe() {
if let Some(parent) = exe_path.parent() {
return Ok(parent.to_path_buf());
}
}
std::env::current_dir().context("无法获取包目录")
}
pub fn get_deepseek_api_key() -> Result<String> {
std::env::var("DEEPSEEK_API_KEY")
.context("DEEPSEEK_API_KEY环境变量未设置")
}
pub fn get_deepseek_api_endpoint() -> String {
std::env::var("DEEPSEEK_API_ENDPOINT")
.unwrap_or_else(|_| "https://api.deepseek.com".to_string())
}
pub fn get_openai_api_key() -> Result<String> {
std::env::var("OPENAI_API_KEY")
.context("OPENAI_API_KEY环境变量未设置")
}
pub fn get_openai_api_endpoint() -> String {
std::env::var("OPENAI_BASE_URL")
.unwrap_or_else(|_| "https://api.openai.com/v1".to_string())
}
pub fn get_openai_model() -> String {
std::env::var("OPENAI_MODEL")
.unwrap_or_else(|_| "gpt-4o".to_string())
}
pub fn get_sandbox() -> String {
std::env::var("SANDBOX")
.unwrap_or_else(|_| String::new())
}
pub fn get_session_id() -> String {
std::env::var("SESSION_ID")
.unwrap_or_else(|_| {
use uuid::Uuid;
Uuid::new_v4().to_string()
})
}
pub fn get_gemini_system_md() -> Option<String> {
std::env::var("GEMINI_SYSTEM_MD").ok()
}
pub fn get_gemini_write_system_md() -> Option<String> {
std::env::var("GEMINI_WRITE_SYSTEM_MD").ok()
}
pub fn is_debug_enabled() -> bool {
std::env::var("DEBUG")
.map(|v| v.to_lowercase() == "true" || v == "1")
.unwrap_or(false)
}
pub fn get_log_level() -> String {
std::env::var("LOG_LEVEL")
.unwrap_or_else(|_| "info".to_string())
}
pub fn get_max_retries() -> usize {
std::env::var("MAX_RETRIES")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(3)
}
pub fn get_request_timeout() -> u64 {
std::env::var("REQUEST_TIMEOUT")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(30)
}
pub fn get_max_tokens() -> usize {
std::env::var("MAX_TOKENS")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(4096)
}
pub fn get_temperature() -> f64 {
std::env::var("TEMPERATURE")
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(0.7)
}
pub fn get_mcp_config_dir() -> PathBuf {
std::env::var("MCP_CONFIG_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
pub fn get_mcp_config_path() -> PathBuf {
get_mcp_config_dir().join("mcp.json")
}
pub fn get_workspace_root() -> PathBuf {
std::env::var("WORKSPACE_ROOT")
.map(PathBuf::from)
.unwrap_or_else(|_| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")))
}
pub fn get_allowed_directories() -> Vec<PathBuf> {
std::env::var("ALLOWED_DIRECTORIES")
.map(|v| {
v.split(',')
.map(|s| PathBuf::from(s.trim()))
.collect()
})
.unwrap_or_else(|_| vec![get_workspace_root()])
}
pub fn is_sandbox_mode() -> bool {
std::env::var("SANDBOX_MODE")
.map(|v| v.to_lowercase() == "true" || v == "1")
.unwrap_or(false)
}
pub fn get_user_agent() -> String {
std::env::var("USER_AGENT")
.unwrap_or_else(|_| "Alou-Rust/0.1.0".to_string())
}
pub fn get_proxy_settings() -> (Option<String>, Option<String>) {
let http_proxy = std::env::var("HTTP_PROXY").ok();
let https_proxy = std::env::var("HTTPS_PROXY").ok();
(http_proxy, https_proxy)
}
pub fn get_cache_dir() -> PathBuf {
std::env::var("CACHE_DIR")
.map(PathBuf::from)
.unwrap_or_else(|_| {
std::env::temp_dir().join("alou-cache")
})
}
pub fn get_log_file() -> PathBuf {
std::env::var("LOG_FILE")
.map(PathBuf::from)
.unwrap_or_else(|_| {
get_cache_dir().join("alou.log")
})
}
pub fn init_env_config() -> Result<()> {
load_env_file()?;
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", &get_log_level());
}
if std::env::var("RUST_BACKTRACE").is_err() && is_debug_enabled() {
std::env::set_var("RUST_BACKTRACE", "1");
}
Ok(())
}
pub fn validate_required_env() -> Result<()> {
if get_deepseek_api_key().is_err() && get_openai_api_key().is_err() {
return Err(anyhow::anyhow!(
"必须设置DEEPSEEK_API_KEY或OPENAI_API_KEY环境变量"
));
}
let workspace_root = get_workspace_root();
if !workspace_root.exists() {
return Err(anyhow::anyhow!(
"工作区根目录不存在: {}",
workspace_root.display()
));
}
let mcp_config_path = get_mcp_config_path();
if !mcp_config_path.exists() {
create_default_mcp_config()?;
}
Ok(())
}
fn create_default_mcp_config() -> Result<()> {
let mcp_config_path = get_mcp_config_path();
if let Some(parent) = mcp_config_path.parent() {
fs::create_dir_all(parent)?;
}
let default_config = r#"{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
"env": {}
},
"fetch": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-fetch"],
"env": {}
},
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"],
"env": {}
}
}
}"#;
fs::write(&mcp_config_path, default_config)?;
Ok(())
}
pub fn get_env_summary() -> std::collections::HashMap<String, String> {
let mut summary = std::collections::HashMap::new();
summary.insert("DEEPSEEK_API_KEY".to_string(),
get_deepseek_api_key().map(|_| "***".to_string()).unwrap_or_else(|_| "未设置".to_string()));
summary.insert("DEEPSEEK_API_ENDPOINT".to_string(), get_deepseek_api_endpoint());
summary.insert("OPENAI_API_KEY".to_string(),
get_openai_api_key().map(|_| "***".to_string()).unwrap_or_else(|_| "未设置".to_string()));
summary.insert("OPENAI_BASE_URL".to_string(), get_openai_api_endpoint());
summary.insert("OPENAI_MODEL".to_string(), get_openai_model());
summary.insert("SANDBOX".to_string(), get_sandbox());
summary.insert("SESSION_ID".to_string(), get_session_id());
summary.insert("DEBUG".to_string(), is_debug_enabled().to_string());
summary.insert("LOG_LEVEL".to_string(), get_log_level());
summary.insert("MAX_RETRIES".to_string(), get_max_retries().to_string());
summary.insert("REQUEST_TIMEOUT".to_string(), get_request_timeout().to_string());
summary.insert("MAX_TOKENS".to_string(), get_max_tokens().to_string());
summary.insert("TEMPERATURE".to_string(), get_temperature().to_string());
summary.insert("WORKSPACE_ROOT".to_string(), get_workspace_root().to_string_lossy().to_string());
summary.insert("MCP_CONFIG_PATH".to_string(), get_mcp_config_path().to_string_lossy().to_string());
summary.insert("CACHE_DIR".to_string(), get_cache_dir().to_string_lossy().to_string());
summary.insert("LOG_FILE".to_string(), get_log_file().to_string_lossy().to_string());
summary
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
#[test]
fn test_get_deepseek_api_endpoint() {
env::set_var("DEEPSEEK_API_ENDPOINT", "https://custom.deepseek.com");
assert_eq!(get_deepseek_api_endpoint(), "https://custom.deepseek.com");
env::remove_var("DEEPSEEK_API_ENDPOINT");
assert_eq!(get_deepseek_api_endpoint(), "https://api.deepseek.com");
}
#[test]
fn test_get_openai_model() {
env::set_var("OPENAI_MODEL", "gpt-4");
assert_eq!(get_openai_model(), "gpt-4");
env::remove_var("OPENAI_MODEL");
assert_eq!(get_openai_model(), "gpt-4o");
}
#[test]
fn test_is_debug_enabled() {
env::set_var("DEBUG", "true");
assert!(is_debug_enabled());
env::set_var("DEBUG", "1");
assert!(is_debug_enabled());
env::set_var("DEBUG", "false");
assert!(!is_debug_enabled());
env::remove_var("DEBUG");
assert!(!is_debug_enabled());
}
#[test]
fn test_get_max_retries() {
env::set_var("MAX_RETRIES", "5");
assert_eq!(get_max_retries(), 5);
env::remove_var("MAX_RETRIES");
assert_eq!(get_max_retries(), 3);
}
#[test]
fn test_get_temperature() {
env::set_var("TEMPERATURE", "0.5");
assert_eq!(get_temperature(), 0.5);
env::remove_var("TEMPERATURE");
assert_eq!(get_temperature(), 0.7);
}
#[test]
fn test_get_allowed_directories() {
env::set_var("ALLOWED_DIRECTORIES", "/tmp,/var/tmp");
let dirs = get_allowed_directories();
assert_eq!(dirs.len(), 2);
assert!(dirs.contains(&PathBuf::from("/tmp")));
assert!(dirs.contains(&PathBuf::from("/var/tmp")));
env::remove_var("ALLOWED_DIRECTORIES");
let dirs = get_allowed_directories();
assert_eq!(dirs.len(), 1);
}
#[test]
fn test_get_proxy_settings() {
env::set_var("HTTP_PROXY", "http://proxy.example.com:8080");
env::set_var("HTTPS_PROXY", "https://proxy.example.com:8080");
let (http_proxy, https_proxy) = get_proxy_settings();
assert_eq!(http_proxy, Some("http://proxy.example.com:8080".to_string()));
assert_eq!(https_proxy, Some("https://proxy.example.com:8080".to_string()));
env::remove_var("HTTP_PROXY");
env::remove_var("HTTPS_PROXY");
}
}