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}