Skip to main content

walrus_daemon/config/
mod.rs

1//! Daemon configuration loaded from TOML.
2
3pub use ::model::{ProviderConfig, ProviderManager};
4use anyhow::Result;
5use compact_str::CompactString;
6pub use default::{
7    AGENTS_DIR, DATA_DIR, SKILLS_DIR, global_config_dir, scaffold_config_dir, socket_path,
8};
9use serde::{Deserialize, Serialize};
10use std::collections::BTreeMap;
11pub use {agent::AgentConfig, loader::load_agents_dir};
12pub use {channel::ChannelConfig, mcp::McpServerConfig};
13
14mod agent;
15mod default;
16mod loader;
17mod mcp;
18
19/// Top-level daemon configuration.
20#[derive(Debug, Serialize, Deserialize)]
21pub struct DaemonConfig {
22    /// LLM provider configurations keyed by a user-defined name.
23    /// If a provider's `model` field is empty, it is filled from the key.
24    #[serde(default)]
25    pub models: BTreeMap<CompactString, ProviderConfig>,
26    /// Channel configurations keyed by a user-defined name.
27    /// If a channel's `platform` field is empty, it is filled from the key.
28    #[serde(default)]
29    pub channels: BTreeMap<CompactString, ChannelConfig>,
30    /// MCP server configurations.
31    #[serde(default)]
32    pub mcp_servers: BTreeMap<CompactString, mcp::McpServerConfig>,
33    /// Agent configurations.
34    #[serde(default)]
35    pub agents: AgentConfig,
36}
37
38impl DaemonConfig {
39    /// Parse a TOML string into a `DaemonConfig`.
40    pub fn from_toml(toml_str: &str) -> Result<Self> {
41        let mut config: Self = toml::from_str(toml_str)?;
42        config.models.iter_mut().for_each(|(key, provider)| {
43            if provider.model.is_empty() {
44                provider.model = key.clone();
45            }
46        });
47        config.channels.iter_mut().for_each(|(key, channel)| {
48            if channel.platform.is_empty() {
49                channel.platform = key.clone();
50            }
51        });
52        config.mcp_servers.iter_mut().for_each(|(name, server)| {
53            if server.name.is_empty() {
54                server.name = name.clone();
55            }
56        });
57        Ok(config)
58    }
59
60    /// Load configuration from a file path.
61    pub fn load(path: &std::path::Path) -> Result<Self> {
62        let content = std::fs::read_to_string(path)?;
63        Self::from_toml(&content)
64    }
65}
66
67#[cfg(not(feature = "local"))]
68impl Default for DaemonConfig {
69    fn default() -> Self {
70        Self {
71            models: [(
72                "deepseek-chat".into(),
73                ProviderConfig {
74                    model: "deepseek-chat".into(),
75                    api_key: None,
76                    base_url: None,
77                    loader: None,
78                    quantization: None,
79                    chat_template: None,
80                },
81            )]
82            .into(),
83            channels: Default::default(),
84            mcp_servers: Default::default(),
85            agents: AgentConfig::default(),
86        }
87    }
88}
89
90#[cfg(feature = "local")]
91impl Default for DaemonConfig {
92    fn default() -> Self {
93        Self {
94            models: [(
95                "local".into(),
96                ProviderConfig {
97                    model: "Qwen/Qwen3-4B".into(),
98                    api_key: None,
99                    base_url: None,
100                    loader: Some(model::Loader::Text),
101                    quantization: None,
102                    chat_template: None,
103                },
104            )]
105            .into(),
106            channels: Default::default(),
107            mcp_servers: Default::default(),
108            agents: AgentConfig::default(),
109        }
110    }
111}