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}