diff --git a/Cargo.lock b/Cargo.lock index e3e01249fa6eb9e1d7593845d55fc43d4541665c..628074a875a4a7d3ac0fea87dc5b1cf3bd85fcf1 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 02fa67ef86828938c39250dca86c9eac621c77fb..dbcbe8eda0358ff71997dfe695a871efad954ac6 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 3f03ecf32257fb2c5650de0289e37ac3a6efe139..e2aa6547539390f3ed018e220b12a212e494b680 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 ee657799f09cabbb1b01b64ec084e848090c2160..e61f56ec6f208d87139acd3c3157b94aa4ae5e3d 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 8602b253e6b895249471ccd9c995c3db35a41c8e..eac4db09d4c6dd96901cadcdde9bbe881e95d50f 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 1dd7162d3d93a68f981489cfb38edb71b2c3e504..3b0cf3a31d0718f89994c4cd3cb2bf82f4ec4408 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 03f7ba125f54ef0ae18c53d094fa4798c86b24aa..33afd7ee46a8c92b636cda40a1cacfa36f94fc07 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), }