1use std::sync::Arc;
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 available_models: content.available_models.map(|models| {
91 models
92 .into_iter()
93 .filter_map(|model| match model {
94 anthropic::Model::Custom {
95 name,
96 display_name,
97 max_tokens,
98 tool_override,
99 cache_configuration,
100 max_output_tokens,
101 default_temperature,
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 default_temperature,
116 }),
117 _ => None,
118 })
119 .collect()
120 }),
121 },
122 true,
123 ),
124 AnthropicSettingsContent::Versioned(content) => match content {
125 VersionedAnthropicSettingsContent::V1(content) => (content, false),
126 },
127 }
128 }
129}
130
131#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
132pub struct LegacyAnthropicSettingsContent {
133 pub api_url: Option<String>,
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 available_models: Option<Vec<provider::anthropic::AvailableModel>>,
148}
149
150#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
151pub struct OllamaSettingsContent {
152 pub api_url: Option<String>,
153 pub available_models: Option<Vec<provider::ollama::AvailableModel>>,
154}
155
156#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
157#[serde(untagged)]
158pub enum OpenAiSettingsContent {
159 Legacy(LegacyOpenAiSettingsContent),
160 Versioned(VersionedOpenAiSettingsContent),
161}
162
163impl OpenAiSettingsContent {
164 pub fn upgrade(self) -> (OpenAiSettingsContentV1, bool) {
165 match self {
166 OpenAiSettingsContent::Legacy(content) => (
167 OpenAiSettingsContentV1 {
168 api_url: content.api_url,
169 available_models: content.available_models.map(|models| {
170 models
171 .into_iter()
172 .filter_map(|model| match model {
173 open_ai::Model::Custom {
174 name,
175 display_name,
176 max_tokens,
177 max_output_tokens,
178 max_completion_tokens,
179 } => Some(provider::open_ai::AvailableModel {
180 name,
181 max_tokens,
182 max_output_tokens,
183 display_name,
184 max_completion_tokens,
185 }),
186 _ => None,
187 })
188 .collect()
189 }),
190 },
191 true,
192 ),
193 OpenAiSettingsContent::Versioned(content) => match content {
194 VersionedOpenAiSettingsContent::V1(content) => (content, false),
195 },
196 }
197 }
198}
199
200#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
201pub struct LegacyOpenAiSettingsContent {
202 pub api_url: Option<String>,
203 pub available_models: Option<Vec<open_ai::Model>>,
204}
205
206#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
207#[serde(tag = "version")]
208pub enum VersionedOpenAiSettingsContent {
209 #[serde(rename = "1")]
210 V1(OpenAiSettingsContentV1),
211}
212
213#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
214pub struct OpenAiSettingsContentV1 {
215 pub api_url: Option<String>,
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 available_models: Option<Vec<provider::google::AvailableModel>>,
223}
224
225#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
226pub struct ZedDotDevSettingsContent {
227 available_models: Option<Vec<cloud::AvailableModel>>,
228}
229
230#[derive(Default, Clone, Debug, Serialize, Deserialize, PartialEq, JsonSchema)]
231pub struct CopilotChatSettingsContent {}
232
233impl settings::Settings for AllLanguageModelSettings {
234 const KEY: Option<&'static str> = Some("language_models");
235
236 const PRESERVED_KEYS: Option<&'static [&'static str]> = Some(&["version"]);
237
238 type FileContent = AllLanguageModelSettingsContent;
239
240 fn load(sources: SettingsSources<Self::FileContent>, _: &mut AppContext) -> Result<Self> {
241 fn merge<T>(target: &mut T, value: Option<T>) {
242 if let Some(value) = value {
243 *target = value;
244 }
245 }
246
247 let mut settings = AllLanguageModelSettings::default();
248
249 for value in sources.defaults_and_customizations() {
250 // Anthropic
251 let (anthropic, upgraded) = match value.anthropic.clone().map(|s| s.upgrade()) {
252 Some((content, upgraded)) => (Some(content), upgraded),
253 None => (None, false),
254 };
255
256 if upgraded {
257 settings.anthropic.needs_setting_migration = true;
258 }
259
260 merge(
261 &mut settings.anthropic.api_url,
262 anthropic.as_ref().and_then(|s| s.api_url.clone()),
263 );
264 merge(
265 &mut settings.anthropic.available_models,
266 anthropic.as_ref().and_then(|s| s.available_models.clone()),
267 );
268
269 // Ollama
270 let ollama = value.ollama.clone();
271
272 merge(
273 &mut settings.ollama.api_url,
274 value.ollama.as_ref().and_then(|s| s.api_url.clone()),
275 );
276 merge(
277 &mut settings.ollama.available_models,
278 ollama.as_ref().and_then(|s| s.available_models.clone()),
279 );
280
281 // OpenAI
282 let (openai, upgraded) = match value.openai.clone().map(|s| s.upgrade()) {
283 Some((content, upgraded)) => (Some(content), upgraded),
284 None => (None, false),
285 };
286
287 if upgraded {
288 settings.openai.needs_setting_migration = true;
289 }
290
291 merge(
292 &mut settings.openai.api_url,
293 openai.as_ref().and_then(|s| s.api_url.clone()),
294 );
295 merge(
296 &mut settings.openai.available_models,
297 openai.as_ref().and_then(|s| s.available_models.clone()),
298 );
299 merge(
300 &mut settings.zed_dot_dev.available_models,
301 value
302 .zed_dot_dev
303 .as_ref()
304 .and_then(|s| s.available_models.clone()),
305 );
306 merge(
307 &mut settings.google.api_url,
308 value.google.as_ref().and_then(|s| s.api_url.clone()),
309 );
310 merge(
311 &mut settings.google.available_models,
312 value
313 .google
314 .as_ref()
315 .and_then(|s| s.available_models.clone()),
316 );
317 }
318
319 Ok(settings)
320 }
321}