Skip to main content

magi_code/config/settings/
core.rs

1use crate::config::{CustomProviderConfig, HookSettings};
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5
6use super::{
7    agent::{
8        CompactionSettings, InstructionsSettings, IntegrationsSettings, ModelsSettings,
9        SessionTitleSettings, SkillsSettings, SubagentsSettings, TuiSettings,
10    },
11    helpers::is_false,
12    providers::{ProviderStreamSettings, SelectedModelSettings, TtsrSettings},
13    services::{LspSettings, McpServersSettings},
14    tools::ToolSettings,
15};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum SettingsScope {
19    Global,
20    Project,
21}
22
23impl SettingsScope {
24    pub(crate) fn label(self) -> &'static str {
25        match self {
26            Self::Global => "Global",
27            Self::Project => "Project",
28        }
29    }
30
31    pub(crate) fn toggle(self) -> Self {
32        match self {
33            Self::Global => Self::Project,
34            Self::Project => Self::Global,
35        }
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum SettingsListKind {
41    Skills,
42    Tools,
43    Subagents,
44    Models,
45}
46
47#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
48#[serde(rename_all = "lowercase")]
49pub enum TextVerbosity {
50    #[default]
51    Low,
52    Medium,
53    High,
54}
55
56impl TextVerbosity {
57    pub(crate) fn as_api_str(self) -> &'static str {
58        match self {
59            Self::Low => "low",
60            Self::Medium => "medium",
61            Self::High => "high",
62        }
63    }
64}
65
66#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
67pub struct FastSettings {
68    #[serde(default, skip_serializing_if = "is_false")]
69    #[schemars(!skip_serializing_if, default)]
70    pub enabled: bool,
71}
72
73impl FastSettings {
74    pub(crate) fn is_default(&self) -> bool {
75        self == &Self::default()
76    }
77}
78
79#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
80pub struct OpenAiResponsesSettings {
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub text_verbosity: Option<TextVerbosity>,
83}
84
85impl OpenAiResponsesSettings {
86    pub(crate) fn is_default(&self) -> bool {
87        self == &Self::default()
88    }
89}
90
91#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
92pub struct OpenAiCodexSettings {
93    #[serde(default)]
94    pub text_verbosity: TextVerbosity,
95}
96
97impl OpenAiCodexSettings {
98    pub(crate) fn is_default(&self) -> bool {
99        self == &Self::default()
100    }
101}
102
103#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
104pub enum AnthropicCacheTtl {
105    #[default]
106    #[serde(rename = "5m")]
107    FiveMinutes,
108    #[serde(rename = "1h")]
109    OneHour,
110}
111
112pub const DEFAULT_SESSION_RETENTION_DAYS: u64 = 30;
113
114fn default_session_retention_days() -> u64 {
115    DEFAULT_SESSION_RETENTION_DAYS
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
119pub struct SessionSettings {
120    #[serde(default = "default_session_retention_days")]
121    pub retention_days: u64,
122}
123
124impl Default for SessionSettings {
125    fn default() -> Self {
126        Self {
127            retention_days: DEFAULT_SESSION_RETENTION_DAYS,
128        }
129    }
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
133pub(crate) struct AppearanceSettings {
134    #[serde(
135        default = "default_appearance_theme",
136        skip_serializing_if = "is_default_appearance_theme"
137    )]
138    pub(crate) theme: String,
139    #[serde(default, skip_serializing_if = "is_false")]
140    pub(crate) reduced_motion: bool,
141}
142
143impl Default for AppearanceSettings {
144    fn default() -> Self {
145        Self {
146            theme: crate::appearance::DEFAULT_THEME_ID.to_string(),
147            reduced_motion: false,
148        }
149    }
150}
151
152fn default_appearance_theme() -> String {
153    crate::appearance::DEFAULT_THEME_ID.to_string()
154}
155
156fn is_default_appearance_theme(theme: &String) -> bool {
157    theme == crate::appearance::DEFAULT_THEME_ID
158}
159
160#[derive(Debug, Clone, PartialEq, Eq)]
161pub struct Settings {
162    pub selected_model: SelectedModelSettings,
163    pub fast: FastSettings,
164    pub openai_codex: OpenAiCodexSettings,
165    pub openai_responses: OpenAiResponsesSettings,
166    pub no_color: Option<bool>,
167    pub anthropic_cache_ttl: Option<AnthropicCacheTtl>,
168    pub file_autocomplete_respects_gitignore: bool,
169    pub context: Option<crate::context::ContextBudget>,
170    pub session_titles: SessionTitleSettings,
171    pub summarizer: super::agent::SummarizerSettings,
172    pub compaction: CompactionSettings,
173    pub tools: ToolSettings,
174    pub subagents: SubagentsSettings,
175    pub models: ModelsSettings,
176    pub hooks: HookSettings,
177    pub instructions: InstructionsSettings,
178    pub skills: SkillsSettings,
179    pub custom_providers: BTreeMap<String, CustomProviderConfig>,
180    pub mcp_servers: McpServersSettings,
181    pub lsp: LspSettings,
182    pub integrations: IntegrationsSettings,
183    pub tui: TuiSettings,
184    pub provider_stream: ProviderStreamSettings,
185    pub ttsr: TtsrSettings,
186    pub selected_primary_agent: Option<String>,
187}
188
189impl Default for Settings {
190    fn default() -> Self {
191        Self {
192            fast: FastSettings::default(),
193
194            openai_responses: OpenAiResponsesSettings::default(),
195            selected_model: SelectedModelSettings::default(),
196            openai_codex: OpenAiCodexSettings::default(),
197            no_color: None,
198            file_autocomplete_respects_gitignore: true,
199            anthropic_cache_ttl: None,
200            context: None,
201            session_titles: SessionTitleSettings::default(),
202            summarizer: super::agent::SummarizerSettings::default(),
203            compaction: CompactionSettings::default(),
204            tools: ToolSettings::default(),
205            subagents: SubagentsSettings::default(),
206            models: ModelsSettings::default(),
207            hooks: HookSettings::default(),
208            instructions: InstructionsSettings::default(),
209            skills: SkillsSettings::default(),
210            custom_providers: BTreeMap::new(),
211            mcp_servers: BTreeMap::new(),
212            lsp: LspSettings::default(),
213            integrations: IntegrationsSettings::default(),
214            tui: TuiSettings::default(),
215            provider_stream: ProviderStreamSettings::default(),
216            ttsr: TtsrSettings::default(),
217            selected_primary_agent: None,
218        }
219    }
220}
221
222impl Settings {
223    pub(crate) fn text_verbosity_for(&self, provider: &str) -> Option<TextVerbosity> {
224        if provider == crate::providers::OPENAI_CODEX_PROVIDER {
225            return Some(
226                self.openai_responses
227                    .text_verbosity
228                    .unwrap_or(self.openai_codex.text_verbosity),
229            );
230        }
231        self.custom_providers.get(provider).and_then(|custom| {
232            (custom.use_responses_endpoint && custom.supports_text_verbosity)
233                .then_some(self.openai_responses.text_verbosity)
234                .flatten()
235        })
236    }
237}