assistant: Overhaul provider infrastructure (#14929)

<img width="624" alt="image"
src="https://github.com/user-attachments/assets/f492b0bd-14c3-49e2-b2ff-dc78e52b0815">

- [x] Correctly set custom model token count
- [x] How to count tokens for Gemini models?
- [x] Feature flag zed.dev provider
- [x] Figure out how to configure custom models
- [ ] Update docs

Release Notes:

- Added support for quickly switching between multiple language model
providers in the assistant panel

---------

Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
Bennet Bo Fenner 2024-07-23 19:48:41 +02:00 committed by GitHub
parent 17ef9a367f
commit d0f52e90e6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 2757 additions and 2023 deletions

View file

@ -21,7 +21,7 @@ pub use settings_store::{
pub struct SettingsAssets;
pub fn init(cx: &mut AppContext) {
let mut settings = SettingsStore::default();
let mut settings = SettingsStore::new(cx);
settings
.set_default_settings(&default_settings(), cx)
.unwrap();

View file

@ -1,9 +1,8 @@
use crate::{settings_store::SettingsStore, Settings};
use anyhow::{Context, Result};
use fs::Fs;
use futures::{channel::mpsc, StreamExt};
use gpui::{AppContext, BackgroundExecutor, UpdateGlobal};
use std::{io::ErrorKind, path::PathBuf, sync::Arc, time::Duration};
use gpui::{AppContext, BackgroundExecutor, ReadGlobal, UpdateGlobal};
use std::{path::PathBuf, sync::Arc, time::Duration};
use util::ResultExt;
pub const EMPTY_THEME_NAME: &str = "empty-theme";
@ -91,46 +90,10 @@ pub fn handle_settings_file_changes(
.detach();
}
async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
match fs.load(paths::settings_file()).await {
result @ Ok(_) => result,
Err(err) => {
if let Some(e) = err.downcast_ref::<std::io::Error>() {
if e.kind() == ErrorKind::NotFound {
return Ok(crate::initial_user_settings_content().to_string());
}
}
Err(err)
}
}
}
pub fn update_settings_file<T: Settings>(
fs: Arc<dyn Fs>,
cx: &mut AppContext,
update: impl 'static + Send + FnOnce(&mut T::FileContent),
cx: &AppContext,
update: impl 'static + Send + FnOnce(&mut T::FileContent, &AppContext),
) {
cx.spawn(|cx| async move {
let old_text = load_settings(&fs).await?;
let new_text = cx.read_global(|store: &SettingsStore, _cx| {
store.new_text_for_update::<T>(old_text, update)
})?;
let initial_path = paths::settings_file().as_path();
if fs.is_file(initial_path).await {
let resolved_path = fs.canonicalize(initial_path).await.with_context(|| {
format!("Failed to canonicalize settings path {:?}", initial_path)
})?;
fs.atomic_write(resolved_path.clone(), new_text)
.await
.with_context(|| format!("Failed to write settings to file {:?}", resolved_path))?;
} else {
fs.atomic_write(initial_path.to_path_buf(), new_text)
.await
.with_context(|| format!("Failed to write settings to file {:?}", initial_path))?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
SettingsStore::global(cx).update_settings_file::<T>(fs, update);
}

View file

@ -1,6 +1,8 @@
use anyhow::{anyhow, Context, Result};
use collections::{btree_map, hash_map, BTreeMap, HashMap};
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, UpdateGlobal};
use fs::Fs;
use futures::{channel::mpsc, future::LocalBoxFuture, FutureExt, StreamExt};
use gpui::{AppContext, AsyncAppContext, BorrowAppContext, Global, Task, UpdateGlobal};
use lazy_static::lazy_static;
use schemars::{gen::SchemaGenerator, schema::RootSchema, JsonSchema};
use serde::{de::DeserializeOwned, Deserialize as _, Serialize};
@ -161,23 +163,14 @@ pub struct SettingsStore {
TypeId,
Box<dyn Fn(&dyn Any) -> Option<usize> + Send + Sync + 'static>,
)>,
_setting_file_updates: Task<()>,
setting_file_updates_tx: mpsc::UnboundedSender<
Box<dyn FnOnce(AsyncAppContext) -> LocalBoxFuture<'static, Result<()>>>,
>,
}
impl Global for SettingsStore {}
impl Default for SettingsStore {
fn default() -> Self {
SettingsStore {
setting_values: Default::default(),
raw_default_settings: serde_json::json!({}),
raw_user_settings: serde_json::json!({}),
raw_extension_settings: serde_json::json!({}),
raw_local_settings: Default::default(),
tab_size_callback: Default::default(),
}
}
}
#[derive(Debug)]
struct SettingValue<T> {
global_value: Option<T>,
@ -207,6 +200,24 @@ trait AnySettingValue: 'static + Send + Sync {
struct DeserializedSetting(Box<dyn Any>);
impl SettingsStore {
pub fn new(cx: &AppContext) -> Self {
let (setting_file_updates_tx, mut setting_file_updates_rx) = mpsc::unbounded();
Self {
setting_values: Default::default(),
raw_default_settings: serde_json::json!({}),
raw_user_settings: serde_json::json!({}),
raw_extension_settings: serde_json::json!({}),
raw_local_settings: Default::default(),
tab_size_callback: Default::default(),
setting_file_updates_tx,
_setting_file_updates: cx.spawn(|cx| async move {
while let Some(setting_file_update) = setting_file_updates_rx.next().await {
(setting_file_update)(cx.clone()).await.log_err();
}
}),
}
}
pub fn update<C, R>(cx: &mut C, f: impl FnOnce(&mut Self, &mut C) -> R) -> R
where
C: BorrowAppContext,
@ -301,7 +312,7 @@ impl SettingsStore {
#[cfg(any(test, feature = "test-support"))]
pub fn test(cx: &mut AppContext) -> Self {
let mut this = Self::default();
let mut this = Self::new(cx);
this.set_default_settings(&crate::test_settings(), cx)
.unwrap();
this.set_user_settings("{}", cx).unwrap();
@ -323,6 +334,59 @@ impl SettingsStore {
self.set_user_settings(&new_text, cx).unwrap();
}
async fn load_settings(fs: &Arc<dyn Fs>) -> Result<String> {
match fs.load(paths::settings_file()).await {
result @ Ok(_) => result,
Err(err) => {
if let Some(e) = err.downcast_ref::<std::io::Error>() {
if e.kind() == std::io::ErrorKind::NotFound {
return Ok(crate::initial_user_settings_content().to_string());
}
}
Err(err)
}
}
}
pub fn update_settings_file<T: Settings>(
&self,
fs: Arc<dyn Fs>,
update: impl 'static + Send + FnOnce(&mut T::FileContent, &AppContext),
) {
self.setting_file_updates_tx
.unbounded_send(Box::new(move |cx: AsyncAppContext| {
async move {
let old_text = Self::load_settings(&fs).await?;
let new_text = cx.read_global(|store: &SettingsStore, cx| {
store.new_text_for_update::<T>(old_text, |content| update(content, cx))
})?;
let initial_path = paths::settings_file().as_path();
if fs.is_file(initial_path).await {
let resolved_path =
fs.canonicalize(initial_path).await.with_context(|| {
format!("Failed to canonicalize settings path {:?}", initial_path)
})?;
fs.atomic_write(resolved_path.clone(), new_text)
.await
.with_context(|| {
format!("Failed to write settings to file {:?}", resolved_path)
})?;
} else {
fs.atomic_write(initial_path.to_path_buf(), new_text)
.await
.with_context(|| {
format!("Failed to write settings to file {:?}", initial_path)
})?;
}
anyhow::Ok(())
}
.boxed_local()
}))
.ok();
}
/// Updates the value of a setting in a JSON file, returning the new text
/// for that JSON file.
pub fn new_text_for_update<T: Settings>(
@ -1019,7 +1083,7 @@ mod tests {
#[gpui::test]
fn test_settings_store_basic(cx: &mut AppContext) {
let mut store = SettingsStore::default();
let mut store = SettingsStore::new(cx);
store.register_setting::<UserSettings>(cx);
store.register_setting::<TurboSetting>(cx);
store.register_setting::<MultiKeySettings>(cx);
@ -1148,7 +1212,7 @@ mod tests {
#[gpui::test]
fn test_setting_store_assign_json_before_register(cx: &mut AppContext) {
let mut store = SettingsStore::default();
let mut store = SettingsStore::new(cx);
store
.set_default_settings(
r#"{
@ -1191,7 +1255,7 @@ mod tests {
#[gpui::test]
fn test_setting_store_update(cx: &mut AppContext) {
let mut store = SettingsStore::default();
let mut store = SettingsStore::new(cx);
store.register_setting::<MultiKeySettings>(cx);
store.register_setting::<UserSettings>(cx);
store.register_setting::<LanguageSettings>(cx);