settings.rs

  1use std::sync::Arc;
  2
  3use anyhow::Result;
  4use gpui::App;
  5use language_model::LanguageModelCacheConfiguration;
  6use project::Fs;
  7use schemars::JsonSchema;
  8use serde::{Deserialize, Serialize};
  9use settings::{Settings, SettingsSources, update_settings_file};
 10
 11use crate::provider::{
 12    self,
 13    anthropic::AnthropicSettings,
 14    bedrock::AmazonBedrockSettings,
 15    cloud::{self, ZedDotDevSettings},
 16    copilot_chat::CopilotChatSettings,
 17    deepseek::DeepSeekSettings,
 18    google::GoogleSettings,
 19    lmstudio::LmStudioSettings,
 20    mistral::MistralSettings,
 21    ollama::OllamaSettings,
 22    open_ai::OpenAiSettings,
 23    open_router::OpenRouterSettings,
 24};
 25
 26/// Initializes the language model settings.
 27pub fn init(fs: Arc<dyn Fs>, cx: &mut App) {
 28    AllLanguageModelSettings::register(cx);
 29
 30    if AllLanguageModelSettings::get_global(cx)
 31        .openai
 32        .needs_setting_migration
 33    {
 34        update_settings_file::<AllLanguageModelSettings>(fs.clone(), cx, move |setting, _| {
 35            if let Some(settings) = setting.openai.clone() {
 36                let (newest_version, _) = settings.upgrade();
 37                setting.openai = Some(OpenAiSettingsContent::Versioned(
 38                    VersionedOpenAiSettingsContent::V1(newest_version),
 39                ));
 40            }
 41        });
 42    }
 43
 44    if AllLanguageModelSettings::get_global(cx)
 45        .anthropic
 46        .needs_setting_migration
 47    {
 48        update_settings_file::<AllLanguageModelSettings>(fs, cx, move |setting, _| {
 49            if let Some(settings) = setting.anthropic.clone() {
 50                let (newest_version, _) = settings.upgrade();
 51                setting.anthropic = Some(AnthropicSettingsContent::Versioned(
 52                    VersionedAnthropicSettingsContent::V1(newest_version),
 53                ));
 54            }
 55        });
 56    }
 57}
 58
 59#[derive(Default)]
 60pub struct AllLanguageModelSettings {
 61    pub anthropic: AnthropicSettings,
 62    pub bedrock: AmazonBedrockSettings,
 63    pub ollama: OllamaSettings,
 64    pub openai: OpenAiSettings,
 65    pub open_router: OpenRouterSettings,
 66    pub zed_dot_dev: ZedDotDevSettings,
 67    pub google: GoogleSettings,
 68    pub copilot_chat: CopilotChatSettings,
 69    pub lmstudio: LmStudioSettings,
 70    pub deepseek: DeepSeekSettings,
 71    pub mistral: MistralSettings,
 72}
 73
 74#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
 75pub struct AllLanguageModelSettingsContent {
 76    pub anthropic: Option<AnthropicSettingsContent>,
 77    pub bedrock: Option<AmazonBedrockSettingsContent>,
 78    pub ollama: Option<OllamaSettingsContent>,
 79    pub lmstudio: Option<LmStudioSettingsContent>,
 80    pub openai: Option<OpenAiSettingsContent>,
 81    pub open_router: Option<OpenRouterSettingsContent>,
 82    #[serde(rename = "zed.dev")]
 83    pub zed_dot_dev: Option<ZedDotDevSettingsContent>,
 84    pub google: Option<GoogleSettingsContent>,
 85    pub deepseek: Option<DeepseekSettingsContent>,
 86    pub copilot_chat: Option<CopilotChatSettingsContent>,
 87    pub mistral: Option<MistralSettingsContent>,
 88}
 89
 90#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
 91#[serde(untagged)]
 92pub enum AnthropicSettingsContent {
 93    Versioned(VersionedAnthropicSettingsContent),
 94    Legacy(LegacyAnthropicSettingsContent),
 95}
 96
 97impl AnthropicSettingsContent {
 98    pub fn upgrade(self) -> (AnthropicSettingsContentV1, bool) {
 99        match self {
100            AnthropicSettingsContent::Legacy(content) => (
101                AnthropicSettingsContentV1 {
102                    api_url: content.api_url,
103                    available_models: content.available_models.map(|models| {
104                        models
105                            .into_iter()
106                            .filter_map(|model| match model {
107                                anthropic::Model::Custom {
108                                    name,
109                                    display_name,
110                                    max_tokens,
111                                    tool_override,
112                                    cache_configuration,
113                                    max_output_tokens,
114                                    default_temperature,
115                                    extra_beta_headers,
116                                    mode,
117                                } => Some(provider::anthropic::AvailableModel {
118                                    name,
119                                    display_name,
120                                    max_tokens,
121                                    tool_override,
122                                    cache_configuration: cache_configuration.as_ref().map(
123                                        |config| LanguageModelCacheConfiguration {
124                                            max_cache_anchors: config.max_cache_anchors,
125                                            should_speculate: config.should_speculate,
126                                            min_total_token: config.min_total_token,
127                                        },
128                                    ),
129                                    max_output_tokens,
130                                    default_temperature,
131                                    extra_beta_headers,
132                                    mode: Some(mode.into()),
133                                }),
134                                _ => None,
135                            })
136                            .collect()
137                    }),
138                },
139                true,
140            ),
141            AnthropicSettingsContent::Versioned(content) => match content {
142                VersionedAnthropicSettingsContent::V1(content) => (content, false),
143            },
144        }
145    }
146}
147
148#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
149pub struct LegacyAnthropicSettingsContent {
150    pub api_url: Option<String>,
151    pub available_models: Option<Vec<anthropic::Model>>,
152}
153
154#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
155#[serde(tag = "version")]
156pub enum VersionedAnthropicSettingsContent {
157    #[serde(rename = "1")]
158    V1(AnthropicSettingsContentV1),
159}
160
161#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
162pub struct AnthropicSettingsContentV1 {
163    pub api_url: Option<String>,
164    pub available_models: Option<Vec<provider::anthropic::AvailableModel>>,
165}
166
167#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
168pub struct AmazonBedrockSettingsContent {
169    available_models: Option<Vec<provider::bedrock::AvailableModel>>,
170    endpoint_url: Option<String>,
171    region: Option<String>,
172    profile: Option<String>,
173    authentication_method: Option<provider::bedrock::BedrockAuthMethod>,
174}
175
176#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
177pub struct OllamaSettingsContent {
178    pub api_url: Option<String>,
179    pub available_models: Option<Vec<provider::ollama::AvailableModel>>,
180}
181
182#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
183pub struct LmStudioSettingsContent {
184    pub api_url: Option<String>,
185    pub available_models: Option<Vec<provider::lmstudio::AvailableModel>>,
186}
187
188#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
189pub struct DeepseekSettingsContent {
190    pub api_url: Option<String>,
191    pub available_models: Option<Vec<provider::deepseek::AvailableModel>>,
192}
193
194#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
195pub struct MistralSettingsContent {
196    pub api_url: Option<String>,
197    pub available_models: Option<Vec<provider::mistral::AvailableModel>>,
198}
199
200#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
201#[serde(untagged)]
202pub enum OpenAiSettingsContent {
203    Versioned(VersionedOpenAiSettingsContent),
204    Legacy(LegacyOpenAiSettingsContent),
205}
206
207impl OpenAiSettingsContent {
208    pub fn upgrade(self) -> (OpenAiSettingsContentV1, bool) {
209        match self {
210            OpenAiSettingsContent::Legacy(content) => (
211                OpenAiSettingsContentV1 {
212                    api_url: content.api_url,
213                    available_models: content.available_models.map(|models| {
214                        models
215                            .into_iter()
216                            .filter_map(|model| match model {
217                                open_ai::Model::Custom {
218                                    name,
219                                    display_name,
220                                    max_tokens,
221                                    max_output_tokens,
222                                    max_completion_tokens,
223                                } => Some(provider::open_ai::AvailableModel {
224                                    name,
225                                    max_tokens,
226                                    max_output_tokens,
227                                    display_name,
228                                    max_completion_tokens,
229                                }),
230                                _ => None,
231                            })
232                            .collect()
233                    }),
234                },
235                true,
236            ),
237            OpenAiSettingsContent::Versioned(content) => match content {
238                VersionedOpenAiSettingsContent::V1(content) => (content, false),
239            },
240        }
241    }
242}
243
244#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
245pub struct LegacyOpenAiSettingsContent {
246    pub api_url: Option<String>,
247    pub available_models: Option<Vec<open_ai::Model>>,
248}
249
250#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
251#[serde(tag = "version")]
252pub enum VersionedOpenAiSettingsContent {
253    #[serde(rename = "1")]
254    V1(OpenAiSettingsContentV1),
255}
256
257#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
258pub struct OpenAiSettingsContentV1 {
259    pub api_url: Option<String>,
260    pub available_models: Option<Vec<provider::open_ai::AvailableModel>>,
261}
262
263#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
264pub struct GoogleSettingsContent {
265    pub api_url: Option<String>,
266    pub available_models: Option<Vec<provider::google::AvailableModel>>,
267}
268
269#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
270pub struct ZedDotDevSettingsContent {
271    available_models: Option<Vec<cloud::AvailableModel>>,
272}
273
274#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
275pub struct CopilotChatSettingsContent {
276    pub api_url: Option<String>,
277    pub auth_url: Option<String>,
278    pub models_url: Option<String>,
279}
280
281#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
282pub struct OpenRouterSettingsContent {
283    pub api_url: Option<String>,
284    pub available_models: Option<Vec<provider::open_router::AvailableModel>>,
285}
286
287impl settings::Settings for AllLanguageModelSettings {
288    const KEY: Option<&'static str> = Some("language_models");
289
290    const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
291
292    type FileContent = AllLanguageModelSettingsContent;
293
294    fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
295        fn merge<T>(target: &mut T, value: Option<T>) {
296            if let Some(value) = value {
297                *target = value;
298            }
299        }
300
301        let mut settings = AllLanguageModelSettings::default();
302
303        for value in sources.defaults_and_customizations() {
304            // Anthropic
305            let (anthropic, upgraded) = match value.anthropic.clone().map(|s| s.upgrade()) {
306                Some((content, upgraded)) => (Some(content), upgraded),
307                None => (None, false),
308            };
309
310            if upgraded {
311                settings.anthropic.needs_setting_migration = true;
312            }
313
314            merge(
315                &mut settings.anthropic.api_url,
316                anthropic.as_ref().and_then(|s| s.api_url.clone()),
317            );
318            merge(
319                &mut settings.anthropic.available_models,
320                anthropic.as_ref().and_then(|s| s.available_models.clone()),
321            );
322
323            // Bedrock
324            let bedrock = value.bedrock.clone();
325            merge(
326                &mut settings.bedrock.profile_name,
327                bedrock.as_ref().map(|s| s.profile.clone()),
328            );
329            merge(
330                &mut settings.bedrock.authentication_method,
331                bedrock.as_ref().map(|s| s.authentication_method.clone()),
332            );
333            merge(
334                &mut settings.bedrock.region,
335                bedrock.as_ref().map(|s| s.region.clone()),
336            );
337            merge(
338                &mut settings.bedrock.endpoint,
339                bedrock.as_ref().map(|s| s.endpoint_url.clone()),
340            );
341
342            // Ollama
343            let ollama = value.ollama.clone();
344
345            merge(
346                &mut settings.ollama.api_url,
347                value.ollama.as_ref().and_then(|s| s.api_url.clone()),
348            );
349            merge(
350                &mut settings.ollama.available_models,
351                ollama.as_ref().and_then(|s| s.available_models.clone()),
352            );
353
354            // LM Studio
355            let lmstudio = value.lmstudio.clone();
356
357            merge(
358                &mut settings.lmstudio.api_url,
359                value.lmstudio.as_ref().and_then(|s| s.api_url.clone()),
360            );
361            merge(
362                &mut settings.lmstudio.available_models,
363                lmstudio.as_ref().and_then(|s| s.available_models.clone()),
364            );
365
366            // DeepSeek
367            let deepseek = value.deepseek.clone();
368
369            merge(
370                &mut settings.deepseek.api_url,
371                value.deepseek.as_ref().and_then(|s| s.api_url.clone()),
372            );
373            merge(
374                &mut settings.deepseek.available_models,
375                deepseek.as_ref().and_then(|s| s.available_models.clone()),
376            );
377
378            // OpenAI
379            let (openai, upgraded) = match value.openai.clone().map(|s| s.upgrade()) {
380                Some((content, upgraded)) => (Some(content), upgraded),
381                None => (None, false),
382            };
383
384            if upgraded {
385                settings.openai.needs_setting_migration = true;
386            }
387
388            merge(
389                &mut settings.openai.api_url,
390                openai.as_ref().and_then(|s| s.api_url.clone()),
391            );
392            merge(
393                &mut settings.openai.available_models,
394                openai.as_ref().and_then(|s| s.available_models.clone()),
395            );
396            merge(
397                &mut settings.zed_dot_dev.available_models,
398                value
399                    .zed_dot_dev
400                    .as_ref()
401                    .and_then(|s| s.available_models.clone()),
402            );
403            merge(
404                &mut settings.google.api_url,
405                value.google.as_ref().and_then(|s| s.api_url.clone()),
406            );
407            merge(
408                &mut settings.google.available_models,
409                value
410                    .google
411                    .as_ref()
412                    .and_then(|s| s.available_models.clone()),
413            );
414
415            // Mistral
416            let mistral = value.mistral.clone();
417            merge(
418                &mut settings.mistral.api_url,
419                mistral.as_ref().and_then(|s| s.api_url.clone()),
420            );
421            merge(
422                &mut settings.mistral.available_models,
423                mistral.as_ref().and_then(|s| s.available_models.clone()),
424            );
425
426            // OpenRouter
427            let open_router = value.open_router.clone();
428            merge(
429                &mut settings.open_router.api_url,
430                open_router.as_ref().and_then(|s| s.api_url.clone()),
431            );
432            merge(
433                &mut settings.open_router.available_models,
434                open_router
435                    .as_ref()
436                    .and_then(|s| s.available_models.clone()),
437            );
438
439            // Copilot Chat
440            let copilot_chat = value.copilot_chat.clone().unwrap_or_default();
441
442            settings.copilot_chat.api_url = copilot_chat.api_url.map_or_else(
443                || Arc::from("https://api.githubcopilot.com/chat/completions"),
444                Arc::from,
445            );
446
447            settings.copilot_chat.auth_url = copilot_chat.auth_url.map_or_else(
448                || Arc::from("https://api.github.com/copilot_internal/v2/token"),
449                Arc::from,
450            );
451
452            settings.copilot_chat.models_url = copilot_chat.models_url.map_or_else(
453                || Arc::from("https://api.githubcopilot.com/models"),
454                Arc::from,
455            );
456        }
457
458        Ok(settings)
459    }
460
461    fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
462}