use crate::{Error, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
pub app: AppConfig,
pub providers: HashMap<String, ProviderConfig>,
pub tools: ToolConfig,
pub storage: StorageConfig,
pub auth: AuthConfig,
pub session: SessionConfig,
pub memory: MemoryConfig,
pub agent: AgentConfig,
}
impl Default for Config {
fn default() -> Self {
Self {
app: AppConfig::default(),
providers: HashMap::new(),
tools: ToolConfig::default(),
storage: StorageConfig::default(),
auth: AuthConfig::default(),
session: SessionConfig::default(),
memory: MemoryConfig::default(),
agent: AgentConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub name: String,
pub version: String,
pub data_dir: Option<PathBuf>,
pub config_dir: Option<PathBuf>,
pub log_level: String,
pub features: FeatureConfig,
}
impl Default for AppConfig {
fn default() -> Self {
Self {
name: "code-mesh".to_string(),
version: crate::VERSION.to_string(),
data_dir: None,
config_dir: None,
log_level: "info".to_string(),
features: FeatureConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FeatureConfig {
pub compression: bool,
pub file_watching: bool,
pub advanced_crypto: bool,
pub telemetry: bool,
pub experimental: bool,
}
impl Default for FeatureConfig {
fn default() -> Self {
Self {
compression: crate::features::HAS_COMPRESSION,
file_watching: crate::features::HAS_FILE_WATCHING,
advanced_crypto: crate::features::HAS_ADVANCED_CRYPTO,
telemetry: false,
experimental: false,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderConfig {
pub id: String,
pub name: String,
pub base_url: Option<String>,
pub api_version: Option<String>,
pub default_model: Option<String>,
pub models: HashMap<String, ModelConfig>,
pub options: HashMap<String, serde_json::Value>,
pub rate_limit: RateLimitConfig,
pub timeout: TimeoutConfig,
pub retry: RetryConfig,
pub enabled: bool,
}
impl Default for ProviderConfig {
fn default() -> Self {
Self {
id: String::new(),
name: String::new(),
base_url: None,
api_version: None,
default_model: None,
models: HashMap::new(),
options: HashMap::new(),
rate_limit: RateLimitConfig::default(),
timeout: TimeoutConfig::default(),
retry: RetryConfig::default(),
enabled: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelConfig {
pub id: String,
pub name: String,
pub max_context: Option<u32>,
pub max_output: Option<u32>,
pub temperature: Option<f32>,
pub supports_tools: bool,
pub supports_vision: bool,
pub supports_streaming: bool,
pub supports_caching: bool,
pub cost: CostConfig,
pub options: HashMap<String, serde_json::Value>,
}
impl Default for ModelConfig {
fn default() -> Self {
Self {
id: String::new(),
name: String::new(),
max_context: None,
max_output: None,
temperature: None,
supports_tools: true,
supports_vision: false,
supports_streaming: true,
supports_caching: false,
cost: CostConfig::default(),
options: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CostConfig {
pub input_cost_per_1k: f64,
pub output_cost_per_1k: f64,
pub cache_read_cost_per_1k: Option<f64>,
pub cache_write_cost_per_1k: Option<f64>,
pub currency: String,
}
impl Default for CostConfig {
fn default() -> Self {
Self {
input_cost_per_1k: 0.0,
output_cost_per_1k: 0.0,
cache_read_cost_per_1k: None,
cache_write_cost_per_1k: None,
currency: "USD".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimitConfig {
pub requests_per_minute: Option<u32>,
pub tokens_per_minute: Option<u32>,
pub concurrent_requests: Option<u32>,
}
impl Default for RateLimitConfig {
fn default() -> Self {
Self {
requests_per_minute: None,
tokens_per_minute: None,
concurrent_requests: Some(4),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimeoutConfig {
pub connect: u64,
pub request: u64,
pub stream: u64,
}
impl Default for TimeoutConfig {
fn default() -> Self {
Self {
connect: 10,
request: 300,
stream: 600,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RetryConfig {
pub max_retries: u32,
pub base_delay: u64,
pub max_delay: u64,
pub backoff_multiplier: f64,
}
impl Default for RetryConfig {
fn default() -> Self {
Self {
max_retries: 3,
base_delay: 1000,
max_delay: 60000,
backoff_multiplier: 2.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolConfig {
pub enabled: Vec<String>,
pub disabled: Vec<String>,
pub tool_configs: HashMap<String, ToolSpecificConfig>,
pub default_permission: String,
pub sandbox: SandboxConfig,
}
impl Default for ToolConfig {
fn default() -> Self {
Self {
enabled: vec!["read".to_string(), "write".to_string(), "edit".to_string()],
disabled: Vec::new(),
tool_configs: HashMap::new(),
default_permission: "restricted".to_string(),
sandbox: SandboxConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolSpecificConfig {
pub permission: String,
pub allowed_patterns: Vec<String>,
pub denied_patterns: Vec<String>,
pub max_file_size: Option<u64>,
pub options: HashMap<String, serde_json::Value>,
}
impl Default for ToolSpecificConfig {
fn default() -> Self {
Self {
permission: "restricted".to_string(),
allowed_patterns: Vec::new(),
denied_patterns: Vec::new(),
max_file_size: None,
options: HashMap::new(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SandboxConfig {
pub enabled: bool,
pub allowed_dirs: Vec<PathBuf>,
pub denied_dirs: Vec<PathBuf>,
pub network_access: bool,
pub max_execution_time: u64,
pub max_memory_mb: Option<u64>,
}
impl Default for SandboxConfig {
fn default() -> Self {
Self {
enabled: true,
allowed_dirs: Vec::new(),
denied_dirs: Vec::new(),
network_access: false,
max_execution_time: 30,
max_memory_mb: Some(512),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageConfig {
pub backend: String,
pub connection: String,
pub compression: bool,
pub encryption: EncryptionConfig,
pub backup: BackupConfig,
}
impl Default for StorageConfig {
fn default() -> Self {
Self {
backend: "file".to_string(),
connection: "data/storage".to_string(),
compression: crate::features::HAS_COMPRESSION,
encryption: EncryptionConfig::default(),
backup: BackupConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncryptionConfig {
pub enabled: bool,
pub algorithm: String,
pub key_derivation: String,
}
impl Default for EncryptionConfig {
fn default() -> Self {
Self {
enabled: false,
algorithm: "aes-256-gcm".to_string(),
key_derivation: "pbkdf2".to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupConfig {
pub enabled: bool,
pub interval_hours: u64,
pub max_backups: u32,
pub backup_dir: Option<PathBuf>,
}
impl Default for BackupConfig {
fn default() -> Self {
Self {
enabled: true,
interval_hours: 24,
max_backups: 7,
backup_dir: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AuthConfig {
pub storage_backend: String,
pub refresh_interval: u64,
pub security: SecurityConfig,
}
impl Default for AuthConfig {
fn default() -> Self {
Self {
storage_backend: "encrypted_file".to_string(),
refresh_interval: 60,
security: SecurityConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SecurityConfig {
pub key_rotation: bool,
pub rotation_interval_days: u64,
pub audit_logging: bool,
}
impl Default for SecurityConfig {
fn default() -> Self {
Self {
key_rotation: true,
rotation_interval_days: 30,
audit_logging: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionConfig {
pub max_messages: u32,
pub max_age_hours: u64,
pub auto_save_interval: u64,
pub context_window: ContextWindowConfig,
}
impl Default for SessionConfig {
fn default() -> Self {
Self {
max_messages: 1000,
max_age_hours: 24 * 7, auto_save_interval: 30,
context_window: ContextWindowConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextWindowConfig {
pub max_tokens: u32,
pub trim_strategy: String,
pub preserve_system: bool,
pub preserve_recent: u32,
}
impl Default for ContextWindowConfig {
fn default() -> Self {
Self {
max_tokens: 100000,
trim_strategy: "preserve_recent".to_string(),
preserve_system: true,
preserve_recent: 10,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryConfig {
pub backend: String,
pub max_entries: u32,
pub ttl_hours: u64,
pub compression: bool,
pub indexing: IndexingConfig,
}
impl Default for MemoryConfig {
fn default() -> Self {
Self {
backend: "hybrid".to_string(),
max_entries: 10000,
ttl_hours: 24 * 30, compression: crate::features::HAS_COMPRESSION,
indexing: IndexingConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IndexingConfig {
pub semantic_search: bool,
pub full_text_search: bool,
pub embedding_model: Option<String>,
}
impl Default for IndexingConfig {
fn default() -> Self {
Self {
semantic_search: false,
full_text_search: true,
embedding_model: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AgentConfig {
pub max_concurrent: u32,
pub timeout_seconds: u64,
pub collaboration: bool,
pub default_capabilities: Vec<String>,
}
impl Default for AgentConfig {
fn default() -> Self {
Self {
max_concurrent: 4,
timeout_seconds: 300,
collaboration: true,
default_capabilities: vec![
"read".to_string(),
"write".to_string(),
"execute".to_string(),
],
}
}
}
pub struct ConfigManager {
config: Config,
config_path: Option<PathBuf>,
}
impl ConfigManager {
pub fn new() -> Self {
Self {
config: Config::default(),
config_path: None,
}
}
pub async fn load_from_file<P: Into<PathBuf>>(&mut self, path: P) -> Result<()> {
let path = path.into();
let content = std::fs::read_to_string(&path)
.map_err(|e| Error::Io(e))?;
let config: Config = match path.extension().and_then(|s| s.to_str()) {
Some("json") => serde_json::from_str(&content)?,
Some("toml") => toml::from_str(&content)
.map_err(|e| Error::Other(anyhow::anyhow!("TOML parse error: {}", e)))?,
Some("yaml") | Some("yml") => {
return Err(Error::Other(anyhow::anyhow!("YAML support not implemented")));
}
_ => return Err(Error::Other(anyhow::anyhow!("Unsupported config file format"))),
};
self.config = config;
self.config_path = Some(path);
Ok(())
}
pub fn load_from_env(&mut self) -> Result<()> {
let mut config = self.config.clone();
if let Ok(log_level) = std::env::var("CODE_MESH_LOG_LEVEL") {
config.app.log_level = log_level;
}
if let Ok(data_dir) = std::env::var("CODE_MESH_DATA_DIR") {
config.app.data_dir = Some(PathBuf::from(data_dir));
}
self.config = config;
Ok(())
}
pub async fn save_to_file<P: Into<PathBuf>>(&self, path: P) -> Result<()> {
let path = path.into();
let content = match path.extension().and_then(|s| s.to_str()) {
Some("json") => serde_json::to_string_pretty(&self.config)?,
Some("toml") => toml::to_string_pretty(&self.config)
.map_err(|e| Error::Other(anyhow::anyhow!("TOML serialize error: {}", e)))?,
_ => return Err(Error::Other(anyhow::anyhow!("Unsupported config file format"))),
};
std::fs::write(path, content)?;
Ok(())
}
pub fn config(&self) -> &Config {
&self.config
}
pub fn config_mut(&mut self) -> &mut Config {
&mut self.config
}
pub fn validate(&self) -> Result<()> {
for (id, provider) in &self.config.providers {
if provider.id != *id {
return Err(Error::Other(anyhow::anyhow!(
"Provider ID mismatch: {} != {}",
provider.id,
id
)));
}
}
if self.config.tools.default_permission.is_empty() {
return Err(Error::Other(anyhow::anyhow!(
"Default permission cannot be empty"
)));
}
Ok(())
}
pub fn get_provider_config(&self, provider_id: &str) -> Option<&ProviderConfig> {
self.config.providers.get(provider_id)
}
pub fn get_tool_config(&self, tool_id: &str) -> Option<&ToolSpecificConfig> {
self.config.tools.tool_configs.get(tool_id)
}
pub fn merge(&mut self, other: Config) -> Result<()> {
for (id, provider) in other.providers {
self.config.providers.insert(id, provider);
}
for (id, tool_config) in other.tools.tool_configs {
self.config.tools.tool_configs.insert(id, tool_config);
}
Ok(())
}
}
impl Default for ConfigManager {
fn default() -> Self {
Self::new()
}
}