Files
zesdex/apps/domain/src/cms/app_config.rs
T

128 lines
4.7 KiB
Rust
Raw Normal View History

//! Pure domain entity for application configuration.
//!
//! Defines `AppConfig`, `ProviderConfig`, and `ModelRole` — the data
//! structures that describe which LLM providers are registered, which
//! model roles exist, and which provider/model is the default.
//!
//! # Architecture
//! These are pure data structures with **no I/O logic**. Load/save
//! responsibilities live in `AppConfigRepository` (domain::repository).
//!
//! ## Data Flow
//! 1. `AppConfig` is deserialised from `app_config.json` at startup
//! 2. The HTTP handler layer calls `SettingsService::update_provider()`
//! to mutate the provider map
//! 3. The modified `AppConfig` is serialised back to `app_config.json`
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
/// Top-level application configuration.
///
/// Holds the registry of configured LLM providers, named model roles
/// (logical profiles mapping to a provider+model pair), and the default
/// provider/model selection.
///
/// ## Fields
/// - `providers` — map of provider name → connection details
/// - `model_roles` — map of role name → provider/model/temperature
/// - `default_provider` — the provider to use when none is specified
/// - `default_model` — the model to use when none is specified
/// - `default_context_window` — fallback context window size in tokens
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AppConfig {
pub providers: HashMap<String, ProviderConfig>,
pub model_roles: HashMap<String, ModelRole>,
pub default_provider: String,
pub default_model: String,
pub default_context_window: u32,
}
/// Connection details for a single LLM provider endpoint.
///
/// ## Fields
/// - `api_base` — base URL for the provider API
/// - `api_key_env` — optional environment variable name holding the API key
/// - `default_model` — optional default model name for this provider
/// - `default_api_key` — optional inline API key (less secure than env var)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProviderConfig {
pub api_base: String,
pub api_key_env: Option<String>,
pub default_model: Option<String>,
pub default_api_key: Option<String>,
}
/// A named model role mapping to a specific provider/model with parameters.
///
/// Roles allow the UI to present logical profiles (e.g. "fast", "reasoning")
/// that abstract over concrete provider+model strings.
///
/// ## Fields
/// - `provider` — which provider serves this role
/// - `model` — which model to use for this role
/// - `max_tokens` — optional maximum output token limit
/// - `context_window` — optional context window override
/// - `temperature` — optional generation temperature
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModelRole {
pub provider: String,
pub model: String,
pub max_tokens: Option<u32>,
pub context_window: Option<u32>,
pub temperature: Option<f32>,
}
/// Returns the default AppConfig with built-in "zen" and "router" providers.
impl Default for AppConfig {
/// Construct an AppConfig with the default "zen" and "router" providers.
///
/// ## Defaults
/// - Zen provider: `deepseek-v4-flash-free` model
/// - Router provider: `claude-opus-5` model
/// - Default role: "default" → zen / deepseek-v4-flash-free, temp 0.7
/// - `default_context_window`: 256,000 tokens
fn default() -> Self {
let mut providers = HashMap::new();
providers.insert(
"zen".to_string(),
ProviderConfig {
api_base: "https://opencode.ai/zen/v1".to_string(),
api_key_env: Some("API_KEY".to_string()),
default_model: Some("deepseek-v4-flash-free".to_string()),
default_api_key: None,
},
);
providers.insert(
"router".to_string(),
ProviderConfig {
api_base: "https://9router.asepharyana.my.id/v1".to_string(),
api_key_env: Some("ROUTER_API_KEY".to_string()),
default_model: Some("claude-opus-5".to_string()),
default_api_key: None,
},
);
let mut model_roles = HashMap::new();
model_roles.insert(
"default".to_string(),
ModelRole {
provider: "zen".to_string(),
model: "deepseek-v4-flash-free".to_string(),
max_tokens: None,
context_window: None,
temperature: Some(0.7),
},
);
Self {
providers,
model_roles,
default_provider: "zen".to_string(),
default_model: "deepseek-v4-flash-free".to_string(),
default_context_window: 256_000,
}
}
}