From c3870d18f333fb8e395cadb22550d06de92ad5b3 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Thu, 22 Jan 2026 13:08:32 -0500 Subject: [PATCH] agent: Add thinking toggle for Zed provider (#47407) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR adds a thinking toggle for controlling whether to use thinking for a model in the Zed provider: Screenshot 2026-01-22 at 12 34 01 PM Previously we would create separate "Thinking" variants of the models that supported thinking in the model selector. This only applies to Anthropic models in the Zed provider, currently. This is gated behind the `cloud-thinking-toggle` feature flag. Release Notes: - N/A --------- Co-authored-by: Neel --- Cargo.lock | 1 + crates/agent/src/thread.rs | 16 +++++++- crates/agent_ui/src/acp/thread_view.rs | 41 +++++++++++++++++++- crates/feature_flags/src/flags.rs | 11 ++++++ crates/language_model/src/language_model.rs | 5 +++ crates/language_models/Cargo.toml | 1 + crates/language_models/src/provider/cloud.rs | 37 +++++++++++++----- 7 files changed, 100 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e3e01249fa..628074a875 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9040,6 +9040,7 @@ dependencies = [ "editor", "extension", "extension_host", + "feature_flags", "fs", "futures 0.3.31", "google_ai", diff --git a/crates/agent/src/thread.rs b/crates/agent/src/thread.rs index 02fa67ef86..dbcbe8eda0 100644 --- a/crates/agent/src/thread.rs +++ b/crates/agent/src/thread.rs @@ -749,6 +749,7 @@ pub struct Thread { templates: Arc, model: Option>, summarization_model: Option>, + thinking_enabled: bool, prompt_capabilities_tx: watch::Sender, pub(crate) prompt_capabilities_rx: watch::Receiver, pub(crate) project: Entity, @@ -811,6 +812,7 @@ impl Thread { templates, model, summarization_model: None, + thinking_enabled: true, prompt_capabilities_tx, prompt_capabilities_rx, project, @@ -872,6 +874,7 @@ impl Thread { templates, model: Some(model), summarization_model: None, + thinking_enabled: true, prompt_capabilities_tx, prompt_capabilities_rx, project, @@ -1069,6 +1072,8 @@ impl Thread { templates, model, summarization_model: None, + // TODO: Persist this on the `DbThread`. + thinking_enabled: true, project, action_log, updated_at: db_thread.updated_at, @@ -1167,6 +1172,15 @@ impl Thread { cx.notify() } + pub fn thinking_enabled(&self) -> bool { + self.thinking_enabled + } + + pub fn set_thinking_enabled(&mut self, enabled: bool, cx: &mut Context) { + self.thinking_enabled = enabled; + cx.notify(); + } + pub fn last_message(&self) -> Option { if let Some(message) = self.pending_message.clone() { Some(Message::Agent(message)) @@ -2292,7 +2306,7 @@ impl Thread { tool_choice: None, stop: Vec::new(), temperature: AgentSettings::temperature_for_model(model, cx), - thinking_allowed: true, + thinking_allowed: self.thinking_enabled, bypass_rate_limit: false, }; diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 3f03ecf322..e2aa654753 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -21,8 +21,8 @@ use editor::{ Editor, EditorEvent, EditorMode, MultiBuffer, PathKey, SelectionEffects, SizingBehavior, }; use feature_flags::{ - AgentSharingFeatureFlag, AgentV2FeatureFlag, FeatureFlagAppExt as _, - UserSlashCommandsFeatureFlag, + AgentSharingFeatureFlag, AgentV2FeatureFlag, CloudThinkingToggleFeatureFlag, + FeatureFlagAppExt as _, UserSlashCommandsFeatureFlag, }; use file_icons::FileIcons; use fs::Fs; @@ -6216,6 +6216,7 @@ impl AcpThreadView { h_flex() .gap_1() .children(self.render_token_usage(cx)) + .children(self.render_thinking_toggle(cx)) .children(self.profile_selector.clone()) // Either config_options_view OR (mode_selector + model_selector) .children(self.config_options_view.clone()) @@ -6486,6 +6487,42 @@ impl AcpThreadView { } } + fn render_thinking_toggle(&self, cx: &mut Context) -> Option { + if !cx.has_flag::() { + return None; + } + + let thread = self.as_native_thread(cx)?.read(cx); + + let supports_thinking = thread.model()?.supports_thinking(); + if !supports_thinking { + return None; + } + + let thinking = thread.thinking_enabled(); + + let tooltip_label = if thinking { + "Disable Thinking Mode".to_string() + } else { + "Enable Thinking Mode".to_string() + }; + + Some( + IconButton::new("thinking-mode", IconName::ToolThink) + .icon_size(IconSize::Small) + .icon_color(Color::Muted) + .toggle_state(thinking) + .tooltip(Tooltip::text(tooltip_label)) + .on_click(cx.listener(move |this, _, _window, cx| { + if let Some(thread) = this.as_native_thread(cx) { + thread.update(cx, |thread, cx| { + thread.set_thinking_enabled(!thread.thinking_enabled(), cx); + }); + } + })), + ) + } + fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context) { let Some(thread) = self.thread() else { return; diff --git a/crates/feature_flags/src/flags.rs b/crates/feature_flags/src/flags.rs index ee657799f0..e61f56ec6f 100644 --- a/crates/feature_flags/src/flags.rs +++ b/crates/feature_flags/src/flags.rs @@ -65,3 +65,14 @@ impl FeatureFlag for DiffReviewFeatureFlag { false } } + +/// Controls whether we show the new thinking toggle in the Agent Panel when using models through the Zed provider (Cloud). +pub struct CloudThinkingToggleFeatureFlag; + +impl FeatureFlag for CloudThinkingToggleFeatureFlag { + const NAME: &'static str = "cloud-thinking-toggle"; + + fn enabled_for_staff() -> bool { + false + } +} diff --git a/crates/language_model/src/language_model.rs b/crates/language_model/src/language_model.rs index 8602b253e6..eac4db09d4 100644 --- a/crates/language_model/src/language_model.rs +++ b/crates/language_model/src/language_model.rs @@ -591,6 +591,11 @@ pub trait LanguageModel: Send + Sync { None } + /// Whether this model supports extended thinking. + fn supports_thinking(&self) -> bool { + false + } + /// Whether this model supports images fn supports_images(&self) -> bool; diff --git a/crates/language_models/Cargo.toml b/crates/language_models/Cargo.toml index 1dd7162d3d..3b0cf3a31d 100644 --- a/crates/language_models/Cargo.toml +++ b/crates/language_models/Cargo.toml @@ -32,6 +32,7 @@ credentials_provider.workspace = true deepseek = { workspace = true, features = ["schemars"] } extension.workspace = true extension_host.workspace = true +feature_flags.workspace = true fs.workspace = true futures.workspace = true google_ai = { workspace = true, features = ["schemars"] } diff --git a/crates/language_models/src/provider/cloud.rs b/crates/language_models/src/provider/cloud.rs index 03f7ba125f..33afd7ee46 100644 --- a/crates/language_models/src/provider/cloud.rs +++ b/crates/language_models/src/provider/cloud.rs @@ -9,6 +9,7 @@ use cloud_llm_client::{ ListModelsResponse, Plan, PlanV2, SERVER_SUPPORTS_STATUS_MESSAGES_HEADER_NAME, ZED_VERSION_HEADER_NAME, }; +use feature_flags::{CloudThinkingToggleFeatureFlag, FeatureFlagAppExt as _}; use futures::{ AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream, }; @@ -164,20 +165,27 @@ impl State { state.update(cx, |_, cx| cx.notify()) }) } + fn update_models(&mut self, response: ListModelsResponse, cx: &mut Context) { + let is_thinking_toggle_enabled = cx.has_flag::(); + let mut models = Vec::new(); for model in response.models { models.push(Arc::new(model.clone())); - // Right now we represent thinking variants of models as separate models on the client, - // so we need to insert variants for any model that supports thinking. - if model.supports_thinking { - models.push(Arc::new(cloud_llm_client::LanguageModel { - id: cloud_llm_client::LanguageModelId(format!("{}-thinking", model.id).into()), - display_name: format!("{} Thinking", model.display_name), - ..model - })); + if !is_thinking_toggle_enabled { + // Right now we represent thinking variants of models as separate models on the client, + // so we need to insert variants for any model that supports thinking. + if model.supports_thinking { + models.push(Arc::new(cloud_llm_client::LanguageModel { + id: cloud_llm_client::LanguageModelId( + format!("{}-thinking", model.id).into(), + ), + display_name: format!("{} Thinking", model.display_name), + ..model + })); + } } } @@ -570,6 +578,10 @@ impl LanguageModel for CloudLanguageModel { self.model.supports_images } + fn supports_thinking(&self) -> bool { + self.model.supports_thinking + } + fn supports_streaming_tools(&self) -> bool { self.model.supports_streaming_tools } @@ -721,6 +733,13 @@ impl LanguageModel for CloudLanguageModel { let bypass_rate_limit = request.bypass_rate_limit; let app_version = Some(cx.update(|cx| AppVersion::global(cx))); let thinking_allowed = request.thinking_allowed; + let is_thinking_toggle_enabled = + cx.update(|cx| cx.has_flag::()); + let enable_thinking = if is_thinking_toggle_enabled { + thinking_allowed && self.model.supports_thinking + } else { + thinking_allowed && self.model.id.0.ends_with("-thinking") + }; let provider_name = provider_name(&self.model.provider); match self.model.provider { cloud_llm_client::LanguageModelProvider::Anthropic => { @@ -729,7 +748,7 @@ impl LanguageModel for CloudLanguageModel { self.model.id.to_string(), 1.0, self.model.max_output_tokens as u64, - if thinking_allowed && self.model.id.0.ends_with("-thinking") { + if enable_thinking { AnthropicModelMode::Thinking { budget_tokens: Some(4_096), }