mirror of
https://github.com/zed-industries/zed.git
synced 2026-05-25 06:24:56 +00:00
agent: Respect favorite model settings and sync UI changes back to settings (#54318)
Closes #54313 **Before:** - Favoriting a model only stored `provider`, `model`, and hardcoded `enable_thinking` to `false`. - Selecting a favorited model would not restore your preferred `enable_thinking`, `effort`, or `speed` settings. This means that if you'd like to use, say, GPT 5.4 your favorite model on `xhigh` effort every single time, switching to it would set effort to `medium` (the default for the model) instead. **After:** - Favoriting a model captures your current `enable_thinking`, `effort`, and `speed` when it matches the currently-selected model. Otherwise, it falls back to the model's own defaults, i.e. thinking-capable models are no longer favorited with `enable_thinking` forced to `false`. - Selecting a favorited model applies its stored thinking / effort / speed. - Toggling thinking, changing effort, or toggling fast mode on a favorited model updates the favorite entry in settings (along with the existing `default_model` setting), so the preference doesn't drift. Release Notes: - Agent favorite models now remember and restore per-model thinking, effort level, and fast mode preferences.
This commit is contained in:
parent
cd12d5f98d
commit
68da244d37
6 changed files with 194 additions and 48 deletions
|
|
@ -47,7 +47,7 @@ use prompt_store::{
|
|||
WorktreeContext,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::{LanguageModelSelection, update_settings_file};
|
||||
use settings::{LanguageModelSelection, Settings as _, update_settings_file};
|
||||
use std::any::Any;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
|
@ -1423,16 +1423,29 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector {
|
|||
return Task::ready(Err(anyhow!("Invalid model ID {}", model_id)));
|
||||
};
|
||||
|
||||
// We want to reset the effort level when switching models, as the currently-selected effort level may
|
||||
// not be compatible.
|
||||
let effort = model
|
||||
.default_effort_level()
|
||||
.map(|effort_level| effort_level.value.to_string());
|
||||
let favorite = agent_settings::AgentSettings::get_global(cx)
|
||||
.favorite_models
|
||||
.iter()
|
||||
.find(|favorite| {
|
||||
favorite.provider.0 == model.provider_id().0.as_ref()
|
||||
&& favorite.model == model.id().0.as_ref()
|
||||
})
|
||||
.cloned();
|
||||
|
||||
let LanguageModelSelection {
|
||||
enable_thinking,
|
||||
effort,
|
||||
speed,
|
||||
..
|
||||
} = agent_settings::language_model_to_selection(&model, favorite.as_ref());
|
||||
|
||||
thread.update(cx, |thread, cx| {
|
||||
thread.set_model(model.clone(), cx);
|
||||
thread.set_thinking_effort(effort.clone(), cx);
|
||||
thread.set_thinking_enabled(model.supports_thinking(), cx);
|
||||
thread.set_thinking_enabled(enable_thinking, cx);
|
||||
if let Some(speed) = speed {
|
||||
thread.set_speed(speed, cx);
|
||||
}
|
||||
});
|
||||
|
||||
update_settings_file(
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@ use std::{any::Any, rc::Rc, sync::Arc};
|
|||
|
||||
use agent_client_protocol as acp;
|
||||
use agent_servers::{AgentServer, AgentServerDelegate};
|
||||
use agent_settings::AgentSettings;
|
||||
use agent_settings::{AgentSettings, language_model_to_selection};
|
||||
use anyhow::Result;
|
||||
use collections::HashSet;
|
||||
use fs::Fs;
|
||||
use gpui::{App, Entity, Task};
|
||||
use language_model::{LanguageModelId, LanguageModelProviderId, LanguageModelRegistry};
|
||||
use project::{AgentId, Project};
|
||||
use prompt_store::PromptStore;
|
||||
use settings::{LanguageModelSelection, Settings as _, update_settings_file};
|
||||
|
|
@ -76,7 +77,7 @@ impl AgentServer for NativeAgentServer {
|
|||
fs: Arc<dyn Fs>,
|
||||
cx: &App,
|
||||
) {
|
||||
let selection = model_id_to_selection(&model_id);
|
||||
let selection = model_id_to_selection(&model_id, cx);
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
let agent = settings.agent.get_or_insert_default();
|
||||
if should_be_favorite {
|
||||
|
|
@ -89,16 +90,41 @@ impl AgentServer for NativeAgentServer {
|
|||
}
|
||||
|
||||
/// Convert a ModelId (e.g. "anthropic/claude-3-5-sonnet") to a LanguageModelSelection.
|
||||
fn model_id_to_selection(model_id: &acp::ModelId) -> LanguageModelSelection {
|
||||
fn model_id_to_selection(model_id: &acp::ModelId, cx: &App) -> LanguageModelSelection {
|
||||
let id = model_id.0.as_ref();
|
||||
let (provider, model) = id.split_once('/').unwrap_or(("", id));
|
||||
LanguageModelSelection {
|
||||
provider: provider.to_owned().into(),
|
||||
model: model.to_owned(),
|
||||
enable_thinking: false,
|
||||
effort: None,
|
||||
speed: None,
|
||||
}
|
||||
|
||||
let provider_id = LanguageModelProviderId(provider.to_string().into());
|
||||
let model_id_typed = LanguageModelId(model.to_string().into());
|
||||
let resolved = LanguageModelRegistry::global(cx)
|
||||
.read(cx)
|
||||
.provider(&provider_id)
|
||||
.and_then(|p| {
|
||||
p.provided_models(cx)
|
||||
.into_iter()
|
||||
.find(|m| m.id() == model_id_typed)
|
||||
});
|
||||
|
||||
let Some(resolved) = resolved else {
|
||||
return LanguageModelSelection {
|
||||
provider: provider.to_owned().into(),
|
||||
model: model.to_owned(),
|
||||
enable_thinking: false,
|
||||
effort: None,
|
||||
speed: None,
|
||||
};
|
||||
};
|
||||
|
||||
let current_user_selection = AgentSettings::get_global(cx)
|
||||
.default_model
|
||||
.as_ref()
|
||||
.filter(|selection| {
|
||||
selection.provider.0 == resolved.provider_id().0.as_ref()
|
||||
&& selection.model == resolved.id().0.as_ref()
|
||||
})
|
||||
.cloned();
|
||||
|
||||
language_model_to_selection(&resolved, current_user_selection.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -210,7 +210,48 @@ impl AgentSettings {
|
|||
.map(|sel| ModelId::new(format!("{}/{}", sel.provider.0, sel.model)))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn language_model_to_selection(
|
||||
model: &Arc<dyn LanguageModel>,
|
||||
override_selection: Option<&LanguageModelSelection>,
|
||||
) -> LanguageModelSelection {
|
||||
let provider = model.provider_id().0.to_string().into();
|
||||
let model_name = model.id().0.to_string();
|
||||
match override_selection {
|
||||
Some(current) => LanguageModelSelection {
|
||||
provider,
|
||||
model: model_name,
|
||||
enable_thinking: current.enable_thinking && model.supports_thinking(),
|
||||
effort: current
|
||||
.effort
|
||||
.clone()
|
||||
.filter(|value| {
|
||||
model
|
||||
.supported_effort_levels()
|
||||
.iter()
|
||||
.any(|level| level.value.as_ref() == value.as_str())
|
||||
})
|
||||
.or_else(|| {
|
||||
model
|
||||
.default_effort_level()
|
||||
.map(|effort| effort.value.to_string())
|
||||
}),
|
||||
speed: current.speed.filter(|_| model.supports_fast_mode()),
|
||||
},
|
||||
None => LanguageModelSelection {
|
||||
provider,
|
||||
model: model_name,
|
||||
enable_thinking: model.supports_thinking(),
|
||||
effort: model
|
||||
.default_effort_level()
|
||||
.map(|effort| effort.value.to_string()),
|
||||
speed: None,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl AgentSettings {
|
||||
pub fn get_layout(cx: &App) -> WindowLayout {
|
||||
let store = cx.global::<SettingsStore>();
|
||||
let merged = store.merged_settings();
|
||||
|
|
|
|||
|
|
@ -3842,12 +3842,22 @@ impl ThreadView {
|
|||
let enable_thinking = !thread.thinking_enabled();
|
||||
thread.set_thinking_enabled(enable_thinking, cx);
|
||||
|
||||
let favorite_key = thread.model().map(|model| {
|
||||
(model.provider_id().0.to_string(), model.id().0.to_string())
|
||||
});
|
||||
let fs = thread.project().read(cx).fs().clone();
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
if let Some(agent) = settings.agent.as_mut()
|
||||
&& let Some(default_model) = agent.default_model.as_mut()
|
||||
{
|
||||
default_model.enable_thinking = enable_thinking;
|
||||
if let Some(agent) = settings.agent.as_mut() {
|
||||
if let Some(default_model) = agent.default_model.as_mut() {
|
||||
default_model.enable_thinking = enable_thinking;
|
||||
}
|
||||
if let Some((provider_id, model_id)) = &favorite_key {
|
||||
agent.update_favorite_model(
|
||||
provider_id,
|
||||
model_id,
|
||||
|favorite| favorite.enable_thinking = enable_thinking,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -3978,14 +3988,33 @@ impl ThreadView {
|
|||
cx,
|
||||
);
|
||||
|
||||
let favorite_key = thread.model().map(|model| {
|
||||
(
|
||||
model.provider_id().0.to_string(),
|
||||
model.id().0.to_string(),
|
||||
)
|
||||
});
|
||||
let fs = thread.project().read(cx).fs().clone();
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
if let Some(agent) = settings.agent.as_mut()
|
||||
&& let Some(default_model) =
|
||||
if let Some(agent) = settings.agent.as_mut() {
|
||||
if let Some(default_model) =
|
||||
agent.default_model.as_mut()
|
||||
{
|
||||
default_model.effort =
|
||||
Some(effort.to_string());
|
||||
{
|
||||
default_model.effort =
|
||||
Some(effort.to_string());
|
||||
}
|
||||
if let Some((provider_id, model_id)) =
|
||||
&favorite_key
|
||||
{
|
||||
agent.update_favorite_model(
|
||||
provider_id,
|
||||
model_id,
|
||||
|favorite| {
|
||||
favorite.effort =
|
||||
Some(effort.to_string())
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -8881,12 +8910,20 @@ impl ThreadView {
|
|||
.unwrap_or(Speed::Fast);
|
||||
thread.set_speed(new_speed, cx);
|
||||
|
||||
let favorite_key = thread
|
||||
.model()
|
||||
.map(|model| (model.provider_id().0.to_string(), model.id().0.to_string()));
|
||||
let fs = thread.project().read(cx).fs().clone();
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
if let Some(agent) = settings.agent.as_mut()
|
||||
&& let Some(default_model) = agent.default_model.as_mut()
|
||||
{
|
||||
default_model.speed = Some(new_speed);
|
||||
if let Some(agent) = settings.agent.as_mut() {
|
||||
if let Some(default_model) = agent.default_model.as_mut() {
|
||||
default_model.speed = Some(new_speed);
|
||||
}
|
||||
if let Some((provider_id, model_id)) = &favorite_key {
|
||||
agent.update_favorite_model(provider_id, model_id, |favorite| {
|
||||
favorite.speed = Some(new_speed)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -8927,12 +8964,20 @@ impl ThreadView {
|
|||
thread.update(cx, |thread, cx| {
|
||||
thread.set_thinking_effort(Some(next_effort.clone()), cx);
|
||||
|
||||
let favorite_key = thread
|
||||
.model()
|
||||
.map(|model| (model.provider_id().0.to_string(), model.id().0.to_string()));
|
||||
let fs = thread.project().read(cx).fs().clone();
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
if let Some(agent) = settings.agent.as_mut()
|
||||
&& let Some(default_model) = agent.default_model.as_mut()
|
||||
{
|
||||
default_model.effort = Some(next_effort);
|
||||
if let Some(agent) = settings.agent.as_mut() {
|
||||
if let Some(default_model) = agent.default_model.as_mut() {
|
||||
default_model.effort = Some(next_effort.clone());
|
||||
}
|
||||
if let Some((provider_id, model_id)) = &favorite_key {
|
||||
agent.update_favorite_model(provider_id, model_id, |favorite| {
|
||||
favorite.effort = Some(next_effort)
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,27 +1,27 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use agent_settings::{AgentSettings, language_model_to_selection};
|
||||
use fs::Fs;
|
||||
use language_model::LanguageModel;
|
||||
use settings::{LanguageModelSelection, update_settings_file};
|
||||
use settings::{Settings as _, update_settings_file};
|
||||
use ui::App;
|
||||
|
||||
fn language_model_to_selection(model: &Arc<dyn LanguageModel>) -> LanguageModelSelection {
|
||||
LanguageModelSelection {
|
||||
provider: model.provider_id().to_string().into(),
|
||||
model: model.id().0.to_string(),
|
||||
enable_thinking: false,
|
||||
effort: None,
|
||||
speed: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn toggle_in_settings(
|
||||
model: Arc<dyn LanguageModel>,
|
||||
should_be_favorite: bool,
|
||||
fs: Arc<dyn Fs>,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let selection = language_model_to_selection(&model);
|
||||
let current_user_selection = AgentSettings::get_global(cx)
|
||||
.default_model
|
||||
.as_ref()
|
||||
.filter(|selection| {
|
||||
selection.provider.0 == model.provider_id().0.as_ref()
|
||||
&& selection.model == model.id().0.as_ref()
|
||||
})
|
||||
.cloned();
|
||||
|
||||
let selection = language_model_to_selection(&model, current_user_selection.as_ref());
|
||||
update_settings_file(fs, cx, move |settings, _| {
|
||||
let agent = settings.agent.get_or_insert_default();
|
||||
if should_be_favorite {
|
||||
|
|
|
|||
|
|
@ -275,13 +275,34 @@ impl AgentSettingsContent {
|
|||
}
|
||||
|
||||
pub fn add_favorite_model(&mut self, model: LanguageModelSelection) {
|
||||
if !self.favorite_models.contains(&model) {
|
||||
// Note: this is intentional to not compare using `PartialEq`here.
|
||||
// Full equality would treat entries that differ just in thinking/effort/speed
|
||||
// as distinct and silently produce duplicates.
|
||||
if !self
|
||||
.favorite_models
|
||||
.iter()
|
||||
.any(|m| m.provider == model.provider && m.model == model.model)
|
||||
{
|
||||
self.favorite_models.push(model);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_favorite_model(&mut self, model: &LanguageModelSelection) {
|
||||
self.favorite_models.retain(|m| m != model);
|
||||
self.favorite_models
|
||||
.retain(|m| !(m.provider == model.provider && m.model == model.model));
|
||||
}
|
||||
|
||||
pub fn update_favorite_model<F>(&mut self, provider: &str, model: &str, f: F)
|
||||
where
|
||||
F: FnOnce(&mut LanguageModelSelection),
|
||||
{
|
||||
if let Some(entry) = self
|
||||
.favorite_models
|
||||
.iter_mut()
|
||||
.find(|m| m.provider.0 == provider && m.model == model)
|
||||
{
|
||||
f(entry);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_tool_default_permission(&mut self, tool_id: &str, mode: ToolPermissionMode) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue