1use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Deserialize, Serialize)]
10pub struct AppConfig {
11 #[serde(default)]
13 pub providers: HashMap<String, ProviderConfig>,
14 #[serde(default)]
16 pub cascades: HashMap<String, CascadeConfig>,
17 #[serde(default)]
19 pub database: DatabaseConfig,
20 #[serde(default)]
22 pub failure_persistence: FailureConfig,
23}
24
25#[derive(Debug, Deserialize, Serialize, Clone)]
27pub struct ProviderConfig {
28 pub r#type: ProviderType,
30 #[serde(default)]
32 pub api_key_service: Option<String>,
33 #[serde(default)]
35 pub api_key_env: Option<String>,
36 #[serde(default)]
38 pub base_url: Option<String>,
39}
40
41#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq)]
43#[serde(rename_all = "lowercase")]
44pub enum ProviderType {
45 Openai,
47 Anthropic,
49 Gemini,
51 Ollama,
53}
54
55#[derive(Debug, Deserialize, Serialize, Clone)]
57pub struct CascadeEntry {
58 pub provider: String,
60 pub model: String,
62}
63
64#[derive(Debug, Deserialize, Serialize)]
66pub struct CascadeConfig {
67 pub entries: Vec<CascadeEntry>,
69}
70
71#[derive(Debug, Deserialize, Serialize)]
73pub struct DatabaseConfig {
74 #[serde(default = "default_db_path")]
76 pub path: String,
77}
78
79fn default_db_path() -> String {
80 "~/.local/share/llm-cascade/db.sqlite".to_string()
81}
82
83impl Default for DatabaseConfig {
84 fn default() -> Self {
85 Self {
86 path: default_db_path(),
87 }
88 }
89}
90
91#[derive(Debug, Deserialize, Serialize)]
93pub struct FailureConfig {
94 #[serde(default = "default_failure_dir")]
96 pub dir: String,
97}
98
99fn default_failure_dir() -> String {
100 "~/.local/share/llm-cascade/failed_prompts".to_string()
101}
102
103impl Default for FailureConfig {
104 fn default() -> Self {
105 Self {
106 dir: default_failure_dir(),
107 }
108 }
109}
110
111pub fn expand_tilde(path: &str) -> PathBuf {
113 if let Some(rest) = path.strip_prefix("~/")
114 && let Some(home) = dirs_home()
115 {
116 return home.join(rest);
117 }
118 PathBuf::from(path)
119}
120
121fn dirs_home() -> Option<PathBuf> {
122 std::env::var("HOME").ok().map(PathBuf::from)
123}
124
125pub fn load_config(path: &Path) -> Result<AppConfig, String> {
127 let content = std::fs::read_to_string(path)
128 .map_err(|e| format!("Failed to read config file '{}': {}", path.display(), e))?;
129 let config: AppConfig = toml::from_str(&content)
130 .map_err(|e| format!("Failed to parse config file '{}': {}", path.display(), e))?;
131 Ok(config)
132}
133
134pub fn default_config_path() -> PathBuf {
136 let config_dir = std::env::var("XDG_CONFIG_HOME")
137 .map(PathBuf::from)
138 .unwrap_or_else(|_| expand_tilde("~/.config"));
139 config_dir.join("llm-cascade").join("config.toml")
140}