1use std::{sync::Arc, time::Duration};
2
3use anyhow::Result;
4use gpui::AppContext;
5use project::Fs;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use settings::{update_settings_file, Settings, SettingsSources};
9
10use crate::{
11 provider::{
12 self,
13 anthropic::AnthropicSettings,
14 cloud::{self, ZedDotDevSettings},
15 copilot_chat::CopilotChatSettings,
16 google::GoogleSettings,
17 ollama::OllamaSettings,
18 open_ai::OpenAiSettings,
19 },
20 LanguageModelCacheConfiguration,
21};
22
23/// Initializes the language model settings.
24pub fn init(fs: Arc<dyn Fs>, cx: &mut AppContext) {
25 AllLanguageModelSettings::register(cx);
26
27 if AllLanguageModelSettings::get_global(cx)
28 .openai
29 .needs_setting_migration
30 {
31 update_settings_file::<AllLanguageModelSettings>(fs.clone(), cx, move |setting, _| {
32 if let Some(settings) = setting.openai.clone() {
33 let (newest_version, _) = settings.upgrade();
34 setting.openai = Some(OpenAiSettingsContent::Versioned(
35 VersionedOpenAiSettingsContent::V1(newest_version),
36 ));
37 }
38 });
39 }
40
41 if AllLanguageModelSettings::get_global(cx)
42 .anthropic
43 .needs_setting_migration
44 {
45 update_settings_file::<AllLanguageModelSettings>(fs, cx, move |setting, _| {
46 if let Some(settings) = setting.anthropic.clone() {
47 let (newest_version, _) = settings.upgrade();
48 setting.anthropic = Some(AnthropicSettingsContent::Versioned(
49 VersionedAnthropicSettingsContent::V1(newest_version),
50 ));
51 }
52 });
53 }
54}
55
56#[derive(Default)]
57pub struct AllLanguageModelSettings {
58 pub anthropic: AnthropicSettings,
59 pub ollama: OllamaSettings,
60 pub openai: OpenAiSettings,
61 pub zed_dot_dev: ZedDotDevSettings,
62 pub google: GoogleSettings,
63 pub copilot_chat: CopilotChatSettings,
64}
65
66#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
67pub struct AllLanguageModelSettingsContent {
68 pub anthropic: Option<AnthropicSettingsContent>,
69 pub ollama: Option<OllamaSettingsContent>,
70 pub openai: Option<OpenAiSettingsContent>,
71 #[serde(rename = "zed.dev")]
72 pub zed_dot_dev: Option<ZedDotDevSettingsContent>,
73 pub google: Option<GoogleSettingsContent>,
74 pub copilot_chat: Option<CopilotChatSettingsContent>,
75}
76
77#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
78#[serde(untagged)]
79pub enum AnthropicSettingsContent {
80 Legacy(LegacyAnthropicSettingsContent),
81 Versioned(VersionedAnthropicSettingsContent),
82}
83
84impl AnthropicSettingsContent {
85 pub fn upgrade(self) -> (AnthropicSettingsContentV1, bool) {
86 match self {
87 AnthropicSettingsContent::Legacy(content) => (
88 AnthropicSettingsContentV1 {
89 api_url: content.api_url,
90 low_speed_timeout_in_seconds: content.low_speed_timeout_in_seconds,
91 available_models: content.available_models.map(|models| {
92 models
93 .into_iter()
94 .filter_map(|model| match model {
95 anthropic::Model::Custom {
96 name,
97 display_name,
98 max_tokens,
99 tool_override,
100 cache_configuration,
101 max_output_tokens,
102 } => Some(provider::anthropic::AvailableModel {
103 name,
104 display_name,
105 max_tokens,
106 tool_override,
107 cache_configuration: cache_configuration.as_ref().map(
108 |config| LanguageModelCacheConfiguration {
109 max_cache_anchors: config.max_cache_anchors,
110 should_speculate: config.should_speculate,
111 min_total_token: config.min_total_token,
112 },
113 ),
114 max_output_tokens,
115 }),
116 _ => None,
117 })
118 .collect()
119 }),
120 },
121 true,
122 ),
123 AnthropicSettingsContent::Versioned(content) => match content {
124 VersionedAnthropicSettingsContent::V1(content) => (content, false),
125 },
126 }
127 }
128}
129
130#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
131pub struct LegacyAnthropicSettingsContent {
132 pub api_url: Option<String>,
133 pub low_speed_timeout_in_seconds: Option<u64>,
134 pub available_models: Option<Vec<anthropic::Model>>,
135}
136
137#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
138#[serde(tag = "version")]
139pub enum VersionedAnthropicSettingsContent {
140 #[serde(rename = "1")]
141 V1(AnthropicSettingsContentV1),
142}
143
144#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
145pub struct AnthropicSettingsContentV1 {
146 pub api_url: Option<String>,
147 pub low_speed_timeout_in_seconds: Option<u64>,
148 pub available_models: Option<Vec<provider::anthropic::AvailableModel>>,
149}
150
151#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
152pub struct OllamaSettingsContent {
153 pub api_url: Option<String>,
154 pub low_speed_timeout_in_seconds: Option<u64>,
155}
156
157#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
158#[serde(untagged)]
159pub enum OpenAiSettingsContent {
160 Legacy(LegacyOpenAiSettingsContent),
161 Versioned(VersionedOpenAiSettingsContent),
162}
163
164impl OpenAiSettingsContent {
165 pub fn upgrade(self) -> (OpenAiSettingsContentV1, bool) {
166 match self {
167 OpenAiSettingsContent::Legacy(content) => (
168 OpenAiSettingsContentV1 {
169 api_url: content.api_url,
170 low_speed_timeout_in_seconds: content.low_speed_timeout_in_seconds,
171 available_models: content.available_models.map(|models| {
172 models
173 .into_iter()
174 .filter_map(|model| match model {
175 open_ai::Model::Custom {
176 name,
177 max_tokens,
178 max_output_tokens,
179 } => Some(provider::open_ai::AvailableModel {
180 name,
181 max_tokens,
182 max_output_tokens,
183 }),
184 _ => None,
185 })
186 .collect()
187 }),
188 },
189 true,
190 ),
191 OpenAiSettingsContent::Versioned(content) => match content {
192 VersionedOpenAiSettingsContent::V1(content) => (content, false),
193 },
194 }
195 }
196}
197
198#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
199pub struct LegacyOpenAiSettingsContent {
200 pub api_url: Option<String>,
201 pub low_speed_timeout_in_seconds: Option<u64>,
202 pub available_models: Option<Vec<open_ai::Model>>,
203}
204
205#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
206#[serde(tag = "version")]
207pub enum VersionedOpenAiSettingsContent {
208 #[serde(rename = "1")]
209 V1(OpenAiSettingsContentV1),
210}
211
212#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
213pub struct OpenAiSettingsContentV1 {
214 pub api_url: Option<String>,
215 pub low_speed_timeout_in_seconds: Option<u64>,
216 pub available_models: Option<Vec<provider::open_ai::AvailableModel>>,
217}
218
219#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
220pub struct GoogleSettingsContent {
221 pub api_url: Option<String>,
222 pub low_speed_timeout_in_seconds: Option<u64>,
223 pub available_models: Option<Vec<provider::google::AvailableModel>>,
224}
225
226#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
227pub struct ZedDotDevSettingsContent {
228 available_models: Option<Vec<cloud::AvailableModel>>,
229}
230
231#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
232pub struct CopilotChatSettingsContent {
233 low_speed_timeout_in_seconds: Option<u64>,
234}
235
236impl settings::Settings for AllLanguageModelSettings {
237 const KEY: Option<&'static str> = Some("language_models");
238
239 const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
240
241 type FileContent = AllLanguageModelSettingsContent;
242
243 fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
244 fn merge<T>(target: &mut T, value: Option<T>) {
245 if let Some(value) = value {
246 *target = value;
247 }
248 }
249
250 let mut settings = AllLanguageModelSettings::default();
251
252 for value in sources.defaults_and_customizations() {
253 // Anthropic
254 let (anthropic, upgraded) = match value.anthropic.clone().map(|s| s.upgrade()) {
255 Some((content, upgraded)) => (Some(content), upgraded),
256 None => (None, false),
257 };
258
259 if upgraded {
260 settings.anthropic.needs_setting_migration = true;
261 }
262
263 merge(
264 &mut settings.anthropic.api_url,
265 anthropic.as_ref().and_then(|s| s.api_url.clone()),
266 );
267 if let Some(low_speed_timeout_in_seconds) = anthropic
268 .as_ref()
269 .and_then(|s| s.low_speed_timeout_in_seconds)
270 {
271 settings.anthropic.low_speed_timeout =
272 Some(Duration::from_secs(low_speed_timeout_in_seconds));
273 }
274 merge(
275 &mut settings.anthropic.available_models,
276 anthropic.as_ref().and_then(|s| s.available_models.clone()),
277 );
278
279 merge(
280 &mut settings.ollama.api_url,
281 value.ollama.as_ref().and_then(|s| s.api_url.clone()),
282 );
283 if let Some(low_speed_timeout_in_seconds) = value
284 .ollama
285 .as_ref()
286 .and_then(|s| s.low_speed_timeout_in_seconds)
287 {
288 settings.ollama.low_speed_timeout =
289 Some(Duration::from_secs(low_speed_timeout_in_seconds));
290 }
291
292 // OpenAI
293 let (openai, upgraded) = match value.openai.clone().map(|s| s.upgrade()) {
294 Some((content, upgraded)) => (Some(content), upgraded),
295 None => (None, false),
296 };
297
298 if upgraded {
299 settings.openai.needs_setting_migration = true;
300 }
301
302 merge(
303 &mut settings.openai.api_url,
304 openai.as_ref().and_then(|s| s.api_url.clone()),
305 );
306 if let Some(low_speed_timeout_in_seconds) =
307 openai.as_ref().and_then(|s| s.low_speed_timeout_in_seconds)
308 {
309 settings.openai.low_speed_timeout =
310 Some(Duration::from_secs(low_speed_timeout_in_seconds));
311 }
312 merge(
313 &mut settings.openai.available_models,
314 openai.as_ref().and_then(|s| s.available_models.clone()),
315 );
316
317 merge(
318 &mut settings.zed_dot_dev.available_models,
319 value
320 .zed_dot_dev
321 .as_ref()
322 .and_then(|s| s.available_models.clone()),
323 );
324
325 merge(
326 &mut settings.google.api_url,
327 value.google.as_ref().and_then(|s| s.api_url.clone()),
328 );
329 if let Some(low_speed_timeout_in_seconds) = value
330 .google
331 .as_ref()
332 .and_then(|s| s.low_speed_timeout_in_seconds)
333 {
334 settings.google.low_speed_timeout =
335 Some(Duration::from_secs(low_speed_timeout_in_seconds));
336 }
337 merge(
338 &mut settings.google.available_models,
339 value
340 .google
341 .as_ref()
342 .and_then(|s| s.available_models.clone()),
343 );
344
345 if let Some(low_speed_timeout) = value
346 .copilot_chat
347 .as_ref()
348 .and_then(|s| s.low_speed_timeout_in_seconds)
349 {
350 settings.copilot_chat.low_speed_timeout =
351 Some(Duration::from_secs(low_speed_timeout));
352 }
353 }
354
355 Ok(settings)
356 }
357}