2026-07-19 17:05:27 +07:00
|
|
|
//! 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.
|
2026-07-16 12:32:17 +07:00
|
|
|
//!
|
|
|
|
|
//! # Architecture
|
2026-07-19 17:05:27 +07:00
|
|
|
//! 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`
|
2026-07-16 12:32:17 +07:00
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
|
2026-07-19 17:05:27 +07:00
|
|
|
/// 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
|
2026-07-16 12:32:17 +07:00
|
|
|
#[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,
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 17:05:27 +07:00
|
|
|
/// 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)
|
2026-07-16 12:32:17 +07:00
|
|
|
#[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>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 17:05:27 +07:00
|
|
|
/// 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
|
2026-07-16 12:32:17 +07:00
|
|
|
#[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>,
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-19 17:05:27 +07:00
|
|
|
/// Returns the default AppConfig with built-in "zen" and "router" providers.
|
2026-07-16 12:32:17 +07:00
|
|
|
impl Default for AppConfig {
|
2026-07-19 17:05:27 +07:00
|
|
|
/// Construct an AppConfig with the default "zen" and "router" providers.
|
|
|
|
|
///
|
|
|
|
|
/// ## Defaults
|
|
|
|
|
/// - Zen provider: `deepseek-v4-flash-free` model
|
|
|
|
|
/// - Router provider: `claude-opus-4-8` model
|
|
|
|
|
/// - Default role: "default" → zen / deepseek-v4-flash-free, temp 0.7
|
|
|
|
|
/// - `default_context_window`: 256,000 tokens
|
2026-07-16 12:32:17 +07:00
|
|
|
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-4-8".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,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|