use std::collections::HashMap;
use std::path::PathBuf;
use serde::Deserialize;
use crate::session::storage;
#[cfg(feature = "mcp")]
use crate::extras::mcp::config::McpServerConfig;
#[cfg(feature = "acp")]
use crate::extras::acp::config::AcpServerConfig;
#[derive(Debug, Clone, Deserialize)]
pub struct QuickModelConfig {
pub provider: String,
pub model: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CustomProviderConfig {
pub provider_type: String,
pub base_url: String,
pub api_key_env: Option<String>,
}
#[derive(Debug, Default, Deserialize)]
#[serde(default)]
pub struct Config {
pub model: Option<String>,
pub provider: Option<String>,
pub max_tokens: Option<u64>,
pub temperature: Option<f64>,
pub no_tools: Option<bool>,
pub no_context_files: Option<bool>,
pub context_window: Option<u64>,
pub reserve_tokens: Option<u64>,
pub keep_recent_tokens: Option<u64>,
pub max_agent_turns: Option<usize>,
pub compact_enabled: Option<bool>,
pub custom_providers: Option<HashMap<String, CustomProviderConfig>>,
pub permission: Option<serde_json::Value>,
pub restrictive: Option<bool>,
pub accept_all: Option<bool>,
pub yolo: Option<bool>,
pub sandbox: Option<bool>,
pub default_permission_mode: Option<String>,
pub show_tool_details: Option<bool>,
pub default_prompt: Option<String>,
pub shell: Option<String>,
pub editor: Option<String>,
pub api_keys: Option<std::collections::HashMap<String, String>>,
pub quick_models: Option<std::collections::HashMap<String, QuickModelConfig>>,
#[cfg(feature = "mcp")]
pub mcp_servers: Option<HashMap<String, McpServerConfig>>,
#[cfg(feature = "acp")]
pub acp_servers: Option<HashMap<String, AcpServerConfig>>,
#[cfg(feature = "acp")]
pub acp_host: Option<String>,
#[cfg(feature = "acp")]
pub acp_port: Option<u16>,
}
impl Config {
pub fn custom_providers_map(&self) -> HashMap<String, CustomProviderConfig> {
self.custom_providers.clone().unwrap_or_default()
}
pub fn resolve_context_window(&self) -> u64 {
self.context_window.unwrap_or(128_000)
}
pub fn resolve_reserve_tokens(&self) -> u64 {
self.reserve_tokens.unwrap_or(16_384)
}
pub fn resolve_keep_recent_tokens(&self) -> u64 {
self.keep_recent_tokens.unwrap_or(20_000)
}
pub fn resolve_compact_enabled(&self) -> bool {
self.compact_enabled.unwrap_or(true)
}
}
pub fn config_file_path() -> PathBuf {
storage::config_path().join("config.json")
}
pub fn quick_models_map(cfg: &Config) -> HashMap<String, QuickModelConfig> {
cfg.quick_models.clone().unwrap_or_default()
}
pub fn save_quick_model(name: &str, provider: &str, model: &str) -> std::io::Result<()> {
let path = config_file_path();
let mut value: serde_json::Value = if path.exists() {
let content = std::fs::read_to_string(&path).unwrap_or_default();
serde_json::from_str(&content).unwrap_or_default()
} else {
serde_json::json!({})
};
let entry = value
.as_object_mut()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "config is not an object"))?;
let quick_models = entry
.entry("quick_models")
.or_insert_with(|| serde_json::json!({}))
.as_object_mut()
.ok_or_else(|| std::io::Error::new(std::io::ErrorKind::InvalidData, "quick_models is not an object"))?;
quick_models.insert(
name.to_string(),
serde_json::json!({
"provider": provider,
"model": model,
}),
);
let parent = path.parent().ok_or_else(|| {
std::io::Error::new(std::io::ErrorKind::InvalidInput, "invalid config path")
})?;
std::fs::create_dir_all(parent)?;
std::fs::write(&path, serde_json::to_string_pretty(&value)?)?;
Ok(())
}
pub fn load() -> Config {
let path = config_file_path();
#[allow(unused_mut)]
let mut cfg: Config = if !path.exists() {
Config::default()
} else {
let content = std::fs::read_to_string(&path).unwrap_or_else(|e| {
eprintln!(
"error: failed to read config file ({}): {}\n\
Fix the file or remove it to use defaults.",
path.display(),
e,
);
std::process::exit(1);
});
serde_json::from_str(&content).unwrap_or_else(|e| {
eprintln!(
"error: {} is not a valid config: {}\n\
Fix the file or remove it to use defaults.",
path.display(),
e,
);
std::process::exit(1);
})
};
#[cfg(feature = "mcp")]
if cfg.mcp_servers.is_none() {
let mut headers = HashMap::new();
if let Ok(key) = std::env::var("EXA_API_KEY") {
headers.insert("x-api-key".to_string(), key);
}
let mut defaults = HashMap::new();
defaults.insert(
"Exa Web Search".to_string(),
McpServerConfig::Url {
url: "https://mcp.exa.ai/mcp".to_string(),
headers,
},
);
cfg.mcp_servers = Some(defaults);
}
cfg
}