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 #[serde(default, skip_serializing_if = "is_false")]
97 pub experimental_reasoning_updates: bool,
98}
99
100impl OpenAiCodexSettings {
101 pub(crate) fn is_default(&self) -> bool {
102 self == &Self::default()
103 }
104}
105
106#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
107pub enum AnthropicCacheTtl {
108 #[default]
109 #[serde(rename = "5m")]
110 FiveMinutes,
111 #[serde(rename = "1h")]
112 OneHour,
113}
114
115pub const DEFAULT_SESSION_RETENTION_DAYS: u64 = 30;
116
117fn default_session_retention_days() -> u64 {
118 DEFAULT_SESSION_RETENTION_DAYS
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
122pub struct SessionSettings {
123 #[serde(default = "default_session_retention_days")]
124 pub retention_days: u64,
125}
126
127impl Default for SessionSettings {
128 fn default() -> Self {
129 Self {
130 retention_days: DEFAULT_SESSION_RETENTION_DAYS,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
136pub(crate) struct AppearanceSettings {
137 #[serde(
138 default = "default_appearance_theme",
139 skip_serializing_if = "is_default_appearance_theme"
140 )]
141 pub(crate) theme: String,
142 #[serde(default, skip_serializing_if = "is_false")]
143 pub(crate) reduced_motion: bool,
144}
145
146impl Default for AppearanceSettings {
147 fn default() -> Self {
148 Self {
149 theme: crate::appearance::DEFAULT_THEME_ID.to_string(),
150 reduced_motion: false,
151 }
152 }
153}
154
155fn default_appearance_theme() -> String {
156 crate::appearance::DEFAULT_THEME_ID.to_string()
157}
158
159fn is_default_appearance_theme(theme: &String) -> bool {
160 theme == crate::appearance::DEFAULT_THEME_ID
161}
162
163#[derive(Debug, Clone, PartialEq, Eq)]
164pub struct Settings {
165 pub selected_model: SelectedModelSettings,
166 pub fast: FastSettings,
167 pub openai_codex: OpenAiCodexSettings,
168 pub openai_responses: OpenAiResponsesSettings,
169 pub no_color: Option<bool>,
170 pub anthropic_cache_ttl: Option<AnthropicCacheTtl>,
171 pub file_autocomplete_respects_gitignore: bool,
172 pub context: Option<crate::context::ContextBudget>,
173 pub session_titles: SessionTitleSettings,
174 pub summarizer: super::agent::SummarizerSettings,
175 pub compaction: CompactionSettings,
176 pub tools: ToolSettings,
177 pub subagents: SubagentsSettings,
178 pub models: ModelsSettings,
179 pub hooks: HookSettings,
180 pub instructions: InstructionsSettings,
181 pub skills: SkillsSettings,
182 pub custom_providers: BTreeMap<String, CustomProviderConfig>,
183 pub mcp_servers: McpServersSettings,
184 pub mcp_approvals: BTreeMap<String, BTreeMap<String, bool>>,
186 pub lsp: LspSettings,
187 pub integrations: IntegrationsSettings,
188 pub tui: TuiSettings,
189 pub provider_stream: ProviderStreamSettings,
190 pub ttsr: TtsrSettings,
191 pub selected_primary_agent: Option<String>,
192}
193
194impl Default for Settings {
195 fn default() -> Self {
196 Self {
197 fast: FastSettings::default(),
198
199 openai_responses: OpenAiResponsesSettings::default(),
200 selected_model: SelectedModelSettings::default(),
201 openai_codex: OpenAiCodexSettings::default(),
202 no_color: None,
203 file_autocomplete_respects_gitignore: true,
204 anthropic_cache_ttl: None,
205 context: None,
206 session_titles: SessionTitleSettings::default(),
207 summarizer: super::agent::SummarizerSettings::default(),
208 compaction: CompactionSettings::default(),
209 tools: ToolSettings::default(),
210 subagents: SubagentsSettings::default(),
211 models: ModelsSettings::default(),
212 hooks: HookSettings::default(),
213 instructions: InstructionsSettings::default(),
214 skills: SkillsSettings::default(),
215 custom_providers: BTreeMap::new(),
216 mcp_servers: BTreeMap::new(),
217 mcp_approvals: BTreeMap::new(),
218 lsp: LspSettings::default(),
219 integrations: IntegrationsSettings::default(),
220 tui: TuiSettings::default(),
221 provider_stream: ProviderStreamSettings::default(),
222 ttsr: TtsrSettings::default(),
223 selected_primary_agent: None,
224 }
225 }
226}
227
228impl Settings {
229 pub(crate) fn text_verbosity_for(&self, provider: &str) -> Option<TextVerbosity> {
230 if provider == crate::providers::OPENAI_CODEX_PROVIDER {
231 return Some(
232 self.openai_responses
233 .text_verbosity
234 .unwrap_or(self.openai_codex.text_verbosity),
235 );
236 }
237 self.custom_providers.get(provider).and_then(|custom| {
238 (custom.use_responses_endpoint && custom.supports_text_verbosity)
239 .then_some(self.openai_responses.text_verbosity)
240 .flatten()
241 })
242 }
243}