Remove ACP feature flags (#38055)

Bennet Bo Fenner created

This removes the `gemini-and-native` and `claude-code` feature flags.
Also, I removed a bunch of unused agent1 code that we do not need
anymore.

Initially I wanted to remove much more of the `agent` code, but noticed
some things that we need to figure out first:
- The inline assistant + context strip use `Thread`/`ContextStore`
directly
- We need some replacement for `ToolWorkingSet`, so we can access
available tools (as well as context server tools) in other places, e.g.
the agent configuration and the configure profile modal

Release Notes:

- N/A

Change summary

Cargo.lock                                                            |    6 
crates/agent/src/history_store.rs                                     |   39 
crates/agent_settings/Cargo.toml                                      |    3 
crates/agent_settings/src/agent_profile.rs                            |   71 
crates/agent_ui/Cargo.toml                                            |    2 
crates/agent_ui/src/acp/thread_view.rs                                |    2 
crates/agent_ui/src/active_thread.rs                                  | 4110 
crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs      |    3 
crates/agent_ui/src/agent_diff.rs                                     |  145 
crates/agent_ui/src/agent_model_selector.rs                           |   23 
crates/agent_ui/src/agent_panel.rs                                    |  763 
crates/agent_ui/src/agent_ui.rs                                       |   10 
crates/agent_ui/src/context_picker.rs                                 |  111 
crates/agent_ui/src/context_strip.rs                                  |  153 
crates/agent_ui/src/debug.rs                                          |  124 
crates/agent_ui/src/inline_prompt_editor.rs                           |   30 
crates/agent_ui/src/message_editor.rs                                 | 1511 
crates/agent_ui/src/profile_selector.rs                               |    6 
crates/agent_ui/src/thread_history.rs                                 |  912 
crates/agent_ui/src/tool_compatibility.rs                             |   94 
crates/agent_ui/src/ui.rs                                             |    3 
crates/agent_ui/src/ui/context_pill.rs                                |  130 
crates/agent_ui/src/ui/preview.rs                                     |    5 
crates/agent_ui/src/ui/preview/agent_preview.rs                       |   89 
crates/agent_ui/src/ui/usage_callout.rs                               |    0 
crates/feature_flags/src/flags.rs                                     |   25 
crates/zed/Cargo.toml                                                 |    2 
crates/zed/src/zed/component_preview.rs                               |  161 
crates/zed/src/zed/component_preview/preview_support.rs               |    1 
crates/zed/src/zed/component_preview/preview_support/active_thread.rs |  100 
30 files changed, 337 insertions(+), 8,297 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -330,6 +330,7 @@ dependencies = [
  "anyhow",
  "cloud_llm_client",
  "collections",
+ "convert_case 0.8.0",
  "fs",
  "gpui",
  "language_model",
@@ -339,6 +340,7 @@ dependencies = [
  "serde_json",
  "serde_json_lenient",
  "settings",
+ "util",
  "workspace-hack",
 ]
 
@@ -383,7 +385,6 @@ dependencies = [
  "html_to_markdown",
  "http_client",
  "indoc",
- "inventory",
  "itertools 0.14.0",
  "jsonschema",
  "language",
@@ -434,7 +435,6 @@ dependencies = [
  "url",
  "urlencoding",
  "util",
- "uuid",
  "watch",
  "workspace",
  "workspace-hack",
@@ -20505,7 +20505,6 @@ dependencies = [
  "ashpd",
  "askpass",
  "assets",
- "assistant_tool",
  "assistant_tools",
  "audio",
  "auto_update",
@@ -20550,7 +20549,6 @@ dependencies = [
  "gpui_tokio",
  "http_client",
  "image_viewer",
- "indoc",
  "inspector_ui",
  "install_cli",
  "itertools 0.14.0",

crates/agent/src/history_store.rs 🔗

@@ -1,7 +1,4 @@
-use crate::{
-    ThreadId,
-    thread_store::{SerializedThreadMetadata, ThreadStore},
-};
+use crate::{ThreadId, thread_store::SerializedThreadMetadata};
 use anyhow::{Context as _, Result};
 use assistant_context::SavedContextMetadata;
 use chrono::{DateTime, Utc};
@@ -61,7 +58,6 @@ enum SerializedRecentOpen {
 }
 
 pub struct HistoryStore {
-    thread_store: Entity<ThreadStore>,
     context_store: Entity<assistant_context::ContextStore>,
     recently_opened_entries: VecDeque<HistoryEntryId>,
     _subscriptions: Vec<gpui::Subscription>,
@@ -70,15 +66,11 @@ pub struct HistoryStore {
 
 impl HistoryStore {
     pub fn new(
-        thread_store: Entity<ThreadStore>,
         context_store: Entity<assistant_context::ContextStore>,
         initial_recent_entries: impl IntoIterator<Item = HistoryEntryId>,
         cx: &mut Context<Self>,
     ) -> Self {
-        let subscriptions = vec![
-            cx.observe(&thread_store, |_, _, cx| cx.notify()),
-            cx.observe(&context_store, |_, _, cx| cx.notify()),
-        ];
+        let subscriptions = vec![cx.observe(&context_store, |_, _, cx| cx.notify())];
 
         cx.spawn(async move |this, cx| {
             let entries = Self::load_recently_opened_entries(cx).await.log_err()?;
@@ -96,7 +88,6 @@ impl HistoryStore {
         .detach();
 
         Self {
-            thread_store,
             context_store,
             recently_opened_entries: initial_recent_entries.into_iter().collect(),
             _subscriptions: subscriptions,
@@ -112,13 +103,6 @@ impl HistoryStore {
             return history_entries;
         }
 
-        history_entries.extend(
-            self.thread_store
-                .read(cx)
-                .reverse_chronological_threads()
-                .cloned()
-                .map(HistoryEntry::Thread),
-        );
         history_entries.extend(
             self.context_store
                 .read(cx)
@@ -141,22 +125,6 @@ impl HistoryStore {
             return Vec::new();
         }
 
-        let thread_entries = self
-            .thread_store
-            .read(cx)
-            .reverse_chronological_threads()
-            .flat_map(|thread| {
-                self.recently_opened_entries
-                    .iter()
-                    .enumerate()
-                    .flat_map(|(index, entry)| match entry {
-                        HistoryEntryId::Thread(id) if &thread.id == id => {
-                            Some((index, HistoryEntry::Thread(thread.clone())))
-                        }
-                        _ => None,
-                    })
-            });
-
         let context_entries =
             self.context_store
                 .read(cx)
@@ -173,8 +141,7 @@ impl HistoryStore {
                         })
                 });
 
-        thread_entries
-            .chain(context_entries)
+        context_entries
             // optimization to halt iteration early
             .take(self.recently_opened_entries.len())
             .sorted_unstable_by_key(|(index, _)| *index)

crates/agent_settings/Cargo.toml 🔗

@@ -15,11 +15,14 @@ path = "src/agent_settings.rs"
 anyhow.workspace = true
 cloud_llm_client.workspace = true
 collections.workspace = true
+convert_case.workspace = true
+fs.workspace = true
 gpui.workspace = true
 language_model.workspace = true
 schemars.workspace = true
 serde.workspace = true
 settings.workspace = true
+util.workspace = true
 workspace-hack.workspace = true
 
 [dev-dependencies]

crates/agent_settings/src/agent_profile.rs 🔗

@@ -1,9 +1,15 @@
 use std::sync::Arc;
 
 use collections::IndexMap;
-use gpui::SharedString;
+use convert_case::{Case, Casing as _};
+use fs::Fs;
+use gpui::{App, SharedString};
 use schemars::JsonSchema;
 use serde::{Deserialize, Serialize};
+use settings::{Settings as _, update_settings_file};
+use util::ResultExt as _;
+
+use crate::AgentSettings;
 
 pub mod builtin_profiles {
     use super::AgentProfileId;
@@ -38,6 +44,69 @@ impl Default for AgentProfileId {
     }
 }
 
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct AgentProfile {
+    id: AgentProfileId,
+}
+
+pub type AvailableProfiles = IndexMap<AgentProfileId, SharedString>;
+
+impl AgentProfile {
+    pub fn new(id: AgentProfileId) -> Self {
+        Self { id }
+    }
+
+    pub fn id(&self) -> &AgentProfileId {
+        &self.id
+    }
+
+    /// Saves a new profile to the settings.
+    pub fn create(
+        name: String,
+        base_profile_id: Option<AgentProfileId>,
+        fs: Arc<dyn Fs>,
+        cx: &App,
+    ) -> AgentProfileId {
+        let id = AgentProfileId(name.to_case(Case::Kebab).into());
+
+        let base_profile =
+            base_profile_id.and_then(|id| AgentSettings::get_global(cx).profiles.get(&id).cloned());
+
+        let profile_settings = AgentProfileSettings {
+            name: name.into(),
+            tools: base_profile
+                .as_ref()
+                .map(|profile| profile.tools.clone())
+                .unwrap_or_default(),
+            enable_all_context_servers: base_profile
+                .as_ref()
+                .map(|profile| profile.enable_all_context_servers)
+                .unwrap_or_default(),
+            context_servers: base_profile
+                .map(|profile| profile.context_servers)
+                .unwrap_or_default(),
+        };
+
+        update_settings_file::<AgentSettings>(fs, cx, {
+            let id = id.clone();
+            move |settings, _cx| {
+                settings.create_profile(id, profile_settings).log_err();
+            }
+        });
+
+        id
+    }
+
+    /// Returns a map of AgentProfileIds to their names
+    pub fn available_profiles(cx: &App) -> AvailableProfiles {
+        let mut profiles = AvailableProfiles::default();
+        for (id, profile) in AgentSettings::get_global(cx).profiles.iter() {
+            profiles.insert(id.clone(), profile.name.clone());
+        }
+        profiles
+    }
+}
+
 /// A profile for the Zed Agent that controls its behavior.
 #[derive(Debug, Clone)]
 pub struct AgentProfileSettings {

crates/agent_ui/Cargo.toml 🔗

@@ -52,7 +52,6 @@ gpui.workspace = true
 html_to_markdown.workspace = true
 http_client.workspace = true
 indoc.workspace = true
-inventory.workspace = true
 itertools.workspace = true
 jsonschema.workspace = true
 language.workspace = true
@@ -98,7 +97,6 @@ ui_input.workspace = true
 url.workspace = true
 urlencoding.workspace = true
 util.workspace = true
-uuid.workspace = true
 watch.workspace = true
 workspace-hack.workspace = true
 workspace.workspace = true

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -62,9 +62,9 @@ use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};
 use crate::agent_diff::AgentDiff;
 use crate::profile_selector::{ProfileProvider, ProfileSelector};
 
-use crate::ui::preview::UsageCallout;
 use crate::ui::{
     AgentNotification, AgentNotificationEvent, BurnModeTooltip, UnavailableEditingTooltip,
+    UsageCallout,
 };
 use crate::{
     AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, ContinueThread, ContinueWithBurnMode,

crates/agent_ui/src/active_thread.rs 🔗

@@ -1,4110 +0,0 @@
-use crate::context_picker::{ContextPicker, MentionLink};
-use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
-use crate::message_editor::{extract_message_creases, insert_message_creases};
-use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill};
-use crate::{AgentPanel, ModelUsageContext};
-use agent::{
-    ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore,
-    Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadStore, ThreadSummary,
-    context::{self, AgentContextHandle, RULES_ICON},
-    thread_store::RulesLoadingError,
-    tool_use::{PendingToolUseStatus, ToolUse},
-};
-use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
-use anyhow::Context as _;
-use assistant_tool::ToolUseStatus;
-use audio::{Audio, Sound};
-use cloud_llm_client::CompletionIntent;
-use collections::{HashMap, HashSet};
-use editor::actions::{MoveUp, Paste};
-use editor::scroll::Autoscroll;
-use editor::{Editor, EditorElement, EditorEvent, EditorStyle, MultiBuffer, SelectionEffects};
-use gpui::{
-    AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
-    ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
-    ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
-    StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle,
-    WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, pulsating_between,
-};
-use language::{Buffer, Language, LanguageRegistry};
-use language_model::{
-    LanguageModelRequestMessage, LanguageModelToolUseId, MessageContent, Role, StopReason,
-};
-use markdown::parser::{CodeBlockKind, CodeBlockMetadata};
-use markdown::{
-    HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, PathWithRange,
-};
-use project::{ProjectEntryId, ProjectItem as _};
-use rope::Point;
-use settings::{Settings as _, SettingsStore, update_settings_file};
-use std::ffi::OsStr;
-use std::path::Path;
-use std::rc::Rc;
-use std::sync::Arc;
-use std::time::Duration;
-use text::ToPoint;
-use theme::ThemeSettings;
-use ui::{
-    Banner, CommonAnimationExt, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar,
-    ScrollbarState, TextSize, Tooltip, prelude::*,
-};
-use util::ResultExt as _;
-use util::markdown::MarkdownCodeBlock;
-use workspace::{CollaboratorId, Workspace};
-use zed_actions::assistant::OpenRulesLibrary;
-
-const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
-const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
-const RESPONSE_PADDING_X: Pixels = px(19.);
-
-pub struct ActiveThread {
-    context_store: Entity<ContextStore>,
-    language_registry: Arc<LanguageRegistry>,
-    thread_store: Entity<ThreadStore>,
-    text_thread_store: Entity<TextThreadStore>,
-    thread: Entity<Thread>,
-    workspace: WeakEntity<Workspace>,
-    save_thread_task: Option<Task<()>>,
-    messages: Vec<MessageId>,
-    list_state: ListState,
-    scrollbar_state: ScrollbarState,
-    rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
-    rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
-    editing_message: Option<(MessageId, EditingMessageState)>,
-    expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
-    expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
-    expanded_code_blocks: HashMap<(MessageId, usize), bool>,
-    last_error: Option<ThreadError>,
-    notifications: Vec<WindowHandle<AgentNotification>>,
-    copied_code_block_ids: HashSet<(MessageId, usize)>,
-    _subscriptions: Vec<Subscription>,
-    notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
-    open_feedback_editors: HashMap<MessageId, Entity<Editor>>,
-    _load_edited_message_context_task: Option<Task<()>>,
-}
-
-struct RenderedMessage {
-    language_registry: Arc<LanguageRegistry>,
-    segments: Vec<RenderedMessageSegment>,
-}
-
-#[derive(Clone)]
-struct RenderedToolUse {
-    label: Entity<Markdown>,
-    input: Entity<Markdown>,
-    output: Entity<Markdown>,
-}
-
-impl RenderedMessage {
-    fn from_segments(
-        segments: &[MessageSegment],
-        language_registry: Arc<LanguageRegistry>,
-        cx: &mut App,
-    ) -> Self {
-        let mut this = Self {
-            language_registry,
-            segments: Vec::with_capacity(segments.len()),
-        };
-        for segment in segments {
-            this.push_segment(segment, cx);
-        }
-        this
-    }
-
-    fn append_thinking(&mut self, text: &String, cx: &mut App) {
-        if let Some(RenderedMessageSegment::Thinking {
-            content,
-            scroll_handle,
-        }) = self.segments.last_mut()
-        {
-            content.update(cx, |markdown, cx| {
-                markdown.append(text, cx);
-            });
-            scroll_handle.scroll_to_bottom();
-        } else {
-            self.segments.push(RenderedMessageSegment::Thinking {
-                content: parse_markdown(text.into(), self.language_registry.clone(), cx),
-                scroll_handle: ScrollHandle::default(),
-            });
-        }
-    }
-
-    fn append_text(&mut self, text: &String, cx: &mut App) {
-        if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() {
-            markdown.update(cx, |markdown, cx| markdown.append(text, cx));
-        } else {
-            self.segments
-                .push(RenderedMessageSegment::Text(parse_markdown(
-                    SharedString::from(text),
-                    self.language_registry.clone(),
-                    cx,
-                )));
-        }
-    }
-
-    fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) {
-        match segment {
-            MessageSegment::Thinking { text, .. } => {
-                self.segments.push(RenderedMessageSegment::Thinking {
-                    content: parse_markdown(text.into(), self.language_registry.clone(), cx),
-                    scroll_handle: ScrollHandle::default(),
-                })
-            }
-            MessageSegment::Text(text) => {
-                self.segments
-                    .push(RenderedMessageSegment::Text(parse_markdown(
-                        text.into(),
-                        self.language_registry.clone(),
-                        cx,
-                    )))
-            }
-            MessageSegment::RedactedThinking(_) => {}
-        };
-    }
-}
-
-enum RenderedMessageSegment {
-    Thinking {
-        content: Entity<Markdown>,
-        scroll_handle: ScrollHandle,
-    },
-    Text(Entity<Markdown>),
-}
-
-fn parse_markdown(
-    text: SharedString,
-    language_registry: Arc<LanguageRegistry>,
-    cx: &mut App,
-) -> Entity<Markdown> {
-    cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))
-}
-
-pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
-    let theme_settings = ThemeSettings::get_global(cx);
-    let colors = cx.theme().colors();
-    let ui_font_size = TextSize::Default.rems(cx);
-    let buffer_font_size = TextSize::Small.rems(cx);
-    let mut text_style = window.text_style();
-    let line_height = buffer_font_size * 1.75;
-
-    text_style.refine(&TextStyleRefinement {
-        font_family: Some(theme_settings.ui_font.family.clone()),
-        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
-        font_features: Some(theme_settings.ui_font.features.clone()),
-        font_size: Some(ui_font_size.into()),
-        line_height: Some(line_height.into()),
-        color: Some(cx.theme().colors().text),
-        ..Default::default()
-    });
-
-    MarkdownStyle {
-        base_text_style: text_style.clone(),
-        syntax: cx.theme().syntax().clone(),
-        selection_background_color: cx.theme().colors().element_selection_background,
-        code_block_overflow_x_scroll: true,
-        table_overflow_x_scroll: true,
-        heading_level_styles: Some(HeadingLevelStyles {
-            h1: Some(TextStyleRefinement {
-                font_size: Some(rems(1.15).into()),
-                ..Default::default()
-            }),
-            h2: Some(TextStyleRefinement {
-                font_size: Some(rems(1.1).into()),
-                ..Default::default()
-            }),
-            h3: Some(TextStyleRefinement {
-                font_size: Some(rems(1.05).into()),
-                ..Default::default()
-            }),
-            h4: Some(TextStyleRefinement {
-                font_size: Some(rems(1.).into()),
-                ..Default::default()
-            }),
-            h5: Some(TextStyleRefinement {
-                font_size: Some(rems(0.95).into()),
-                ..Default::default()
-            }),
-            h6: Some(TextStyleRefinement {
-                font_size: Some(rems(0.875).into()),
-                ..Default::default()
-            }),
-        }),
-        code_block: StyleRefinement {
-            padding: EdgesRefinement {
-                top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
-                left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
-                right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
-                bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
-            },
-            background: Some(colors.editor_background.into()),
-            text: Some(TextStyleRefinement {
-                font_family: Some(theme_settings.buffer_font.family.clone()),
-                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
-                font_features: Some(theme_settings.buffer_font.features.clone()),
-                font_size: Some(buffer_font_size.into()),
-                ..Default::default()
-            }),
-            ..Default::default()
-        },
-        inline_code: TextStyleRefinement {
-            font_family: Some(theme_settings.buffer_font.family.clone()),
-            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
-            font_features: Some(theme_settings.buffer_font.features.clone()),
-            font_size: Some(buffer_font_size.into()),
-            background_color: Some(colors.editor_foreground.opacity(0.08)),
-            ..Default::default()
-        },
-        link: TextStyleRefinement {
-            background_color: Some(colors.editor_foreground.opacity(0.025)),
-            underline: Some(UnderlineStyle {
-                color: Some(colors.text_accent.opacity(0.5)),
-                thickness: px(1.),
-                ..Default::default()
-            }),
-            ..Default::default()
-        },
-        link_callback: Some(Rc::new(move |url, cx| {
-            if MentionLink::is_valid(url) {
-                let colors = cx.theme().colors();
-                Some(TextStyleRefinement {
-                    background_color: Some(colors.element_background),
-                    ..Default::default()
-                })
-            } else {
-                None
-            }
-        })),
-        ..Default::default()
-    }
-}
-
-fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
-    let theme_settings = ThemeSettings::get_global(cx);
-    let colors = cx.theme().colors();
-    let ui_font_size = TextSize::Default.rems(cx);
-    let buffer_font_size = TextSize::Small.rems(cx);
-    let mut text_style = window.text_style();
-
-    text_style.refine(&TextStyleRefinement {
-        font_family: Some(theme_settings.ui_font.family.clone()),
-        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
-        font_features: Some(theme_settings.ui_font.features.clone()),
-        font_size: Some(ui_font_size.into()),
-        color: Some(cx.theme().colors().text),
-        ..Default::default()
-    });
-
-    MarkdownStyle {
-        base_text_style: text_style,
-        syntax: cx.theme().syntax().clone(),
-        selection_background_color: cx.theme().colors().element_selection_background,
-        code_block_overflow_x_scroll: false,
-        code_block: StyleRefinement {
-            margin: EdgesRefinement::default(),
-            padding: EdgesRefinement::default(),
-            background: Some(colors.editor_background.into()),
-            border_color: None,
-            border_widths: EdgesRefinement::default(),
-            text: Some(TextStyleRefinement {
-                font_family: Some(theme_settings.buffer_font.family.clone()),
-                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
-                font_features: Some(theme_settings.buffer_font.features.clone()),
-                font_size: Some(buffer_font_size.into()),
-                ..Default::default()
-            }),
-            ..Default::default()
-        },
-        inline_code: TextStyleRefinement {
-            font_family: Some(theme_settings.buffer_font.family.clone()),
-            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
-            font_features: Some(theme_settings.buffer_font.features.clone()),
-            font_size: Some(TextSize::XSmall.rems(cx).into()),
-            ..Default::default()
-        },
-        heading: StyleRefinement {
-            text: Some(TextStyleRefinement {
-                font_size: Some(ui_font_size.into()),
-                ..Default::default()
-            }),
-            ..Default::default()
-        },
-        ..Default::default()
-    }
-}
-
-fn render_markdown_code_block(
-    message_id: MessageId,
-    ix: usize,
-    kind: &CodeBlockKind,
-    parsed_markdown: &ParsedMarkdown,
-    metadata: CodeBlockMetadata,
-    active_thread: Entity<ActiveThread>,
-    workspace: WeakEntity<Workspace>,
-    _window: &Window,
-    cx: &App,
-) -> Div {
-    let label_size = rems(0.8125);
-
-    let label = match kind {
-        CodeBlockKind::Indented => None,
-        CodeBlockKind::Fenced => Some(
-            h_flex()
-                .px_1()
-                .gap_1()
-                .child(
-                    Icon::new(IconName::Code)
-                        .color(Color::Muted)
-                        .size(IconSize::XSmall),
-                )
-                .child(div().text_size(label_size).child("Plain Text"))
-                .into_any_element(),
-        ),
-        CodeBlockKind::FencedLang(raw_language_name) => Some(render_code_language(
-            parsed_markdown.languages_by_name.get(raw_language_name),
-            raw_language_name.clone(),
-            cx,
-        )),
-        CodeBlockKind::FencedSrc(path_range) => path_range.path.file_name().map(|file_name| {
-            // We tell the model to use /dev/null for the path instead of using ```language
-            // because otherwise it consistently fails to use code citations.
-            if path_range.path.starts_with("/dev/null") {
-                let ext = path_range
-                    .path
-                    .extension()
-                    .and_then(OsStr::to_str)
-                    .map(|str| SharedString::new(str.to_string()))
-                    .unwrap_or_default();
-
-                render_code_language(
-                    parsed_markdown
-                        .languages_by_path
-                        .get(&path_range.path)
-                        .or_else(|| parsed_markdown.languages_by_name.get(&ext)),
-                    ext,
-                    cx,
-                )
-            } else {
-                let content = if let Some(parent) = path_range.path.parent() {
-                    let file_name = file_name.to_string_lossy().to_string();
-                    let path = parent.to_string_lossy().to_string();
-                    let path_and_file = format!("{}/{}", path, file_name);
-
-                    h_flex()
-                        .id(("code-block-header-label", ix))
-                        .ml_1()
-                        .gap_1()
-                        .child(div().text_size(label_size).child(file_name))
-                        .child(Label::new(path).color(Color::Muted).size(LabelSize::Small))
-                        .tooltip(move |window, cx| {
-                            Tooltip::with_meta(
-                                "Jump to File",
-                                None,
-                                path_and_file.clone(),
-                                window,
-                                cx,
-                            )
-                        })
-                        .into_any_element()
-                } else {
-                    div()
-                        .ml_1()
-                        .text_size(label_size)
-                        .child(path_range.path.to_string_lossy().to_string())
-                        .into_any_element()
-                };
-
-                h_flex()
-                    .id(("code-block-header-button", ix))
-                    .w_full()
-                    .max_w_full()
-                    .px_1()
-                    .gap_0p5()
-                    .cursor_pointer()
-                    .rounded_sm()
-                    .hover(|item| item.bg(cx.theme().colors().element_hover.opacity(0.5)))
-                    .child(
-                        h_flex()
-                            .gap_0p5()
-                            .children(
-                                file_icons::FileIcons::get_icon(&path_range.path, cx)
-                                    .map(Icon::from_path)
-                                    .map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)),
-                            )
-                            .child(content)
-                            .child(
-                                Icon::new(IconName::ArrowUpRight)
-                                    .size(IconSize::Small)
-                                    .color(Color::Ignored),
-                            ),
-                    )
-                    .on_click({
-                        let path_range = path_range.clone();
-                        move |_, window, cx| {
-                            workspace
-                                .update(cx, |workspace, cx| {
-                                    open_path(&path_range, window, workspace, cx)
-                                })
-                                .ok();
-                        }
-                    })
-                    .into_any_element()
-            }
-        }),
-    };
-
-    let codeblock_was_copied = active_thread
-        .read(cx)
-        .copied_code_block_ids
-        .contains(&(message_id, ix));
-
-    let is_expanded = active_thread.read(cx).is_codeblock_expanded(message_id, ix);
-
-    let codeblock_header_bg = cx
-        .theme()
-        .colors()
-        .element_background
-        .blend(cx.theme().colors().editor_foreground.opacity(0.025));
-
-    let control_buttons = h_flex()
-        .visible_on_hover(CODEBLOCK_CONTAINER_GROUP)
-        .absolute()
-        .top_0()
-        .right_0()
-        .h_full()
-        .bg(codeblock_header_bg)
-        .rounded_tr_md()
-        .px_1()
-        .gap_1()
-        .child(
-            IconButton::new(
-                ("copy-markdown-code", ix),
-                if codeblock_was_copied {
-                    IconName::Check
-                } else {
-                    IconName::Copy
-                },
-            )
-            .icon_color(Color::Muted)
-            .shape(ui::IconButtonShape::Square)
-            .tooltip(Tooltip::text("Copy Code"))
-            .on_click({
-                let active_thread = active_thread.clone();
-                let parsed_markdown = parsed_markdown.clone();
-                let code_block_range = metadata.content_range;
-                move |_event, _window, cx| {
-                    active_thread.update(cx, |this, cx| {
-                        this.copied_code_block_ids.insert((message_id, ix));
-
-                        let code = parsed_markdown.source()[code_block_range.clone()].to_string();
-                        cx.write_to_clipboard(ClipboardItem::new_string(code));
-
-                        cx.spawn(async move |this, cx| {
-                            cx.background_executor().timer(Duration::from_secs(2)).await;
-
-                            cx.update(|cx| {
-                                this.update(cx, |this, cx| {
-                                    this.copied_code_block_ids.remove(&(message_id, ix));
-                                    cx.notify();
-                                })
-                            })
-                            .ok();
-                        })
-                        .detach();
-                    });
-                }
-            }),
-        )
-        .child(
-            IconButton::new(
-                ("expand-collapse-code", ix),
-                if is_expanded {
-                    IconName::ChevronUp
-                } else {
-                    IconName::ChevronDown
-                },
-            )
-            .icon_color(Color::Muted)
-            .shape(ui::IconButtonShape::Square)
-            .tooltip(Tooltip::text(if is_expanded {
-                "Collapse Code"
-            } else {
-                "Expand Code"
-            }))
-            .on_click({
-                move |_event, _window, cx| {
-                    active_thread.update(cx, |this, cx| {
-                        this.toggle_codeblock_expanded(message_id, ix);
-                        cx.notify();
-                    });
-                }
-            }),
-        );
-
-    let codeblock_header = h_flex()
-        .relative()
-        .p_1()
-        .gap_1()
-        .justify_between()
-        .bg(codeblock_header_bg)
-        .map(|this| {
-            if !is_expanded {
-                this.rounded_md()
-            } else {
-                this.rounded_t_md()
-                    .border_b_1()
-                    .border_color(cx.theme().colors().border.opacity(0.6))
-            }
-        })
-        .children(label)
-        .child(control_buttons);
-
-    v_flex()
-        .group(CODEBLOCK_CONTAINER_GROUP)
-        .my_2()
-        .overflow_hidden()
-        .rounded_md()
-        .border_1()
-        .border_color(cx.theme().colors().border.opacity(0.6))
-        .bg(cx.theme().colors().editor_background)
-        .child(codeblock_header)
-        .when(!is_expanded, |this| this.h(rems_from_px(31.)))
-}
-
-fn open_path(
-    path_range: &PathWithRange,
-    window: &mut Window,
-    workspace: &mut Workspace,
-    cx: &mut Context<'_, Workspace>,
-) {
-    let Some(project_path) = workspace
-        .project()
-        .read(cx)
-        .find_project_path(&path_range.path, cx)
-    else {
-        return; // TODO instead of just bailing out, open that path in a buffer.
-    };
-
-    let Some(target) = path_range.range.as_ref().map(|range| {
-        Point::new(
-            // Line number is 1-based
-            range.start.line.saturating_sub(1),
-            range.start.col.unwrap_or(0),
-        )
-    }) else {
-        return;
-    };
-    let open_task = workspace.open_path(project_path, None, true, window, cx);
-    window
-        .spawn(cx, async move |cx| {
-            let item = open_task.await?;
-            if let Some(active_editor) = item.downcast::<Editor>() {
-                active_editor
-                    .update_in(cx, |editor, window, cx| {
-                        editor.go_to_singleton_buffer_point(target, window, cx);
-                    })
-                    .ok();
-            }
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-}
-
-fn render_code_language(
-    language: Option<&Arc<Language>>,
-    name_fallback: SharedString,
-    cx: &App,
-) -> AnyElement {
-    let icon_path = language.and_then(|language| {
-        language
-            .config()
-            .matcher
-            .path_suffixes
-            .iter()
-            .find_map(|extension| file_icons::FileIcons::get_icon(Path::new(extension), cx))
-            .map(Icon::from_path)
-    });
-
-    let language_label = language
-        .map(|language| language.name().into())
-        .unwrap_or(name_fallback);
-
-    let label_size = rems(0.8125);
-
-    h_flex()
-        .px_1()
-        .gap_1p5()
-        .children(icon_path.map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)))
-        .child(div().text_size(label_size).child(language_label))
-        .into_any_element()
-}
-
-fn open_markdown_link(
-    text: SharedString,
-    workspace: WeakEntity<Workspace>,
-    window: &mut Window,
-    cx: &mut App,
-) {
-    let Some(workspace) = workspace.upgrade() else {
-        cx.open_url(&text);
-        return;
-    };
-
-    match MentionLink::try_parse(&text, &workspace, cx) {
-        Some(MentionLink::File(path, entry)) => workspace.update(cx, |workspace, cx| {
-            if entry.is_dir() {
-                workspace.project().update(cx, |_, cx| {
-                    cx.emit(project::Event::RevealInProjectPanel(entry.id));
-                })
-            } else {
-                workspace
-                    .open_path(path, None, true, window, cx)
-                    .detach_and_log_err(cx);
-            }
-        }),
-        Some(MentionLink::Symbol(path, symbol_name)) => {
-            let open_task = workspace.update(cx, |workspace, cx| {
-                workspace.open_path(path, None, true, window, cx)
-            });
-            window
-                .spawn(cx, async move |cx| {
-                    let active_editor = open_task
-                        .await?
-                        .downcast::<Editor>()
-                        .context("Item is not an editor")?;
-                    active_editor.update_in(cx, |editor, window, cx| {
-                        let symbol_range = editor
-                            .buffer()
-                            .read(cx)
-                            .snapshot(cx)
-                            .outline(None)
-                            .and_then(|outline| {
-                                outline
-                                    .find_most_similar(&symbol_name)
-                                    .map(|(_, item)| item.range.clone())
-                            })
-                            .context("Could not find matching symbol")?;
-
-                        editor.change_selections(
-                            SelectionEffects::scroll(Autoscroll::center()),
-                            window,
-                            cx,
-                            |s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]),
-                        );
-                        anyhow::Ok(())
-                    })
-                })
-                .detach_and_log_err(cx);
-        }
-        Some(MentionLink::Selection(path, line_range)) => {
-            let open_task = workspace.update(cx, |workspace, cx| {
-                workspace.open_path(path, None, true, window, cx)
-            });
-            window
-                .spawn(cx, async move |cx| {
-                    let active_editor = open_task
-                        .await?
-                        .downcast::<Editor>()
-                        .context("Item is not an editor")?;
-                    active_editor.update_in(cx, |editor, window, cx| {
-                        editor.change_selections(
-                            SelectionEffects::scroll(Autoscroll::center()),
-                            window,
-                            cx,
-                            |s| {
-                                s.select_ranges([Point::new(line_range.start as u32, 0)
-                                    ..Point::new(line_range.start as u32, 0)])
-                            },
-                        );
-                        anyhow::Ok(())
-                    })
-                })
-                .detach_and_log_err(cx);
-        }
-        Some(MentionLink::Thread(thread_id)) => workspace.update(cx, |workspace, cx| {
-            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                panel.update(cx, |panel, cx| {
-                    panel
-                        .open_thread_by_id(&thread_id, window, cx)
-                        .detach_and_log_err(cx)
-                });
-            }
-        }),
-        Some(MentionLink::TextThread(path)) => workspace.update(cx, |workspace, cx| {
-            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                panel.update(cx, |panel, cx| {
-                    panel
-                        .open_saved_prompt_editor(path, window, cx)
-                        .detach_and_log_err(cx);
-                });
-            }
-        }),
-        Some(MentionLink::Fetch(url)) => cx.open_url(&url),
-        Some(MentionLink::Rule(prompt_id)) => window.dispatch_action(
-            Box::new(OpenRulesLibrary {
-                prompt_to_select: Some(prompt_id.0),
-            }),
-            cx,
-        ),
-        None => cx.open_url(&text),
-    }
-}
-
-struct EditingMessageState {
-    editor: Entity<Editor>,
-    context_strip: Entity<ContextStrip>,
-    context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
-    last_estimated_token_count: Option<u64>,
-    _subscriptions: [Subscription; 2],
-    _update_token_count_task: Option<Task<()>>,
-}
-
-impl ActiveThread {
-    pub fn new(
-        thread: Entity<Thread>,
-        thread_store: Entity<ThreadStore>,
-        text_thread_store: Entity<TextThreadStore>,
-        context_store: Entity<ContextStore>,
-        language_registry: Arc<LanguageRegistry>,
-        workspace: WeakEntity<Workspace>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let subscriptions = vec![
-            cx.observe(&thread, |_, _, cx| cx.notify()),
-            cx.subscribe_in(&thread, window, Self::handle_thread_event),
-            cx.subscribe(&thread_store, Self::handle_rules_loading_error),
-            cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
-        ];
-
-        let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.));
-
-        let workspace_subscription = workspace.upgrade().map(|workspace| {
-            cx.observe_release(&workspace, |this, _, cx| {
-                this.dismiss_notifications(cx);
-            })
-        });
-
-        let mut this = Self {
-            language_registry,
-            thread_store,
-            text_thread_store,
-            context_store,
-            thread: thread.clone(),
-            workspace,
-            save_thread_task: None,
-            messages: Vec::new(),
-            rendered_messages_by_id: HashMap::default(),
-            rendered_tool_uses: HashMap::default(),
-            expanded_tool_uses: HashMap::default(),
-            expanded_thinking_segments: HashMap::default(),
-            expanded_code_blocks: HashMap::default(),
-            list_state: list_state.clone(),
-            scrollbar_state: ScrollbarState::new(list_state).parent_entity(&cx.entity()),
-            editing_message: None,
-            last_error: None,
-            copied_code_block_ids: HashSet::default(),
-            notifications: Vec::new(),
-            _subscriptions: subscriptions,
-            notification_subscriptions: HashMap::default(),
-            open_feedback_editors: HashMap::default(),
-            _load_edited_message_context_task: None,
-        };
-
-        for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
-            let rendered_message = RenderedMessage::from_segments(
-                &message.segments,
-                this.language_registry.clone(),
-                cx,
-            );
-            this.push_rendered_message(message.id, rendered_message);
-
-            for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) {
-                this.render_tool_use_markdown(
-                    tool_use.id.clone(),
-                    tool_use.ui_text.clone(),
-                    &serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
-                    tool_use.status.text(),
-                    cx,
-                );
-            }
-        }
-
-        if let Some(subscription) = workspace_subscription {
-            this._subscriptions.push(subscription);
-        }
-
-        this
-    }
-
-    pub fn thread(&self) -> &Entity<Thread> {
-        &self.thread
-    }
-
-    pub fn is_empty(&self) -> bool {
-        self.messages.is_empty()
-    }
-
-    pub fn summary<'a>(&'a self, cx: &'a App) -> &'a ThreadSummary {
-        self.thread.read(cx).summary()
-    }
-
-    pub fn regenerate_summary(&self, cx: &mut App) {
-        self.thread.update(cx, |thread, cx| thread.summarize(cx))
-    }
-
-    pub fn cancel_last_completion(&mut self, window: &mut Window, cx: &mut App) -> bool {
-        self.last_error.take();
-        self.thread.update(cx, |thread, cx| {
-            thread.cancel_last_completion(Some(window.window_handle()), cx)
-        })
-    }
-
-    pub fn last_error(&self) -> Option<ThreadError> {
-        self.last_error.clone()
-    }
-
-    pub fn clear_last_error(&mut self) {
-        self.last_error.take();
-    }
-
-    /// Returns the editing message id and the estimated token count in the content
-    pub fn editing_message_id(&self) -> Option<(MessageId, u64)> {
-        self.editing_message
-            .as_ref()
-            .map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0)))
-    }
-
-    pub fn context_store(&self) -> &Entity<ContextStore> {
-        &self.context_store
-    }
-
-    pub fn thread_store(&self) -> &Entity<ThreadStore> {
-        &self.thread_store
-    }
-
-    pub fn text_thread_store(&self) -> &Entity<TextThreadStore> {
-        &self.text_thread_store
-    }
-
-    fn push_rendered_message(&mut self, id: MessageId, rendered_message: RenderedMessage) {
-        let old_len = self.messages.len();
-        self.messages.push(id);
-        self.list_state.splice(old_len..old_len, 1);
-        self.rendered_messages_by_id.insert(id, rendered_message);
-    }
-
-    fn deleted_message(&mut self, id: &MessageId) {
-        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
-            return;
-        };
-        self.messages.remove(index);
-        self.list_state.splice(index..index + 1, 0);
-        self.rendered_messages_by_id.remove(id);
-    }
-
-    fn render_tool_use_markdown(
-        &mut self,
-        tool_use_id: LanguageModelToolUseId,
-        tool_label: impl Into<SharedString>,
-        tool_input: &str,
-        tool_output: SharedString,
-        cx: &mut Context<Self>,
-    ) {
-        let rendered = self
-            .rendered_tool_uses
-            .entry(tool_use_id)
-            .or_insert_with(|| RenderedToolUse {
-                label: cx.new(|cx| {
-                    Markdown::new("".into(), Some(self.language_registry.clone()), None, cx)
-                }),
-                input: cx.new(|cx| {
-                    Markdown::new("".into(), Some(self.language_registry.clone()), None, cx)
-                }),
-                output: cx.new(|cx| {
-                    Markdown::new("".into(), Some(self.language_registry.clone()), None, cx)
-                }),
-            });
-
-        rendered.label.update(cx, |this, cx| {
-            this.replace(tool_label, cx);
-        });
-        rendered.input.update(cx, |this, cx| {
-            this.replace(
-                MarkdownCodeBlock {
-                    tag: "json",
-                    text: tool_input,
-                }
-                .to_string(),
-                cx,
-            );
-        });
-        rendered.output.update(cx, |this, cx| {
-            this.replace(tool_output, cx);
-        });
-    }
-
-    fn handle_thread_event(
-        &mut self,
-        _thread: &Entity<Thread>,
-        event: &ThreadEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            ThreadEvent::CancelEditing => {
-                if self.editing_message.is_some() {
-                    self.cancel_editing_message(&menu::Cancel, window, cx);
-                }
-            }
-            ThreadEvent::ShowError(error) => {
-                self.last_error = Some(error.clone());
-            }
-            ThreadEvent::NewRequest => {
-                cx.notify();
-            }
-            ThreadEvent::CompletionCanceled => {
-                self.thread.update(cx, |thread, cx| {
-                    thread.project().update(cx, |project, cx| {
-                        project.set_agent_location(None, cx);
-                    })
-                });
-                self.workspace
-                    .update(cx, |workspace, cx| {
-                        if workspace.is_being_followed(CollaboratorId::Agent) {
-                            workspace.unfollow(CollaboratorId::Agent, window, cx);
-                        }
-                    })
-                    .ok();
-                cx.notify();
-            }
-            ThreadEvent::StreamedCompletion
-            | ThreadEvent::SummaryGenerated
-            | ThreadEvent::SummaryChanged => {
-                self.save_thread(cx);
-            }
-            ThreadEvent::Stopped(reason) => {
-                match reason {
-                    Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
-                        let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
-                        self.notify_with_sound(
-                            if used_tools {
-                                "Finished running tools"
-                            } else {
-                                "New message"
-                            },
-                            IconName::ZedAssistant,
-                            window,
-                            cx,
-                        );
-                    }
-                    Ok(StopReason::ToolUse) => {
-                        // Don't notify for intermediate tool use
-                    }
-                    Ok(StopReason::Refusal) => {
-                        let model_name = self
-                            .thread
-                            .read(cx)
-                            .configured_model()
-                            .map(|configured| configured.model.name().0.to_string())
-                            .unwrap_or_else(|| "The model".to_string());
-                        let refusal_message = format!(
-                            "{} refused to respond to this prompt. This can happen when a model believes the prompt violates its content policy or safety guidelines, so rephrasing it can sometimes address the issue.",
-                            model_name
-                        );
-                        self.last_error = Some(ThreadError::Message {
-                            header: SharedString::from("Request Refused"),
-                            message: SharedString::from(refusal_message),
-                        });
-                        self.notify_with_sound(
-                            format!("{} refused to respond", model_name),
-                            IconName::Warning,
-                            window,
-                            cx,
-                        );
-                    }
-                    Err(error) => {
-                        self.notify_with_sound(
-                            "Agent stopped due to an error",
-                            IconName::Warning,
-                            window,
-                            cx,
-                        );
-
-                        let error_message = error
-                            .chain()
-                            .map(|err| err.to_string())
-                            .collect::<Vec<_>>()
-                            .join("\n");
-                        self.last_error = Some(ThreadError::Message {
-                            header: "Error".into(),
-                            message: error_message.into(),
-                        });
-                    }
-                }
-            }
-            ThreadEvent::ToolConfirmationNeeded => {
-                self.notify_with_sound("Waiting for tool confirmation", IconName::Info, window, cx);
-            }
-            ThreadEvent::ToolUseLimitReached => {
-                self.notify_with_sound(
-                    "Consecutive tool use limit reached.",
-                    IconName::Warning,
-                    window,
-                    cx,
-                );
-            }
-            ThreadEvent::StreamedAssistantText(message_id, text) => {
-                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(message_id) {
-                    rendered_message.append_text(text, cx);
-                }
-            }
-            ThreadEvent::StreamedAssistantThinking(message_id, text) => {
-                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(message_id) {
-                    rendered_message.append_thinking(text, cx);
-                }
-            }
-            ThreadEvent::MessageAdded(message_id) => {
-                self.clear_last_error();
-                if let Some(rendered_message) = self.thread.update(cx, |thread, cx| {
-                    thread.message(*message_id).map(|message| {
-                        RenderedMessage::from_segments(
-                            &message.segments,
-                            self.language_registry.clone(),
-                            cx,
-                        )
-                    })
-                }) {
-                    self.push_rendered_message(*message_id, rendered_message);
-                }
-
-                self.save_thread(cx);
-                cx.notify();
-            }
-            ThreadEvent::MessageEdited(message_id) => {
-                self.clear_last_error();
-                if let Some(index) = self.messages.iter().position(|id| id == message_id)
-                    && let Some(rendered_message) = self.thread.update(cx, |thread, cx| {
-                        thread.message(*message_id).map(|message| {
-                            let mut rendered_message = RenderedMessage {
-                                language_registry: self.language_registry.clone(),
-                                segments: Vec::with_capacity(message.segments.len()),
-                            };
-                            for segment in &message.segments {
-                                rendered_message.push_segment(segment, cx);
-                            }
-                            rendered_message
-                        })
-                    })
-                {
-                    self.list_state.splice(index..index + 1, 1);
-                    self.rendered_messages_by_id
-                        .insert(*message_id, rendered_message);
-                    self.scroll_to_bottom(cx);
-                    self.save_thread(cx);
-                    cx.notify();
-                }
-            }
-            ThreadEvent::MessageDeleted(message_id) => {
-                self.deleted_message(message_id);
-                self.save_thread(cx);
-                cx.notify();
-            }
-            ThreadEvent::UsePendingTools { tool_uses } => {
-                for tool_use in tool_uses {
-                    self.render_tool_use_markdown(
-                        tool_use.id.clone(),
-                        tool_use.ui_text.clone(),
-                        &serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
-                        "".into(),
-                        cx,
-                    );
-                }
-            }
-            ThreadEvent::StreamedToolUse {
-                tool_use_id,
-                ui_text,
-                input,
-            } => {
-                self.render_tool_use_markdown(
-                    tool_use_id.clone(),
-                    ui_text.clone(),
-                    &serde_json::to_string_pretty(&input).unwrap_or_default(),
-                    "".into(),
-                    cx,
-                );
-            }
-            ThreadEvent::ToolFinished {
-                pending_tool_use, ..
-            } => {
-                if let Some(tool_use) = pending_tool_use {
-                    self.render_tool_use_markdown(
-                        tool_use.id.clone(),
-                        tool_use.ui_text.clone(),
-                        &serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
-                        self.thread
-                            .read(cx)
-                            .output_for_tool(&tool_use.id)
-                            .map(|output| output.clone().into())
-                            .unwrap_or("".into()),
-                        cx,
-                    );
-                }
-            }
-            ThreadEvent::CheckpointChanged => cx.notify(),
-            ThreadEvent::ReceivedTextChunk => {}
-            ThreadEvent::InvalidToolInput {
-                tool_use_id,
-                ui_text,
-                invalid_input_json,
-            } => {
-                self.render_tool_use_markdown(
-                    tool_use_id.clone(),
-                    ui_text,
-                    invalid_input_json,
-                    self.thread
-                        .read(cx)
-                        .output_for_tool(tool_use_id)
-                        .map(|output| output.clone().into())
-                        .unwrap_or("".into()),
-                    cx,
-                );
-            }
-            ThreadEvent::MissingToolUse {
-                tool_use_id,
-                ui_text,
-            } => {
-                self.render_tool_use_markdown(
-                    tool_use_id.clone(),
-                    ui_text,
-                    "",
-                    self.thread
-                        .read(cx)
-                        .output_for_tool(tool_use_id)
-                        .map(|output| output.clone().into())
-                        .unwrap_or("".into()),
-                    cx,
-                );
-            }
-            ThreadEvent::ProfileChanged => {
-                self.save_thread(cx);
-                cx.notify();
-            }
-        }
-    }
-
-    fn handle_rules_loading_error(
-        &mut self,
-        _thread_store: Entity<ThreadStore>,
-        error: &RulesLoadingError,
-        cx: &mut Context<Self>,
-    ) {
-        self.last_error = Some(ThreadError::Message {
-            header: "Error loading rules file".into(),
-            message: error.message.clone(),
-        });
-        cx.notify();
-    }
-
-    fn play_notification_sound(&self, window: &Window, cx: &mut App) {
-        let settings = AgentSettings::get_global(cx);
-        if settings.play_sound_when_agent_done && !window.is_window_active() {
-            Audio::play_sound(Sound::AgentDone, cx);
-        }
-    }
-
-    fn show_notification(
-        &mut self,
-        caption: impl Into<SharedString>,
-        icon: IconName,
-        window: &mut Window,
-        cx: &mut Context<ActiveThread>,
-    ) {
-        if window.is_window_active() || !self.notifications.is_empty() {
-            return;
-        }
-
-        let title = self.thread.read(cx).summary().unwrap_or("Agent Panel");
-
-        match AgentSettings::get_global(cx).notify_when_agent_waiting {
-            NotifyWhenAgentWaiting::PrimaryScreen => {
-                if let Some(primary) = cx.primary_display() {
-                    self.pop_up(icon, caption.into(), title, window, primary, cx);
-                }
-            }
-            NotifyWhenAgentWaiting::AllScreens => {
-                let caption = caption.into();
-                for screen in cx.displays() {
-                    self.pop_up(icon, caption.clone(), title.clone(), window, screen, cx);
-                }
-            }
-            NotifyWhenAgentWaiting::Never => {
-                // Don't show anything
-            }
-        }
-    }
-
-    fn notify_with_sound(
-        &mut self,
-        caption: impl Into<SharedString>,
-        icon: IconName,
-        window: &mut Window,
-        cx: &mut Context<ActiveThread>,
-    ) {
-        self.play_notification_sound(window, cx);
-        self.show_notification(caption, icon, window, cx);
-    }
-
-    fn pop_up(
-        &mut self,
-        icon: IconName,
-        caption: SharedString,
-        title: SharedString,
-        window: &mut Window,
-        screen: Rc<dyn PlatformDisplay>,
-        cx: &mut Context<'_, ActiveThread>,
-    ) {
-        let options = AgentNotification::window_options(screen, cx);
-
-        let project_name = self.workspace.upgrade().and_then(|workspace| {
-            workspace
-                .read(cx)
-                .project()
-                .read(cx)
-                .visible_worktrees(cx)
-                .next()
-                .map(|worktree| worktree.read(cx).root_name().to_string())
-        });
-
-        if let Some(screen_window) = cx
-            .open_window(options, |_, cx| {
-                cx.new(|_| {
-                    AgentNotification::new(title.clone(), caption.clone(), icon, project_name)
-                })
-            })
-            .log_err()
-            && let Some(pop_up) = screen_window.entity(cx).log_err()
-        {
-            self.notification_subscriptions
-                .entry(screen_window)
-                .or_insert_with(Vec::new)
-                .push(cx.subscribe_in(&pop_up, window, {
-                    |this, _, event, window, cx| match event {
-                        AgentNotificationEvent::Accepted => {
-                            let handle = window.window_handle();
-                            cx.activate(true);
-
-                            let workspace_handle = this.workspace.clone();
-
-                            // If there are multiple Zed windows, activate the correct one.
-                            cx.defer(move |cx| {
-                                handle
-                                    .update(cx, |_view, window, _cx| {
-                                        window.activate_window();
-
-                                        if let Some(workspace) = workspace_handle.upgrade() {
-                                            workspace.update(_cx, |workspace, cx| {
-                                                workspace.focus_panel::<AgentPanel>(window, cx);
-                                            });
-                                        }
-                                    })
-                                    .log_err();
-                            });
-
-                            this.dismiss_notifications(cx);
-                        }
-                        AgentNotificationEvent::Dismissed => {
-                            this.dismiss_notifications(cx);
-                        }
-                    }
-                }));
-
-            self.notifications.push(screen_window);
-
-            // If the user manually refocuses the original window, dismiss the popup.
-            self.notification_subscriptions
-                .entry(screen_window)
-                .or_insert_with(Vec::new)
-                .push({
-                    let pop_up_weak = pop_up.downgrade();
-
-                    cx.observe_window_activation(window, move |_, window, cx| {
-                        if window.is_window_active()
-                            && let Some(pop_up) = pop_up_weak.upgrade()
-                        {
-                            pop_up.update(cx, |_, cx| {
-                                cx.emit(AgentNotificationEvent::Dismissed);
-                            });
-                        }
-                    })
-                });
-        }
-    }
-
-    /// Spawns a task to save the active thread.
-    ///
-    /// Only one task to save the thread will be in flight at a time.
-    fn save_thread(&mut self, cx: &mut Context<Self>) {
-        let thread = self.thread.clone();
-        self.save_thread_task = Some(cx.spawn(async move |this, cx| {
-            let task = this
-                .update(cx, |this, cx| {
-                    this.thread_store
-                        .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx))
-                })
-                .ok();
-
-            if let Some(task) = task {
-                task.await.log_err();
-            }
-        }));
-    }
-
-    fn start_editing_message(
-        &mut self,
-        message_id: MessageId,
-        message_text: impl Into<Arc<str>>,
-        message_creases: &[MessageCrease],
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let editor = crate::message_editor::create_editor(
-            self.workspace.clone(),
-            self.context_store.downgrade(),
-            self.thread_store.downgrade(),
-            self.text_thread_store.downgrade(),
-            EDIT_PREVIOUS_MESSAGE_MIN_LINES,
-            None,
-            window,
-            cx,
-        );
-        editor.update(cx, |editor, cx| {
-            editor.set_text(message_text, window, cx);
-            insert_message_creases(editor, message_creases, &self.context_store, window, cx);
-            editor.focus_handle(cx).focus(window);
-            editor.move_to_end(&editor::actions::MoveToEnd, window, cx);
-        });
-        let buffer_edited_subscription =
-            cx.subscribe(&editor, |this, _, event: &EditorEvent, cx| {
-                if event == &EditorEvent::BufferEdited {
-                    this.update_editing_message_token_count(true, cx);
-                }
-            });
-
-        let context_picker_menu_handle = PopoverMenuHandle::default();
-        let context_strip = cx.new(|cx| {
-            ContextStrip::new(
-                self.context_store.clone(),
-                self.workspace.clone(),
-                Some(self.thread_store.downgrade()),
-                Some(self.text_thread_store.downgrade()),
-                context_picker_menu_handle.clone(),
-                SuggestContextKind::File,
-                ModelUsageContext::Thread(self.thread.clone()),
-                window,
-                cx,
-            )
-        });
-
-        let context_strip_subscription =
-            cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
-
-        self.editing_message = Some((
-            message_id,
-            EditingMessageState {
-                editor: editor.clone(),
-                context_strip,
-                context_picker_menu_handle,
-                last_estimated_token_count: None,
-                _subscriptions: [buffer_edited_subscription, context_strip_subscription],
-                _update_token_count_task: None,
-            },
-        ));
-        self.update_editing_message_token_count(false, cx);
-        cx.notify();
-    }
-
-    fn handle_context_strip_event(
-        &mut self,
-        _context_strip: &Entity<ContextStrip>,
-        event: &ContextStripEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some((_, state)) = self.editing_message.as_ref() {
-            match event {
-                ContextStripEvent::PickerDismissed
-                | ContextStripEvent::BlurredEmpty
-                | ContextStripEvent::BlurredDown => {
-                    let editor_focus_handle = state.editor.focus_handle(cx);
-                    window.focus(&editor_focus_handle);
-                }
-                ContextStripEvent::BlurredUp => {}
-            }
-        }
-    }
-
-    fn update_editing_message_token_count(&mut self, debounce: bool, cx: &mut Context<Self>) {
-        let Some((message_id, state)) = self.editing_message.as_mut() else {
-            return;
-        };
-
-        cx.emit(ActiveThreadEvent::EditingMessageTokenCountChanged);
-        state._update_token_count_task.take();
-
-        let Some(configured_model) = self.thread.read(cx).configured_model() else {
-            state.last_estimated_token_count.take();
-            return;
-        };
-
-        let editor = state.editor.clone();
-        let thread = self.thread.clone();
-        let message_id = *message_id;
-
-        state._update_token_count_task = Some(cx.spawn(async move |this, cx| {
-            if debounce {
-                cx.background_executor()
-                    .timer(Duration::from_millis(200))
-                    .await;
-            }
-
-            let token_count = if let Some(task) = cx
-                .update(|cx| {
-                    let Some(message) = thread.read(cx).message(message_id) else {
-                        log::error!("Message that was being edited no longer exists");
-                        return None;
-                    };
-                    let message_text = editor.read(cx).text(cx);
-
-                    if message_text.is_empty() && message.loaded_context.is_empty() {
-                        return None;
-                    }
-
-                    let mut request_message = LanguageModelRequestMessage {
-                        role: language_model::Role::User,
-                        content: Vec::new(),
-                        cache: false,
-                    };
-
-                    message
-                        .loaded_context
-                        .add_to_request_message(&mut request_message);
-
-                    if !message_text.is_empty() {
-                        request_message
-                            .content
-                            .push(MessageContent::Text(message_text));
-                    }
-
-                    let request = language_model::LanguageModelRequest {
-                        thread_id: None,
-                        prompt_id: None,
-                        intent: None,
-                        mode: None,
-                        messages: vec![request_message],
-                        tools: vec![],
-                        tool_choice: None,
-                        stop: vec![],
-                        temperature: AgentSettings::temperature_for_model(
-                            &configured_model.model,
-                            cx,
-                        ),
-                        thinking_allowed: true,
-                    };
-
-                    Some(configured_model.model.count_tokens(request, cx))
-                })
-                .ok()
-                .flatten()
-            {
-                task.await.log_err()
-            } else {
-                Some(0)
-            };
-
-            if let Some(token_count) = token_count {
-                this.update(cx, |this, cx| {
-                    let Some((_message_id, state)) = this.editing_message.as_mut() else {
-                        return;
-                    };
-
-                    state.last_estimated_token_count = Some(token_count);
-                    cx.emit(ActiveThreadEvent::EditingMessageTokenCountChanged);
-                })
-                .ok();
-            };
-        }));
-    }
-
-    fn toggle_context_picker(
-        &mut self,
-        _: &crate::ToggleContextPicker,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some((_, state)) = self.editing_message.as_mut() {
-            let handle = state.context_picker_menu_handle.clone();
-            window.defer(cx, move |window, cx| {
-                handle.toggle(window, cx);
-            });
-        }
-    }
-
-    fn remove_all_context(
-        &mut self,
-        _: &crate::RemoveAllContext,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.context_store.update(cx, |store, cx| store.clear(cx));
-        cx.notify();
-    }
-
-    fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some((_, state)) = self.editing_message.as_mut() {
-            if state.context_picker_menu_handle.is_deployed() {
-                cx.propagate();
-            } else {
-                state.context_strip.focus_handle(cx).focus(window);
-            }
-        }
-    }
-
-    fn paste(&mut self, _: &Paste, _window: &mut Window, cx: &mut Context<Self>) {
-        attach_pasted_images_as_context(&self.context_store, cx);
-    }
-
-    fn cancel_editing_message(
-        &mut self,
-        _: &menu::Cancel,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.editing_message.take();
-        cx.notify();
-
-        if let Some(workspace) = self.workspace.upgrade() {
-            workspace.update(cx, |workspace, cx| {
-                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                    panel.focus_handle(cx).focus(window);
-                }
-            });
-        }
-    }
-
-    fn confirm_editing_message(
-        &mut self,
-        _: &menu::Confirm,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let Some((message_id, state)) = self.editing_message.take() else {
-            return;
-        };
-
-        let Some(model) = self
-            .thread
-            .update(cx, |thread, cx| thread.get_or_init_configured_model(cx))
-        else {
-            return;
-        };
-
-        let edited_text = state.editor.read(cx).text(cx);
-
-        let creases = state.editor.update(cx, extract_message_creases);
-
-        let new_context = self
-            .context_store
-            .read(cx)
-            .new_context_for_thread(self.thread.read(cx), Some(message_id));
-
-        let project = self.thread.read(cx).project().clone();
-        let prompt_store = self.thread_store.read(cx).prompt_store().clone();
-
-        let git_store = project.read(cx).git_store().clone();
-        let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
-
-        let load_context_task = context::load_context(new_context, &project, &prompt_store, cx);
-        self._load_edited_message_context_task =
-            Some(cx.spawn_in(window, async move |this, cx| {
-                let (context, checkpoint) =
-                    futures::future::join(load_context_task, checkpoint).await;
-                let _ = this
-                    .update_in(cx, |this, window, cx| {
-                        this.thread.update(cx, |thread, cx| {
-                            thread.edit_message(
-                                message_id,
-                                Role::User,
-                                vec![MessageSegment::Text(edited_text)],
-                                creases,
-                                Some(context.loaded_context),
-                                checkpoint.ok(),
-                                cx,
-                            );
-                            for message_id in this.messages_after(message_id) {
-                                thread.delete_message(*message_id, cx);
-                            }
-                        });
-
-                        this.thread.update(cx, |thread, cx| {
-                            thread.advance_prompt_id();
-                            thread.cancel_last_completion(Some(window.window_handle()), cx);
-                            thread.send_to_model(
-                                model.model,
-                                CompletionIntent::UserPrompt,
-                                Some(window.window_handle()),
-                                cx,
-                            );
-                        });
-                        this._load_edited_message_context_task = None;
-                        cx.notify();
-                    })
-                    .log_err();
-            }));
-
-        if let Some(workspace) = self.workspace.upgrade() {
-            workspace.update(cx, |workspace, cx| {
-                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                    panel.focus_handle(cx).focus(window);
-                }
-            });
-        }
-    }
-
-    fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
-        self.messages
-            .iter()
-            .position(|id| *id == message_id)
-            .map(|index| &self.messages[index + 1..])
-            .unwrap_or(&[])
-    }
-
-    fn handle_cancel_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
-        self.cancel_editing_message(&menu::Cancel, window, cx);
-    }
-
-    fn handle_regenerate_click(
-        &mut self,
-        _: &ClickEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.confirm_editing_message(&menu::Confirm, window, cx);
-    }
-
-    fn handle_feedback_click(
-        &mut self,
-        message_id: MessageId,
-        feedback: ThreadFeedback,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let report = self.thread.update(cx, |thread, cx| {
-            thread.report_message_feedback(message_id, feedback, cx)
-        });
-
-        cx.spawn(async move |this, cx| {
-            report.await?;
-            this.update(cx, |_this, cx| cx.notify())
-        })
-        .detach_and_log_err(cx);
-
-        match feedback {
-            ThreadFeedback::Positive => {
-                self.open_feedback_editors.remove(&message_id);
-            }
-            ThreadFeedback::Negative => {
-                self.handle_show_feedback_comments(message_id, window, cx);
-            }
-        }
-    }
-
-    fn handle_show_feedback_comments(
-        &mut self,
-        message_id: MessageId,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let buffer = cx.new(|cx| {
-            let empty_string = String::new();
-            MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)
-        });
-
-        let editor = cx.new(|cx| {
-            let mut editor = Editor::new(
-                editor::EditorMode::AutoHeight {
-                    min_lines: 1,
-                    max_lines: Some(4),
-                },
-                buffer,
-                None,
-                window,
-                cx,
-            );
-            editor.set_placeholder_text(
-                "What went wrong? Share your feedback so we can improve.",
-                window,
-                cx,
-            );
-            editor
-        });
-
-        editor.read(cx).focus_handle(cx).focus(window);
-        self.open_feedback_editors.insert(message_id, editor);
-        cx.notify();
-    }
-
-    fn submit_feedback_message(&mut self, message_id: MessageId, cx: &mut Context<Self>) {
-        let Some(editor) = self.open_feedback_editors.get(&message_id) else {
-            return;
-        };
-
-        let report_task = self.thread.update(cx, |thread, cx| {
-            thread.report_message_feedback(message_id, ThreadFeedback::Negative, cx)
-        });
-
-        let comments = editor.read(cx).text(cx);
-        if !comments.is_empty() {
-            let thread_id = self.thread.read(cx).id().clone();
-            let comments_value = String::from(comments.as_str());
-
-            let message_content = self
-                .thread
-                .read(cx)
-                .message(message_id)
-                .map(|msg| msg.to_message_content())
-                .unwrap_or_default();
-
-            telemetry::event!(
-                "Assistant Thread Feedback Comments",
-                thread_id,
-                message_id = message_id.as_usize(),
-                message_content,
-                comments = comments_value
-            );
-
-            self.open_feedback_editors.remove(&message_id);
-
-            cx.spawn(async move |this, cx| {
-                report_task.await?;
-                this.update(cx, |_this, cx| cx.notify())
-            })
-            .detach_and_log_err(cx);
-        }
-    }
-
-    fn render_edit_message_editor(
-        &self,
-        state: &EditingMessageState,
-        _window: &mut Window,
-        cx: &Context<Self>,
-    ) -> impl IntoElement {
-        let settings = ThemeSettings::get_global(cx);
-        let font_size = TextSize::Small
-            .rems(cx)
-            .to_pixels(settings.agent_font_size(cx));
-        let line_height = font_size * 1.75;
-
-        let colors = cx.theme().colors();
-
-        let text_style = TextStyle {
-            color: colors.text,
-            font_family: settings.buffer_font.family.clone(),
-            font_fallbacks: settings.buffer_font.fallbacks.clone(),
-            font_features: settings.buffer_font.features.clone(),
-            font_size: font_size.into(),
-            line_height: line_height.into(),
-            ..Default::default()
-        };
-
-        v_flex()
-            .key_context("EditMessageEditor")
-            .on_action(cx.listener(Self::toggle_context_picker))
-            .on_action(cx.listener(Self::remove_all_context))
-            .on_action(cx.listener(Self::move_up))
-            .on_action(cx.listener(Self::cancel_editing_message))
-            .on_action(cx.listener(Self::confirm_editing_message))
-            .capture_action(cx.listener(Self::paste))
-            .min_h_6()
-            .w_full()
-            .flex_grow()
-            .gap_2()
-            .child(state.context_strip.clone())
-            .child(div().pt(px(-3.)).px_neg_0p5().child(EditorElement::new(
-                &state.editor,
-                EditorStyle {
-                    background: colors.editor_background,
-                    local_player: cx.theme().players().local(),
-                    text: text_style,
-                    syntax: cx.theme().syntax().clone(),
-                    ..Default::default()
-                },
-            )))
-    }
-
-    fn render_message(
-        &mut self,
-        ix: usize,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> AnyElement {
-        let message_id = self.messages[ix];
-        let workspace = self.workspace.clone();
-        let thread = self.thread.read(cx);
-
-        let is_first_message = ix == 0;
-        let is_last_message = ix == self.messages.len() - 1;
-
-        let Some(message) = thread.message(message_id) else {
-            return Empty.into_any();
-        };
-
-        let is_generating = thread.is_generating();
-        let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
-
-        let loading_dots = (is_generating && is_last_message).then(|| {
-            h_flex()
-                .h_8()
-                .my_3()
-                .mx_5()
-                .when(is_generating_stale || message.is_hidden, |this| {
-                    this.child(LoadingLabel::new("").size(LabelSize::Small))
-                })
-        });
-
-        if message.is_hidden {
-            return div().children(loading_dots).into_any();
-        }
-
-        let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
-            return Empty.into_any();
-        };
-
-        // Get all the data we need from thread before we start using it in closures
-        let checkpoint = thread.checkpoint_for_message(message_id);
-        let configured_model = thread.configured_model().map(|m| m.model);
-        let added_context = thread
-            .context_for_message(message_id)
-            .map(|context| AddedContext::new_attached(context, configured_model.as_ref(), cx))
-            .collect::<Vec<_>>();
-
-        let tool_uses = thread.tool_uses_for_message(message_id, cx);
-        let has_tool_uses = !tool_uses.is_empty();
-
-        let editing_message_state = self
-            .editing_message
-            .as_ref()
-            .filter(|(id, _)| *id == message_id)
-            .map(|(_, state)| state);
-
-        let (editor_bg_color, panel_bg) = {
-            let colors = cx.theme().colors();
-            (colors.editor_background, colors.panel_background)
-        };
-
-        let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::FileMarkdown)
-            .shape(ui::IconButtonShape::Square)
-            .icon_size(IconSize::Small)
-            .icon_color(Color::Ignored)
-            .tooltip(Tooltip::text("Open Thread as Markdown"))
-            .on_click({
-                let thread = self.thread.clone();
-                let workspace = self.workspace.clone();
-                move |_, window, cx| {
-                    if let Some(workspace) = workspace.upgrade() {
-                        open_active_thread_as_markdown(thread.clone(), workspace, window, cx)
-                            .detach_and_log_err(cx);
-                    }
-                }
-            });
-
-        let scroll_to_top = IconButton::new(("scroll_to_top", ix), IconName::ArrowUp)
-            .shape(ui::IconButtonShape::Square)
-            .icon_size(IconSize::Small)
-            .icon_color(Color::Ignored)
-            .tooltip(Tooltip::text("Scroll To Top"))
-            .on_click(cx.listener(move |this, _, _, cx| {
-                this.scroll_to_top(cx);
-            }));
-
-        let show_feedback = thread.is_turn_end(ix);
-        let feedback_container = h_flex()
-            .group("feedback_container")
-            .mt_1()
-            .py_2()
-            .px(RESPONSE_PADDING_X)
-            .mr_1()
-            .gap_1()
-            .opacity(0.4)
-            .hover(|style| style.opacity(1.))
-            .gap_1p5()
-            .flex_wrap()
-            .justify_end();
-        let feedback_items = match self.thread.read(cx).message_feedback(message_id) {
-            Some(feedback) => feedback_container
-                .child(
-                    div().visible_on_hover("feedback_container").child(
-                        Label::new(match feedback {
-                            ThreadFeedback::Positive => "Thanks for your feedback!",
-                            ThreadFeedback::Negative => {
-                                "We appreciate your feedback and will use it to improve."
-                            }
-                        })
-                    .color(Color::Muted)
-                    .size(LabelSize::XSmall)
-                    .truncate())
-                )
-                .child(
-                    h_flex()
-                        .child(
-                            IconButton::new(("feedback-thumbs-up", ix), IconName::ThumbsUp)
-                                .shape(ui::IconButtonShape::Square)
-                                .icon_size(IconSize::Small)
-                                .icon_color(match feedback {
-                                    ThreadFeedback::Positive => Color::Accent,
-                                    ThreadFeedback::Negative => Color::Ignored,
-                                })
-                                .tooltip(Tooltip::text("Helpful Response"))
-                                .on_click(cx.listener(move |this, _, window, cx| {
-                                    this.handle_feedback_click(
-                                        message_id,
-                                        ThreadFeedback::Positive,
-                                        window,
-                                        cx,
-                                    );
-                                })),
-                        )
-                        .child(
-                            IconButton::new(("feedback-thumbs-down", ix), IconName::ThumbsDown)
-                                .shape(ui::IconButtonShape::Square)
-                                .icon_size(IconSize::Small)
-                                .icon_color(match feedback {
-                                    ThreadFeedback::Positive => Color::Ignored,
-                                    ThreadFeedback::Negative => Color::Accent,
-                                })
-                                .tooltip(Tooltip::text("Not Helpful"))
-                                .on_click(cx.listener(move |this, _, window, cx| {
-                                    this.handle_feedback_click(
-                                        message_id,
-                                        ThreadFeedback::Negative,
-                                        window,
-                                        cx,
-                                    );
-                                })),
-                        )
-                        .child(open_as_markdown),
-                )
-                .into_any_element(),
-            None if AgentSettings::get_global(cx).enable_feedback =>
-                feedback_container
-                .child(
-                    div().visible_on_hover("feedback_container").child(
-                        Label::new(
-                            "Rating the thread sends all of your current conversation to the Zed team.",
-                        )
-                        .color(Color::Muted)
-                    .size(LabelSize::XSmall)
-                    .truncate())
-                )
-                .child(
-                    h_flex()
-                        .child(
-                            IconButton::new(("feedback-thumbs-up", ix), IconName::ThumbsUp)
-                                .shape(ui::IconButtonShape::Square)
-                                .icon_size(IconSize::Small)
-                                .icon_color(Color::Ignored)
-                                .tooltip(Tooltip::text("Helpful Response"))
-                                .on_click(cx.listener(move |this, _, window, cx| {
-                                    this.handle_feedback_click(
-                                        message_id,
-                                        ThreadFeedback::Positive,
-                                        window,
-                                        cx,
-                                    );
-                                })),
-                        )
-                        .child(
-                            IconButton::new(("feedback-thumbs-down", ix), IconName::ThumbsDown)
-                                .shape(ui::IconButtonShape::Square)
-                                .icon_size(IconSize::Small)
-                                .icon_color(Color::Ignored)
-                                .tooltip(Tooltip::text("Not Helpful"))
-                                .on_click(cx.listener(move |this, _, window, cx| {
-                                    this.handle_feedback_click(
-                                        message_id,
-                                        ThreadFeedback::Negative,
-                                        window,
-                                        cx,
-                                    );
-                                })),
-                        )
-                        .child(open_as_markdown)
-                        .child(scroll_to_top),
-                )
-                .into_any_element(),
-            None => feedback_container
-                .child(h_flex()
-                    .child(open_as_markdown))
-                    .child(scroll_to_top)
-                .into_any_element(),
-        };
-
-        let message_is_empty = message.should_display_content();
-        let has_content = !message_is_empty || !added_context.is_empty();
-
-        let message_content = has_content.then(|| {
-            if let Some(state) = editing_message_state.as_ref() {
-                self.render_edit_message_editor(state, window, cx)
-                    .into_any_element()
-            } else {
-                v_flex()
-                    .w_full()
-                    .gap_1()
-                    .when(!added_context.is_empty(), |parent| {
-                        parent.child(h_flex().flex_wrap().gap_1().children(
-                            added_context.into_iter().map(|added_context| {
-                                let context = added_context.handle.clone();
-                                ContextPill::added(added_context, false, false, None).on_click(
-                                    Rc::new(cx.listener({
-                                        let workspace = workspace.clone();
-                                        move |_, _, window, cx| {
-                                            if let Some(workspace) = workspace.upgrade() {
-                                                open_context(&context, workspace, window, cx);
-                                                cx.notify();
-                                            }
-                                        }
-                                    })),
-                                )
-                            }),
-                        ))
-                    })
-                    .when(!message_is_empty, |parent| {
-                        parent.child(div().pt_0p5().min_h_6().child(self.render_message_content(
-                            message_id,
-                            rendered_message,
-                            has_tool_uses,
-                            workspace.clone(),
-                            window,
-                            cx,
-                        )))
-                    })
-                    .into_any_element()
-            }
-        });
-
-        let styled_message = if message.ui_only {
-            self.render_ui_notification(message_content, ix, cx)
-        } else {
-            match message.role {
-                Role::User => {
-                    let colors = cx.theme().colors();
-                    v_flex()
-                        .id(("message-container", ix))
-                        .pt_2()
-                        .pl_2()
-                        .pr_2p5()
-                        .pb_4()
-                        .child(
-                            v_flex()
-                                .id(("user-message", ix))
-                                .bg(editor_bg_color)
-                                .rounded_lg()
-                                .shadow_md()
-                                .border_1()
-                                .border_color(colors.border)
-                                .hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
-                                .child(
-                                    v_flex()
-                                        .p_2p5()
-                                        .gap_1()
-                                        .children(message_content)
-                                        .when_some(editing_message_state, |this, state| {
-                                            let focus_handle = state.editor.focus_handle(cx);
-
-                                            this.child(
-                                                h_flex()
-                                                    .w_full()
-                                                    .gap_1()
-                                                    .justify_between()
-                                                    .flex_wrap()
-                                                    .child(
-                                                        h_flex()
-                                                            .gap_1p5()
-                                                            .child(
-                                                                div()
-                                                                    .opacity(0.8)
-                                                                    .child(
-                                                                        Icon::new(IconName::Warning)
-                                                                            .size(IconSize::Indicator)
-                                                                            .color(Color::Warning)
-                                                                    ),
-                                                            )
-                                                            .child(
-                                                                Label::new("Editing will restart the thread from this point.")
-                                                                    .color(Color::Muted)
-                                                                    .size(LabelSize::XSmall),
-                                                            ),
-                                                    )
-                                                    .child(
-                                                        h_flex()
-                                                            .gap_0p5()
-                                                            .child(
-                                                                IconButton::new(
-                                                                    "cancel-edit-message",
-                                                                    IconName::Close,
-                                                                )
-                                                                .shape(ui::IconButtonShape::Square)
-                                                                .icon_color(Color::Error)
-                                                                .icon_size(IconSize::Small)
-                                                                .tooltip({
-                                                                    let focus_handle = focus_handle.clone();
-                                                                    move |window, cx| {
-                                                                        Tooltip::for_action_in(
-                                                                            "Cancel Edit",
-                                                                            &menu::Cancel,
-                                                                            &focus_handle,
-                                                                            window,
-                                                                            cx,
-                                                                        )
-                                                                    }
-                                                                })
-                                                                .on_click(cx.listener(Self::handle_cancel_click)),
-                                                            )
-                                                            .child(
-                                                                IconButton::new(
-                                                                    "confirm-edit-message",
-                                                                    IconName::Return,
-                                                                )
-                                                                .disabled(state.editor.read(cx).is_empty(cx))
-                                                                .shape(ui::IconButtonShape::Square)
-                                                                .icon_color(Color::Muted)
-                                                                .icon_size(IconSize::Small)
-                                                                .tooltip({
-                                                                    move |window, cx| {
-                                                                        Tooltip::for_action_in(
-                                                                            "Regenerate",
-                                                                            &menu::Confirm,
-                                                                            &focus_handle,
-                                                                            window,
-                                                                            cx,
-                                                                        )
-                                                                    }
-                                                                })
-                                                                .on_click(
-                                                                    cx.listener(Self::handle_regenerate_click),
-                                                                ),
-                                                            ),
-                                                    )
-                                            )
-                                        }),
-                                )
-                                .on_click(cx.listener({
-                                    let message_creases = message.creases.clone();
-                                    move |this, _, window, cx| {
-                                        if let Some(message_text) =
-                                            this.thread.read(cx).message(message_id).and_then(|message| {
-                                                message.segments.first().and_then(|segment| {
-                                                    match segment {
-                                                        MessageSegment::Text(message_text) => {
-                                                            Some(Into::<Arc<str>>::into(message_text.as_str()))
-                                                        }
-                                                        _ => {
-                                                            None
-                                                        }
-                                                    }
-                                                })
-                                            })
-                                        {
-                                            this.start_editing_message(
-                                                message_id,
-                                                message_text,
-                                                &message_creases,
-                                                window,
-                                                cx,
-                                            );
-                                        }
-                                    }
-                                })),
-                        )
-                }
-                Role::Assistant => v_flex()
-                    .id(("message-container", ix))
-                    .px(RESPONSE_PADDING_X)
-                    .gap_2()
-                    .children(message_content)
-                    .when(has_tool_uses, |parent| {
-                        parent.children(tool_uses.into_iter().map(|tool_use| {
-                            self.render_tool_use(tool_use, window, workspace.clone(), cx)
-                        }))
-                    }),
-                Role::System => {
-                    let colors = cx.theme().colors();
-                    div().id(("message-container", ix)).py_1().px_2().child(
-                        v_flex()
-                            .bg(colors.editor_background)
-                            .rounded_sm()
-                            .child(div().p_4().children(message_content)),
-                    )
-                }
-            }
-        };
-
-        let after_editing_message = self
-            .editing_message
-            .as_ref()
-            .is_some_and(|(editing_message_id, _)| message_id > *editing_message_id);
-
-        let backdrop = div()
-            .id(("backdrop", ix))
-            .size_full()
-            .absolute()
-            .inset_0()
-            .bg(panel_bg)
-            .opacity(0.8)
-            .block_mouse_except_scroll()
-            .on_click(cx.listener(Self::handle_cancel_click));
-
-        v_flex()
-            .w_full()
-            .map(|parent| {
-                if let Some(checkpoint) = checkpoint.filter(|_| !is_generating) {
-                    let mut is_pending = false;
-                    let mut error = None;
-                    if let Some(last_restore_checkpoint) =
-                        self.thread.read(cx).last_restore_checkpoint()
-                        && last_restore_checkpoint.message_id() == message_id
-                    {
-                        match last_restore_checkpoint {
-                            LastRestoreCheckpoint::Pending { .. } => is_pending = true,
-                            LastRestoreCheckpoint::Error { error: err, .. } => {
-                                error = Some(err.clone());
-                            }
-                        }
-                    }
-
-                    let restore_checkpoint_button =
-                        Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
-                            .icon(if error.is_some() {
-                                IconName::XCircle
-                            } else {
-                                IconName::Undo
-                            })
-                            .icon_size(IconSize::XSmall)
-                            .icon_position(IconPosition::Start)
-                            .icon_color(if error.is_some() {
-                                Some(Color::Error)
-                            } else {
-                                None
-                            })
-                            .label_size(LabelSize::XSmall)
-                            .disabled(is_pending)
-                            .on_click(cx.listener(move |this, _, _window, cx| {
-                                this.thread.update(cx, |thread, cx| {
-                                    thread
-                                        .restore_checkpoint(checkpoint.clone(), cx)
-                                        .detach_and_log_err(cx);
-                                });
-                            }));
-
-                    let restore_checkpoint_button = if is_pending {
-                        restore_checkpoint_button
-                            .with_animation(
-                                ("pulsating-restore-checkpoint-button", ix),
-                                Animation::new(Duration::from_secs(2))
-                                    .repeat()
-                                    .with_easing(pulsating_between(0.6, 1.)),
-                                |label, delta| label.alpha(delta),
-                            )
-                            .into_any_element()
-                    } else if let Some(error) = error {
-                        restore_checkpoint_button
-                            .tooltip(Tooltip::text(error))
-                            .into_any_element()
-                    } else {
-                        restore_checkpoint_button.into_any_element()
-                    };
-
-                    parent.child(
-                        h_flex()
-                            .pt_2p5()
-                            .px_2p5()
-                            .w_full()
-                            .gap_1()
-                            .child(ui::Divider::horizontal())
-                            .child(restore_checkpoint_button)
-                            .child(ui::Divider::horizontal()),
-                    )
-                } else {
-                    parent
-                }
-            })
-            .when(is_first_message, |parent| {
-                parent.child(self.render_rules_item(cx))
-            })
-            .child(styled_message)
-            .children(loading_dots)
-            .when(show_feedback, move |parent| {
-                parent.child(feedback_items).when_some(
-                    self.open_feedback_editors.get(&message_id),
-                    move |parent, feedback_editor| {
-                        let focus_handle = feedback_editor.focus_handle(cx);
-                        parent.child(
-                            v_flex()
-                                .key_context("AgentFeedbackMessageEditor")
-                                .on_action(cx.listener(move |this, _: &menu::Cancel, _, cx| {
-                                    this.open_feedback_editors.remove(&message_id);
-                                    cx.notify();
-                                }))
-                                .on_action(cx.listener(move |this, _: &menu::Confirm, _, cx| {
-                                    this.submit_feedback_message(message_id, cx);
-                                    cx.notify();
-                                }))
-                                .mb_2()
-                                .mx_4()
-                                .p_2()
-                                .rounded_md()
-                                .border_1()
-                                .border_color(cx.theme().colors().border)
-                                .bg(cx.theme().colors().editor_background)
-                                .child(feedback_editor.clone())
-                                .child(
-                                    h_flex()
-                                        .gap_1()
-                                        .justify_end()
-                                        .child(
-                                            Button::new("dismiss-feedback-message", "Cancel")
-                                                .label_size(LabelSize::Small)
-                                                .key_binding(
-                                                    KeyBinding::for_action_in(
-                                                        &menu::Cancel,
-                                                        &focus_handle,
-                                                        window,
-                                                        cx,
-                                                    )
-                                                    .map(|kb| kb.size(rems_from_px(10.))),
-                                                )
-                                                .on_click(cx.listener(
-                                                    move |this, _, _window, cx| {
-                                                        this.open_feedback_editors
-                                                            .remove(&message_id);
-                                                        cx.notify();
-                                                    },
-                                                )),
-                                        )
-                                        .child(
-                                            Button::new(
-                                                "submit-feedback-message",
-                                                "Share Feedback",
-                                            )
-                                            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
-                                            .label_size(LabelSize::Small)
-                                            .key_binding(
-                                                KeyBinding::for_action_in(
-                                                    &menu::Confirm,
-                                                    &focus_handle,
-                                                    window,
-                                                    cx,
-                                                )
-                                                .map(|kb| kb.size(rems_from_px(10.))),
-                                            )
-                                            .on_click(
-                                                cx.listener(move |this, _, _window, cx| {
-                                                    this.submit_feedback_message(message_id, cx);
-                                                    cx.notify()
-                                                }),
-                                            ),
-                                        ),
-                                ),
-                        )
-                    },
-                )
-            })
-            .when(after_editing_message, |parent| {
-                // Backdrop to dim out the whole thread below the editing user message
-                parent.relative().child(backdrop)
-            })
-            .into_any()
-    }
-
-    fn render_message_content(
-        &self,
-        message_id: MessageId,
-        rendered_message: &RenderedMessage,
-        has_tool_uses: bool,
-        workspace: WeakEntity<Workspace>,
-        window: &Window,
-        cx: &Context<Self>,
-    ) -> impl IntoElement {
-        let is_last_message = self.messages.last() == Some(&message_id);
-        let is_generating = self.thread.read(cx).is_generating();
-        let pending_thinking_segment_index = if is_generating && is_last_message && !has_tool_uses {
-            rendered_message
-                .segments
-                .iter()
-                .enumerate()
-                .next_back()
-                .filter(|(_, segment)| matches!(segment, RenderedMessageSegment::Thinking { .. }))
-                .map(|(index, _)| index)
-        } else {
-            None
-        };
-
-        let message_role = self
-            .thread
-            .read(cx)
-            .message(message_id)
-            .map(|m| m.role)
-            .unwrap_or(Role::User);
-
-        let is_assistant_message = message_role == Role::Assistant;
-        let is_user_message = message_role == Role::User;
-
-        v_flex()
-            .text_ui(cx)
-            .gap_2()
-            .when(is_user_message, |this| this.text_xs())
-            .children(
-                rendered_message.segments.iter().enumerate().map(
-                    |(index, segment)| match segment {
-                        RenderedMessageSegment::Thinking {
-                            content,
-                            scroll_handle,
-                        } => self
-                            .render_message_thinking_segment(
-                                message_id,
-                                index,
-                                content.clone(),
-                                scroll_handle,
-                                Some(index) == pending_thinking_segment_index,
-                                window,
-                                cx,
-                            )
-                            .into_any_element(),
-                        RenderedMessageSegment::Text(markdown) => {
-                            let markdown_element = MarkdownElement::new(
-                                markdown.clone(),
-                                if is_user_message {
-                                    let mut style = default_markdown_style(window, cx);
-                                    let mut text_style = window.text_style();
-                                    let theme_settings = ThemeSettings::get_global(cx);
-
-                                    let buffer_font = theme_settings.buffer_font.family.clone();
-                                    let buffer_font_size = TextSize::Small.rems(cx);
-
-                                    text_style.refine(&TextStyleRefinement {
-                                        font_family: Some(buffer_font),
-                                        font_size: Some(buffer_font_size.into()),
-                                        ..Default::default()
-                                    });
-
-                                    style.base_text_style = text_style;
-                                    style
-                                } else {
-                                    default_markdown_style(window, cx)
-                                },
-                            );
-
-                            let markdown_element = if is_assistant_message {
-                                markdown_element.code_block_renderer(
-                                    markdown::CodeBlockRenderer::Custom {
-                                        render: Arc::new({
-                                            let workspace = workspace.clone();
-                                            let active_thread = cx.entity();
-                                            move |kind,
-                                                  parsed_markdown,
-                                                  range,
-                                                  metadata,
-                                                  window,
-                                                  cx| {
-                                                render_markdown_code_block(
-                                                    message_id,
-                                                    range.start,
-                                                    kind,
-                                                    parsed_markdown,
-                                                    metadata,
-                                                    active_thread.clone(),
-                                                    workspace.clone(),
-                                                    window,
-                                                    cx,
-                                                )
-                                            }
-                                        }),
-                                        transform: Some(Arc::new({
-                                            let active_thread = cx.entity();
-
-                                            move |element, range, _, _, cx| {
-                                                let is_expanded = active_thread
-                                                    .read(cx)
-                                                    .is_codeblock_expanded(message_id, range.start);
-
-                                                if is_expanded {
-                                                    return element;
-                                                }
-
-                                                element
-                                            }
-                                        })),
-                                    },
-                                )
-                            } else {
-                                markdown_element.code_block_renderer(
-                                    markdown::CodeBlockRenderer::Default {
-                                        copy_button: false,
-                                        copy_button_on_hover: false,
-                                        border: true,
-                                    },
-                                )
-                            };
-
-                            div()
-                                .child(markdown_element.on_url_click({
-                                    let workspace = self.workspace.clone();
-                                    move |text, window, cx| {
-                                        open_markdown_link(text, workspace.clone(), window, cx);
-                                    }
-                                }))
-                                .into_any_element()
-                        }
-                    },
-                ),
-            )
-    }
-
-    fn tool_card_border_color(&self, cx: &Context<Self>) -> Hsla {
-        cx.theme().colors().border.opacity(0.5)
-    }
-
-    fn tool_card_header_bg(&self, cx: &Context<Self>) -> Hsla {
-        cx.theme()
-            .colors()
-            .element_background
-            .blend(cx.theme().colors().editor_foreground.opacity(0.025))
-    }
-
-    fn render_ui_notification(
-        &self,
-        message_content: impl IntoIterator<Item = impl IntoElement>,
-        ix: usize,
-        cx: &mut Context<Self>,
-    ) -> Stateful<Div> {
-        let message = div()
-            .flex_1()
-            .min_w_0()
-            .text_size(TextSize::XSmall.rems(cx))
-            .text_color(cx.theme().colors().text_muted)
-            .children(message_content);
-
-        div()
-            .id(("message-container", ix))
-            .py_1()
-            .px_2p5()
-            .child(Banner::new().severity(Severity::Warning).child(message))
-    }
-
-    fn render_message_thinking_segment(
-        &self,
-        message_id: MessageId,
-        ix: usize,
-        markdown: Entity<Markdown>,
-        scroll_handle: &ScrollHandle,
-        pending: bool,
-        window: &Window,
-        cx: &Context<Self>,
-    ) -> impl IntoElement {
-        let is_open = self
-            .expanded_thinking_segments
-            .get(&(message_id, ix))
-            .copied()
-            .unwrap_or_default();
-
-        let editor_bg = cx.theme().colors().panel_background;
-
-        div().map(|this| {
-            if pending {
-                this.v_flex()
-                    .mt_neg_2()
-                    .mb_1p5()
-                    .child(
-                        h_flex()
-                            .group("disclosure-header")
-                            .justify_between()
-                            .child(
-                                h_flex()
-                                    .gap_1p5()
-                                    .child(
-                                        Icon::new(IconName::ToolThink)
-                                            .size(IconSize::Small)
-                                            .color(Color::Muted),
-                                    )
-                                    .child(LoadingLabel::new("Thinking").size(LabelSize::Small)),
-                            )
-                            .child(
-                                h_flex()
-                                    .gap_1()
-                                    .child(
-                                        div().visible_on_hover("disclosure-header").child(
-                                            Disclosure::new("thinking-disclosure", is_open)
-                                                .opened_icon(IconName::ChevronUp)
-                                                .closed_icon(IconName::ChevronDown)
-                                                .on_click(cx.listener({
-                                                    move |this, _event, _window, _cx| {
-                                                        let is_open = this
-                                                            .expanded_thinking_segments
-                                                            .entry((message_id, ix))
-                                                            .or_insert(false);
-
-                                                        *is_open = !*is_open;
-                                                    }
-                                                })),
-                                        ),
-                                    )
-                                    .child({
-                                        Icon::new(IconName::ArrowCircle)
-                                            .color(Color::Accent)
-                                            .size(IconSize::Small)
-                                            .with_rotate_animation(2)
-                                    }),
-                            ),
-                    )
-                    .when(!is_open, |this| {
-                        let gradient_overlay = div()
-                            .rounded_b_lg()
-                            .h_full()
-                            .absolute()
-                            .w_full()
-                            .bottom_0()
-                            .left_0()
-                            .bg(linear_gradient(
-                                180.,
-                                linear_color_stop(editor_bg, 1.),
-                                linear_color_stop(editor_bg.opacity(0.2), 0.),
-                            ));
-
-                        this.child(
-                            div()
-                                .relative()
-                                .bg(editor_bg)
-                                .rounded_b_lg()
-                                .mt_2()
-                                .pl_4()
-                                .child(
-                                    div()
-                                        .id(("thinking-content", ix))
-                                        .max_h_20()
-                                        .track_scroll(scroll_handle)
-                                        .text_ui_sm(cx)
-                                        .overflow_hidden()
-                                        .child(
-                                            MarkdownElement::new(
-                                                markdown.clone(),
-                                                default_markdown_style(window, cx),
-                                            )
-                                            .on_url_click({
-                                                let workspace = self.workspace.clone();
-                                                move |text, window, cx| {
-                                                    open_markdown_link(
-                                                        text,
-                                                        workspace.clone(),
-                                                        window,
-                                                        cx,
-                                                    );
-                                                }
-                                            }),
-                                        ),
-                                )
-                                .child(gradient_overlay),
-                        )
-                    })
-                    .when(is_open, |this| {
-                        this.child(
-                            div()
-                                .id(("thinking-content", ix))
-                                .h_full()
-                                .bg(editor_bg)
-                                .text_ui_sm(cx)
-                                .child(
-                                    MarkdownElement::new(
-                                        markdown.clone(),
-                                        default_markdown_style(window, cx),
-                                    )
-                                    .on_url_click({
-                                        let workspace = self.workspace.clone();
-                                        move |text, window, cx| {
-                                            open_markdown_link(text, workspace.clone(), window, cx);
-                                        }
-                                    }),
-                                ),
-                        )
-                    })
-            } else {
-                this.v_flex()
-                    .mt_neg_2()
-                    .child(
-                        h_flex()
-                            .group("disclosure-header")
-                            .pr_1()
-                            .justify_between()
-                            .opacity(0.8)
-                            .hover(|style| style.opacity(1.))
-                            .child(
-                                h_flex()
-                                    .gap_1p5()
-                                    .child(
-                                        Icon::new(IconName::ToolThink)
-                                            .size(IconSize::XSmall)
-                                            .color(Color::Muted),
-                                    )
-                                    .child(Label::new("Thought Process").size(LabelSize::Small)),
-                            )
-                            .child(
-                                div().visible_on_hover("disclosure-header").child(
-                                    Disclosure::new("thinking-disclosure", is_open)
-                                        .opened_icon(IconName::ChevronUp)
-                                        .closed_icon(IconName::ChevronDown)
-                                        .on_click(cx.listener({
-                                            move |this, _event, _window, _cx| {
-                                                let is_open = this
-                                                    .expanded_thinking_segments
-                                                    .entry((message_id, ix))
-                                                    .or_insert(false);
-
-                                                *is_open = !*is_open;
-                                            }
-                                        })),
-                                ),
-                            ),
-                    )
-                    .child(
-                        div()
-                            .id(("thinking-content", ix))
-                            .relative()
-                            .mt_1p5()
-                            .ml_1p5()
-                            .pl_2p5()
-                            .border_l_1()
-                            .border_color(cx.theme().colors().border_variant)
-                            .text_ui_sm(cx)
-                            .when(is_open, |this| {
-                                this.child(
-                                    MarkdownElement::new(
-                                        markdown.clone(),
-                                        default_markdown_style(window, cx),
-                                    )
-                                    .on_url_click({
-                                        let workspace = self.workspace.clone();
-                                        move |text, window, cx| {
-                                            open_markdown_link(text, workspace.clone(), window, cx);
-                                        }
-                                    }),
-                                )
-                            }),
-                    )
-            }
-        })
-    }
-
-    fn render_tool_use(
-        &self,
-        tool_use: ToolUse,
-        window: &mut Window,
-        workspace: WeakEntity<Workspace>,
-        cx: &mut Context<Self>,
-    ) -> impl IntoElement + use<> {
-        if let Some(card) = self.thread.read(cx).card_for_tool(&tool_use.id) {
-            return card.render(&tool_use.status, window, workspace, cx);
-        }
-
-        let is_open = self
-            .expanded_tool_uses
-            .get(&tool_use.id)
-            .copied()
-            .unwrap_or_default();
-
-        let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_));
-
-        let fs = self
-            .workspace
-            .upgrade()
-            .map(|workspace| workspace.read(cx).app_state().fs.clone());
-        let needs_confirmation = matches!(&tool_use.status, ToolUseStatus::NeedsConfirmation);
-        let needs_confirmation_tools = tool_use.needs_confirmation;
-
-        let status_icons = div().child(match &tool_use.status {
-            ToolUseStatus::NeedsConfirmation => {
-                let icon = Icon::new(IconName::Warning)
-                    .color(Color::Warning)
-                    .size(IconSize::Small);
-                icon.into_any_element()
-            }
-            ToolUseStatus::Pending
-            | ToolUseStatus::InputStillStreaming
-            | ToolUseStatus::Running => Icon::new(IconName::ArrowCircle)
-                .color(Color::Accent)
-                .size(IconSize::Small)
-                .with_rotate_animation(2)
-                .into_any_element(),
-            ToolUseStatus::Finished(_) => div().w_0().into_any_element(),
-            ToolUseStatus::Error(_) => {
-                let icon = Icon::new(IconName::Close)
-                    .color(Color::Error)
-                    .size(IconSize::Small);
-                icon.into_any_element()
-            }
-        });
-
-        let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned();
-        let results_content_container = || v_flex().p_2().gap_0p5();
-
-        let results_content = v_flex()
-            .gap_1()
-            .child(
-                results_content_container()
-                    .child(
-                        Label::new("Input")
-                            .size(LabelSize::XSmall)
-                            .color(Color::Muted)
-                            .buffer_font(cx),
-                    )
-                    .child(
-                        div()
-                            .w_full()
-                            .text_ui_sm(cx)
-                            .children(rendered_tool_use.as_ref().map(|rendered| {
-                                MarkdownElement::new(
-                                    rendered.input.clone(),
-                                    tool_use_markdown_style(window, cx),
-                                )
-                                .code_block_renderer(markdown::CodeBlockRenderer::Default {
-                                    copy_button: false,
-                                    copy_button_on_hover: false,
-                                    border: false,
-                                })
-                                .on_url_click({
-                                    let workspace = self.workspace.clone();
-                                    move |text, window, cx| {
-                                        open_markdown_link(text, workspace.clone(), window, cx);
-                                    }
-                                })
-                            })),
-                    ),
-            )
-            .map(|container| match tool_use.status {
-                ToolUseStatus::Finished(_) => container.child(
-                    results_content_container()
-                        .border_t_1()
-                        .border_color(self.tool_card_border_color(cx))
-                        .child(
-                            Label::new("Result")
-                                .size(LabelSize::XSmall)
-                                .color(Color::Muted)
-                                .buffer_font(cx),
-                        )
-                        .child(div().w_full().text_ui_sm(cx).children(
-                            rendered_tool_use.as_ref().map(|rendered| {
-                                MarkdownElement::new(
-                                    rendered.output.clone(),
-                                    tool_use_markdown_style(window, cx),
-                                )
-                                .code_block_renderer(markdown::CodeBlockRenderer::Default {
-                                    copy_button: false,
-                                    copy_button_on_hover: false,
-                                    border: false,
-                                })
-                                .on_url_click({
-                                    let workspace = self.workspace.clone();
-                                    move |text, window, cx| {
-                                        open_markdown_link(text, workspace.clone(), window, cx);
-                                    }
-                                })
-                                .into_any_element()
-                            }),
-                        )),
-                ),
-                ToolUseStatus::InputStillStreaming | ToolUseStatus::Running => container.child(
-                    results_content_container()
-                        .border_t_1()
-                        .border_color(self.tool_card_border_color(cx))
-                        .child(
-                            h_flex()
-                                .gap_1()
-                                .child(
-                                    Icon::new(IconName::ArrowCircle)
-                                        .size(IconSize::Small)
-                                        .color(Color::Accent)
-                                        .with_rotate_animation(2),
-                                )
-                                .child(
-                                    Label::new("Running…")
-                                        .size(LabelSize::XSmall)
-                                        .color(Color::Muted)
-                                        .buffer_font(cx),
-                                ),
-                        ),
-                ),
-                ToolUseStatus::Error(_) => container.child(
-                    results_content_container()
-                        .border_t_1()
-                        .border_color(self.tool_card_border_color(cx))
-                        .child(
-                            Label::new("Error")
-                                .size(LabelSize::XSmall)
-                                .color(Color::Muted)
-                                .buffer_font(cx),
-                        )
-                        .child(
-                            div()
-                                .text_ui_sm(cx)
-                                .children(rendered_tool_use.as_ref().map(|rendered| {
-                                    MarkdownElement::new(
-                                        rendered.output.clone(),
-                                        tool_use_markdown_style(window, cx),
-                                    )
-                                    .on_url_click({
-                                        let workspace = self.workspace.clone();
-                                        move |text, window, cx| {
-                                            open_markdown_link(text, workspace.clone(), window, cx);
-                                        }
-                                    })
-                                    .into_any_element()
-                                })),
-                        ),
-                ),
-                ToolUseStatus::Pending => container,
-                ToolUseStatus::NeedsConfirmation => container.child(
-                    results_content_container()
-                        .border_t_1()
-                        .border_color(self.tool_card_border_color(cx))
-                        .child(
-                            Label::new("Asking Permission")
-                                .size(LabelSize::Small)
-                                .color(Color::Muted)
-                                .buffer_font(cx),
-                        ),
-                ),
-            });
-
-        let gradient_overlay = |color: Hsla| {
-            div()
-                .h_full()
-                .absolute()
-                .w_12()
-                .bottom_0()
-                .map(|element| {
-                    if is_status_finished {
-                        element.right_6()
-                    } else {
-                        element.right(px(44.))
-                    }
-                })
-                .bg(linear_gradient(
-                    90.,
-                    linear_color_stop(color, 1.),
-                    linear_color_stop(color.opacity(0.2), 0.),
-                ))
-        };
-
-        v_flex().gap_1().mb_2().map(|element| {
-            if !needs_confirmation_tools {
-                element.child(
-                    v_flex()
-                        .child(
-                            h_flex()
-                                .group("disclosure-header")
-                                .relative()
-                                .gap_1p5()
-                                .justify_between()
-                                .opacity(0.8)
-                                .hover(|style| style.opacity(1.))
-                                .when(!is_status_finished, |this| this.pr_2())
-                                .child(
-                                    h_flex()
-                                        .id("tool-label-container")
-                                        .gap_1p5()
-                                        .max_w_full()
-                                        .overflow_x_scroll()
-                                        .child(
-                                            Icon::new(tool_use.icon)
-                                                .size(IconSize::Small)
-                                                .color(Color::Muted),
-                                        )
-                                        .child(
-                                            h_flex().pr_8().text_size(rems(0.8125)).children(
-                                                rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
-                                                    open_markdown_link(text, workspace.clone(), window, cx);
-                                                }}))
-                                            ),
-                                        ),
-                                )
-                                .child(
-                                    h_flex()
-                                        .gap_1()
-                                        .child(
-                                            div().visible_on_hover("disclosure-header").child(
-                                                Disclosure::new("tool-use-disclosure", is_open)
-                                                    .opened_icon(IconName::ChevronUp)
-                                                    .closed_icon(IconName::ChevronDown)
-                                                    .on_click(cx.listener({
-                                                        let tool_use_id = tool_use.id.clone();
-                                                        move |this, _event, _window, _cx| {
-                                                            let is_open = this
-                                                                .expanded_tool_uses
-                                                                .entry(tool_use_id.clone())
-                                                                .or_insert(false);
-
-                                                            *is_open = !*is_open;
-                                                        }
-                                                    })),
-                                            ),
-                                        )
-                                        .child(status_icons),
-                                )
-                                .child(gradient_overlay(cx.theme().colors().panel_background)),
-                        )
-                        .map(|parent| {
-                            if !is_open {
-                                return parent;
-                            }
-
-                            parent.child(
-                                v_flex()
-                                    .mt_1()
-                                    .border_1()
-                                    .border_color(self.tool_card_border_color(cx))
-                                    .bg(cx.theme().colors().editor_background)
-                                    .rounded_lg()
-                                    .child(results_content),
-                            )
-                        }),
-                )
-            } else {
-                v_flex()
-                    .mb_2()
-                    .rounded_lg()
-                    .border_1()
-                    .border_color(self.tool_card_border_color(cx))
-                    .overflow_hidden()
-                    .child(
-                        h_flex()
-                            .group("disclosure-header")
-                            .relative()
-                            .justify_between()
-                            .py_1()
-                            .map(|element| {
-                                if is_status_finished {
-                                    element.pl_2().pr_0p5()
-                                } else {
-                                    element.px_2()
-                                }
-                            })
-                            .bg(self.tool_card_header_bg(cx))
-                            .map(|element| {
-                                if is_open {
-                                    element.border_b_1().rounded_t_md()
-                                } else if needs_confirmation {
-                                    element.rounded_t_md()
-                                } else {
-                                    element.rounded_md()
-                                }
-                            })
-                            .border_color(self.tool_card_border_color(cx))
-                            .child(
-                                h_flex()
-                                    .id("tool-label-container")
-                                    .gap_1p5()
-                                    .max_w_full()
-                                    .overflow_x_scroll()
-                                    .child(
-                                        Icon::new(tool_use.icon)
-                                            .size(IconSize::XSmall)
-                                            .color(Color::Muted),
-                                    )
-                                    .child(
-                                        h_flex().pr_8().text_ui_sm(cx).children(
-                                            rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
-                                                open_markdown_link(text, workspace.clone(), window, cx);
-                                            }}))
-                                        ),
-                                    ),
-                            )
-                            .child(
-                                h_flex()
-                                    .gap_1()
-                                    .child(
-                                        div().visible_on_hover("disclosure-header").child(
-                                            Disclosure::new("tool-use-disclosure", is_open)
-                                                .opened_icon(IconName::ChevronUp)
-                                                .closed_icon(IconName::ChevronDown)
-                                                .on_click(cx.listener({
-                                                    let tool_use_id = tool_use.id.clone();
-                                                    move |this, _event, _window, _cx| {
-                                                        let is_open = this
-                                                            .expanded_tool_uses
-                                                            .entry(tool_use_id.clone())
-                                                            .or_insert(false);
-
-                                                        *is_open = !*is_open;
-                                                    }
-                                                })),
-                                        ),
-                                    )
-                                    .child(status_icons),
-                            )
-                            .child(gradient_overlay(self.tool_card_header_bg(cx))),
-                    )
-                    .map(|parent| {
-                        if !is_open {
-                            return parent;
-                        }
-
-                        parent.child(
-                            v_flex()
-                                .bg(cx.theme().colors().editor_background)
-                                .map(|element| {
-                                    if  needs_confirmation {
-                                        element.rounded_none()
-                                    } else {
-                                        element.rounded_b_lg()
-                                    }
-                                })
-                                .child(results_content),
-                        )
-                    })
-                    .when(needs_confirmation, |this| {
-                        this.child(
-                            h_flex()
-                                .py_1()
-                                .pl_2()
-                                .pr_1()
-                                .gap_1()
-                                .justify_between()
-                                .flex_wrap()
-                                .bg(cx.theme().colors().editor_background)
-                                .border_t_1()
-                                .border_color(self.tool_card_border_color(cx))
-                                .rounded_b_lg()
-                                .child(
-                                    div()
-                                        .min_w(rems_from_px(145.))
-                                        .child(LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
-                                    )
-                                )
-                                .child(
-                                    h_flex()
-                                        .gap_0p5()
-                                        .child({
-                                            let tool_id = tool_use.id.clone();
-                                            Button::new(
-                                                "always-allow-tool-action",
-                                                "Always Allow",
-                                            )
-                                            .label_size(LabelSize::Small)
-                                            .icon(IconName::CheckDouble)
-                                            .icon_position(IconPosition::Start)
-                                            .icon_size(IconSize::Small)
-                                            .icon_color(Color::Success)
-                                            .tooltip(move |window, cx|  {
-                                                Tooltip::with_meta(
-                                                    "Never ask for permission",
-                                                    None,
-                                                    "Restore the original behavior in your Agent Panel settings",
-                                                    window,
-                                                    cx,
-                                                )
-                                            })
-                                            .on_click(cx.listener(
-                                                move |this, event, window, cx| {
-                                                    if let Some(fs) = fs.clone() {
-                                                        update_settings_file::<AgentSettings>(
-                                                            fs.clone(),
-                                                            cx,
-                                                            |settings, _| {
-                                                                settings.set_always_allow_tool_actions(true);
-                                                            },
-                                                        );
-                                                    }
-                                                    this.handle_allow_tool(
-                                                        tool_id.clone(),
-                                                        event,
-                                                        window,
-                                                        cx,
-                                                    )
-                                                },
-                                            ))
-                                        })
-                                        .child({
-                                            let tool_id = tool_use.id.clone();
-                                            Button::new("allow-tool-action", "Allow")
-                                                .label_size(LabelSize::Small)
-                                                .icon(IconName::Check)
-                                                .icon_position(IconPosition::Start)
-                                                .icon_size(IconSize::Small)
-                                                .icon_color(Color::Success)
-                                                .on_click(cx.listener(
-                                                    move |this, event, window, cx| {
-                                                        this.handle_allow_tool(
-                                                            tool_id.clone(),
-                                                            event,
-                                                            window,
-                                                            cx,
-                                                        )
-                                                    },
-                                                ))
-                                        })
-                                        .child({
-                                            let tool_id = tool_use.id.clone();
-                                            let tool_name: Arc<str> = tool_use.name.into();
-                                            Button::new("deny-tool", "Deny")
-                                                .label_size(LabelSize::Small)
-                                                .icon(IconName::Close)
-                                                .icon_position(IconPosition::Start)
-                                                .icon_size(IconSize::Small)
-                                                .icon_color(Color::Error)
-                                                .on_click(cx.listener(
-                                                    move |this, event, window, cx| {
-                                                        this.handle_deny_tool(
-                                                            tool_id.clone(),
-                                                            tool_name.clone(),
-                                                            event,
-                                                            window,
-                                                            cx,
-                                                        )
-                                                    },
-                                                ))
-                                        }),
-                                ),
-                        )
-                    })
-            }
-        }).into_any_element()
-    }
-
-    fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
-        let project_context = self.thread.read(cx).project_context();
-        let project_context = project_context.borrow();
-        let Some(project_context) = project_context.as_ref() else {
-            return div().into_any();
-        };
-
-        let user_rules_text = if project_context.user_rules.is_empty() {
-            None
-        } else if project_context.user_rules.len() == 1 {
-            let user_rules = &project_context.user_rules[0];
-
-            match user_rules.title.as_ref() {
-                Some(title) => Some(format!("Using \"{title}\" user rule")),
-                None => Some("Using user rule".into()),
-            }
-        } else {
-            Some(format!(
-                "Using {} user rules",
-                project_context.user_rules.len()
-            ))
-        };
-
-        let first_user_rules_id = project_context
-            .user_rules
-            .first()
-            .map(|user_rules| user_rules.uuid.0);
-
-        let rules_files = project_context
-            .worktrees
-            .iter()
-            .filter_map(|worktree| worktree.rules_file.as_ref())
-            .collect::<Vec<_>>();
-
-        let rules_file_text = match rules_files.as_slice() {
-            &[] => None,
-            &[rules_file] => Some(format!(
-                "Using project {:?} file",
-                rules_file.path_in_worktree
-            )),
-            rules_files => Some(format!("Using {} project rules files", rules_files.len())),
-        };
-
-        if user_rules_text.is_none() && rules_file_text.is_none() {
-            return div().into_any();
-        }
-
-        v_flex()
-            .pt_2()
-            .px_2p5()
-            .gap_1()
-            .when_some(user_rules_text, |parent, user_rules_text| {
-                parent.child(
-                    h_flex()
-                        .w_full()
-                        .child(
-                            Icon::new(RULES_ICON)
-                                .size(IconSize::XSmall)
-                                .color(Color::Disabled),
-                        )
-                        .child(
-                            Label::new(user_rules_text)
-                                .size(LabelSize::XSmall)
-                                .color(Color::Muted)
-                                .truncate()
-                                .buffer_font(cx)
-                                .ml_1p5()
-                                .mr_0p5(),
-                        )
-                        .child(
-                            IconButton::new("open-prompt-library", IconName::ArrowUpRight)
-                                .shape(ui::IconButtonShape::Square)
-                                .icon_size(IconSize::XSmall)
-                                .icon_color(Color::Ignored)
-                                // TODO: Figure out a way to pass focus handle here so we can display the `OpenRulesLibrary`  keybinding
-                                .tooltip(Tooltip::text("View User Rules"))
-                                .on_click(move |_event, window, cx| {
-                                    window.dispatch_action(
-                                        Box::new(OpenRulesLibrary {
-                                            prompt_to_select: first_user_rules_id,
-                                        }),
-                                        cx,
-                                    )
-                                }),
-                        ),
-                )
-            })
-            .when_some(rules_file_text, |parent, rules_file_text| {
-                parent.child(
-                    h_flex()
-                        .w_full()
-                        .child(
-                            Icon::new(IconName::File)
-                                .size(IconSize::XSmall)
-                                .color(Color::Disabled),
-                        )
-                        .child(
-                            Label::new(rules_file_text)
-                                .size(LabelSize::XSmall)
-                                .color(Color::Muted)
-                                .buffer_font(cx)
-                                .ml_1p5()
-                                .mr_0p5(),
-                        )
-                        .child(
-                            IconButton::new("open-rule", IconName::ArrowUpRight)
-                                .shape(ui::IconButtonShape::Square)
-                                .icon_size(IconSize::XSmall)
-                                .icon_color(Color::Ignored)
-                                .on_click(cx.listener(Self::handle_open_rules))
-                                .tooltip(Tooltip::text("View Rules")),
-                        ),
-                )
-            })
-            .into_any()
-    }
-
-    fn handle_allow_tool(
-        &mut self,
-        tool_use_id: LanguageModelToolUseId,
-        _: &ClickEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
-            .thread
-            .read(cx)
-            .pending_tool(&tool_use_id)
-            .map(|tool_use| tool_use.status.clone())
-        {
-            self.thread.update(cx, |thread, cx| {
-                if let Some(configured) = thread.get_or_init_configured_model(cx) {
-                    thread.run_tool(
-                        c.tool_use_id.clone(),
-                        c.ui_text.clone(),
-                        c.input.clone(),
-                        c.request.clone(),
-                        c.tool.clone(),
-                        configured.model,
-                        Some(window.window_handle()),
-                        cx,
-                    );
-                }
-            });
-        }
-    }
-
-    fn handle_deny_tool(
-        &mut self,
-        tool_use_id: LanguageModelToolUseId,
-        tool_name: Arc<str>,
-        _: &ClickEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let window_handle = window.window_handle();
-        self.thread.update(cx, |thread, cx| {
-            thread.deny_tool_use(tool_use_id, tool_name, Some(window_handle), cx);
-        });
-    }
-
-    fn handle_open_rules(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
-        let project_context = self.thread.read(cx).project_context();
-        let project_context = project_context.borrow();
-        let Some(project_context) = project_context.as_ref() else {
-            return;
-        };
-
-        let project_entry_ids = project_context
-            .worktrees
-            .iter()
-            .flat_map(|worktree| worktree.rules_file.as_ref())
-            .map(|rules_file| ProjectEntryId::from_usize(rules_file.project_entry_id))
-            .collect::<Vec<_>>();
-
-        self.workspace
-            .update(cx, move |workspace, cx| {
-                // TODO: Open a multibuffer instead? In some cases this doesn't make the set of rules
-                // files clear. For example, if rules file 1 is already open but rules file 2 is not,
-                // this would open and focus rules file 2 in a tab that is not next to rules file 1.
-                let project = workspace.project().read(cx);
-                let project_paths = project_entry_ids
-                    .into_iter()
-                    .flat_map(|entry_id| project.path_for_entry(entry_id, cx))
-                    .collect::<Vec<_>>();
-                for project_path in project_paths {
-                    workspace
-                        .open_path(project_path, None, true, window, cx)
-                        .detach_and_log_err(cx);
-                }
-            })
-            .ok();
-    }
-
-    fn dismiss_notifications(&mut self, cx: &mut Context<ActiveThread>) {
-        for window in self.notifications.drain(..) {
-            window
-                .update(cx, |_, window, _| {
-                    window.remove_window();
-                })
-                .ok();
-
-            self.notification_subscriptions.remove(&window);
-        }
-    }
-
-    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Stateful<Div> {
-        div()
-            .occlude()
-            .id("active-thread-scrollbar")
-            .on_mouse_move(cx.listener(|_, _, _, cx| {
-                cx.notify();
-                cx.stop_propagation()
-            }))
-            .on_hover(|_, _, cx| {
-                cx.stop_propagation();
-            })
-            .on_any_mouse_down(|_, _, cx| {
-                cx.stop_propagation();
-            })
-            .on_mouse_up(
-                MouseButton::Left,
-                cx.listener(|_, _, _, cx| {
-                    cx.stop_propagation();
-                }),
-            )
-            .on_scroll_wheel(cx.listener(|_, _, _, cx| {
-                cx.notify();
-            }))
-            .h_full()
-            .absolute()
-            .right_1()
-            .top_1()
-            .bottom_0()
-            .w(px(12.))
-            .cursor_default()
-            .children(Scrollbar::vertical(self.scrollbar_state.clone()).map(|s| s.auto_hide(cx)))
-    }
-
-    pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
-        self.expanded_code_blocks
-            .get(&(message_id, ix))
-            .copied()
-            .unwrap_or(true)
-    }
-
-    pub fn toggle_codeblock_expanded(&mut self, message_id: MessageId, ix: usize) {
-        let is_expanded = self
-            .expanded_code_blocks
-            .entry((message_id, ix))
-            .or_insert(true);
-        *is_expanded = !*is_expanded;
-    }
-
-    pub fn scroll_to_top(&mut self, cx: &mut Context<Self>) {
-        self.list_state.scroll_to(ListOffset::default());
-        cx.notify();
-    }
-
-    pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
-        self.list_state.reset(self.messages.len());
-        cx.notify();
-    }
-}
-
-pub enum ActiveThreadEvent {
-    EditingMessageTokenCountChanged,
-}
-
-impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
-
-impl Render for ActiveThread {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        v_flex()
-            .size_full()
-            .relative()
-            .bg(cx.theme().colors().panel_background)
-            .child(list(self.list_state.clone(), cx.processor(Self::render_message)).flex_grow())
-            .child(self.render_vertical_scrollbar(cx))
-    }
-}
-
-pub(crate) fn open_active_thread_as_markdown(
-    thread: Entity<Thread>,
-    workspace: Entity<Workspace>,
-    window: &mut Window,
-    cx: &mut App,
-) -> Task<anyhow::Result<()>> {
-    let markdown_language_task = workspace
-        .read(cx)
-        .app_state()
-        .languages
-        .language_for_name("Markdown");
-
-    window.spawn(cx, async move |cx| {
-        let markdown_language = markdown_language_task.await?;
-
-        workspace.update_in(cx, |workspace, window, cx| {
-            let thread = thread.read(cx);
-            let markdown = thread.to_markdown(cx)?;
-            let thread_summary = thread.summary().or_default().to_string();
-
-            let project = workspace.project().clone();
-
-            if !project.read(cx).is_local() {
-                anyhow::bail!("failed to open active thread as markdown in remote project");
-            }
-
-            let buffer = project.update(cx, |project, cx| {
-                project.create_local_buffer(&markdown, Some(markdown_language), true, cx)
-            });
-            let buffer =
-                cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone()));
-
-            workspace.add_item_to_active_pane(
-                Box::new(cx.new(|cx| {
-                    let mut editor =
-                        Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
-                    editor.set_breadcrumb_header(thread_summary);
-                    editor
-                })),
-                None,
-                true,
-                window,
-                cx,
-            );
-
-            anyhow::Ok(())
-        })??;
-        anyhow::Ok(())
-    })
-}
-
-pub(crate) fn open_context(
-    context: &AgentContextHandle,
-    workspace: Entity<Workspace>,
-    window: &mut Window,
-    cx: &mut App,
-) {
-    match context {
-        AgentContextHandle::File(file_context) => {
-            if let Some(project_path) = file_context.project_path(cx) {
-                workspace.update(cx, |workspace, cx| {
-                    workspace
-                        .open_path(project_path, None, true, window, cx)
-                        .detach_and_log_err(cx);
-                });
-            }
-        }
-
-        AgentContextHandle::Directory(directory_context) => {
-            let entry_id = directory_context.entry_id;
-            workspace.update(cx, |workspace, cx| {
-                workspace.project().update(cx, |_project, cx| {
-                    cx.emit(project::Event::RevealInProjectPanel(entry_id));
-                })
-            })
-        }
-
-        AgentContextHandle::Symbol(symbol_context) => {
-            let buffer = symbol_context.buffer.read(cx);
-            if let Some(project_path) = buffer.project_path(cx) {
-                let snapshot = buffer.snapshot();
-                let target_position = symbol_context.range.start.to_point(&snapshot);
-                open_editor_at_position(project_path, target_position, &workspace, window, cx)
-                    .detach();
-            }
-        }
-
-        AgentContextHandle::Selection(selection_context) => {
-            let buffer = selection_context.buffer.read(cx);
-            if let Some(project_path) = buffer.project_path(cx) {
-                let snapshot = buffer.snapshot();
-                let target_position = selection_context.range.start.to_point(&snapshot);
-
-                open_editor_at_position(project_path, target_position, &workspace, window, cx)
-                    .detach();
-            }
-        }
-
-        AgentContextHandle::FetchedUrl(fetched_url_context) => {
-            cx.open_url(&fetched_url_context.url);
-        }
-
-        AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
-            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                let thread = thread_context.thread.clone();
-                window.defer(cx, move |window, cx| {
-                    panel.update(cx, |panel, cx| {
-                        panel.open_thread(thread, window, cx);
-                    });
-                });
-            }
-        }),
-
-        AgentContextHandle::TextThread(text_thread_context) => {
-            workspace.update(cx, |workspace, cx| {
-                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                    let context = text_thread_context.context.clone();
-                    window.defer(cx, move |window, cx| {
-                        panel.update(cx, |panel, cx| {
-                            panel.open_prompt_editor(context, window, cx)
-                        });
-                    });
-                }
-            })
-        }
-
-        AgentContextHandle::Rules(rules_context) => window.dispatch_action(
-            Box::new(OpenRulesLibrary {
-                prompt_to_select: Some(rules_context.prompt_id.0),
-            }),
-            cx,
-        ),
-
-        AgentContextHandle::Image(_) => {}
-    }
-}
-
-pub(crate) fn attach_pasted_images_as_context(
-    context_store: &Entity<ContextStore>,
-    cx: &mut App,
-) -> bool {
-    let images = cx
-        .read_from_clipboard()
-        .map(|item| {
-            item.into_entries()
-                .filter_map(|entry| {
-                    if let ClipboardEntry::Image(image) = entry {
-                        Some(image)
-                    } else {
-                        None
-                    }
-                })
-                .collect::<Vec<_>>()
-        })
-        .unwrap_or_default();
-
-    if images.is_empty() {
-        return false;
-    }
-    cx.stop_propagation();
-
-    context_store.update(cx, |store, cx| {
-        for image in images {
-            store.add_image_instance(Arc::new(image), cx);
-        }
-    });
-    true
-}
-
-fn open_editor_at_position(
-    project_path: project::ProjectPath,
-    target_position: Point,
-    workspace: &Entity<Workspace>,
-    window: &mut Window,
-    cx: &mut App,
-) -> Task<()> {
-    let open_task = workspace.update(cx, |workspace, cx| {
-        workspace.open_path(project_path, None, true, window, cx)
-    });
-    window.spawn(cx, async move |cx| {
-        if let Some(active_editor) = open_task
-            .await
-            .log_err()
-            .and_then(|item| item.downcast::<Editor>())
-        {
-            active_editor
-                .downgrade()
-                .update_in(cx, |editor, window, cx| {
-                    editor.go_to_singleton_buffer_point(target_position, window, cx);
-                })
-                .log_err();
-        }
-    })
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use agent::{MessageSegment, context::ContextLoadResult, thread_store};
-    use assistant_tool::{ToolRegistry, ToolWorkingSet};
-    use editor::EditorSettings;
-    use fs::FakeFs;
-    use gpui::{AppContext, TestAppContext, VisualTestContext};
-    use language_model::{
-        ConfiguredModel, LanguageModel, LanguageModelRegistry,
-        fake_provider::{FakeLanguageModel, FakeLanguageModelProvider},
-    };
-    use project::Project;
-    use prompt_store::PromptBuilder;
-    use serde_json::json;
-    use settings::SettingsStore;
-    use util::path;
-    use workspace::CollaboratorId;
-
-    #[gpui::test]
-    async fn test_agent_is_unfollowed_after_cancelling_completion(cx: &mut TestAppContext) {
-        init_test_settings(cx);
-
-        let project = create_test_project(
-            cx,
-            json!({"code.rs": "fn main() {\n    println!(\"Hello, world!\");\n}"}),
-        )
-        .await;
-
-        let (cx, _active_thread, workspace, thread, model) =
-            setup_test_environment(cx, project.clone()).await;
-
-        // Insert user message without any context (empty context vector)
-        thread.update(cx, |thread, cx| {
-            thread.insert_user_message(
-                "What is the best way to learn Rust?",
-                ContextLoadResult::default(),
-                None,
-                vec![],
-                cx,
-            );
-        });
-
-        // Stream response to user message
-        thread.update(cx, |thread, cx| {
-            let intent = CompletionIntent::UserPrompt;
-            let request = thread.to_completion_request(model.clone(), intent, cx);
-            thread.stream_completion(request, model, intent, cx.active_window(), cx)
-        });
-        // Follow the agent
-        cx.update(|window, cx| {
-            workspace.update(cx, |workspace, cx| {
-                workspace.follow(CollaboratorId::Agent, window, cx);
-            })
-        });
-        assert!(cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
-
-        // Cancel the current completion
-        thread.update(cx, |thread, cx| {
-            thread.cancel_last_completion(cx.active_window(), cx)
-        });
-
-        cx.executor().run_until_parked();
-
-        // No longer following the agent
-        assert!(!cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
-    }
-
-    #[gpui::test]
-    async fn test_reinserting_creases_for_edited_message(cx: &mut TestAppContext) {
-        init_test_settings(cx);
-
-        let project = create_test_project(cx, json!({})).await;
-
-        let (cx, active_thread, _, thread, model) =
-            setup_test_environment(cx, project.clone()).await;
-        cx.update(|_, cx| {
-            LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
-                registry.set_default_model(
-                    Some(ConfiguredModel {
-                        provider: Arc::new(FakeLanguageModelProvider::default()),
-                        model,
-                    }),
-                    cx,
-                );
-            });
-        });
-
-        let creases = vec![MessageCrease {
-            range: 14..22,
-            icon_path: "icon".into(),
-            label: "foo.txt".into(),
-            context: None,
-        }];
-
-        let message = thread.update(cx, |thread, cx| {
-            let message_id = thread.insert_user_message(
-                "Tell me about @foo.txt",
-                ContextLoadResult::default(),
-                None,
-                creases,
-                cx,
-            );
-            thread.message(message_id).cloned().unwrap()
-        });
-
-        active_thread.update_in(cx, |active_thread, window, cx| {
-            if let Some(message_text) = message.segments.first().and_then(MessageSegment::text) {
-                active_thread.start_editing_message(
-                    message.id,
-                    message_text,
-                    message.creases.as_slice(),
-                    window,
-                    cx,
-                );
-            }
-            let editor = active_thread
-                .editing_message
-                .as_ref()
-                .unwrap()
-                .1
-                .editor
-                .clone();
-            editor.update(cx, |editor, cx| editor.edit([(0..13, "modified")], cx));
-            active_thread.confirm_editing_message(&Default::default(), window, cx);
-        });
-        cx.run_until_parked();
-
-        let message = thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
-        active_thread.update_in(cx, |active_thread, window, cx| {
-            if let Some(message_text) = message.segments.first().and_then(MessageSegment::text) {
-                active_thread.start_editing_message(
-                    message.id,
-                    message_text,
-                    message.creases.as_slice(),
-                    window,
-                    cx,
-                );
-            }
-            let editor = active_thread
-                .editing_message
-                .as_ref()
-                .unwrap()
-                .1
-                .editor
-                .clone();
-            let text = editor.update(cx, |editor, cx| editor.text(cx));
-            assert_eq!(text, "modified @foo.txt");
-        });
-    }
-
-    #[gpui::test]
-    async fn test_editing_message_cancels_previous_completion(cx: &mut TestAppContext) {
-        init_test_settings(cx);
-
-        let project = create_test_project(cx, json!({})).await;
-
-        let (cx, active_thread, _, thread, model) =
-            setup_test_environment(cx, project.clone()).await;
-
-        cx.update(|_, cx| {
-            LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
-                registry.set_default_model(
-                    Some(ConfiguredModel {
-                        provider: Arc::new(FakeLanguageModelProvider::default()),
-                        model: model.clone(),
-                    }),
-                    cx,
-                );
-            });
-        });
-
-        // Track thread events to verify cancellation
-        let cancellation_events = Arc::new(std::sync::Mutex::new(Vec::new()));
-        let new_request_events = Arc::new(std::sync::Mutex::new(Vec::new()));
-
-        let _subscription = cx.update(|_, cx| {
-            let cancellation_events = cancellation_events.clone();
-            let new_request_events = new_request_events.clone();
-            cx.subscribe(
-                &thread,
-                move |_thread, event: &ThreadEvent, _cx| match event {
-                    ThreadEvent::CompletionCanceled => {
-                        cancellation_events.lock().unwrap().push(());
-                    }
-                    ThreadEvent::NewRequest => {
-                        new_request_events.lock().unwrap().push(());
-                    }
-                    _ => {}
-                },
-            )
-        });
-
-        // Insert a user message and start streaming a response
-        let message = thread.update(cx, |thread, cx| {
-            let message_id = thread.insert_user_message(
-                "Hello, how are you?",
-                ContextLoadResult::default(),
-                None,
-                vec![],
-                cx,
-            );
-            thread.advance_prompt_id();
-            thread.send_to_model(
-                model.clone(),
-                CompletionIntent::UserPrompt,
-                cx.active_window(),
-                cx,
-            );
-            thread.message(message_id).cloned().unwrap()
-        });
-
-        cx.run_until_parked();
-
-        // Verify that a completion is in progress
-        assert!(cx.read(|cx| thread.read(cx).is_generating()));
-        assert_eq!(new_request_events.lock().unwrap().len(), 1);
-
-        // Edit the message while the completion is still running
-        active_thread.update_in(cx, |active_thread, window, cx| {
-            if let Some(message_text) = message.segments.first().and_then(MessageSegment::text) {
-                active_thread.start_editing_message(
-                    message.id,
-                    message_text,
-                    message.creases.as_slice(),
-                    window,
-                    cx,
-                );
-            }
-            let editor = active_thread
-                .editing_message
-                .as_ref()
-                .unwrap()
-                .1
-                .editor
-                .clone();
-            editor.update(cx, |editor, cx| {
-                editor.set_text("What is the weather like?", window, cx);
-            });
-            active_thread.confirm_editing_message(&Default::default(), window, cx);
-        });
-
-        cx.run_until_parked();
-
-        // Verify that the previous completion was canceled
-        assert_eq!(cancellation_events.lock().unwrap().len(), 1);
-
-        // Verify that a new request was started after cancellation
-        assert_eq!(new_request_events.lock().unwrap().len(), 2);
-
-        // Verify that the edited message contains the new text
-        let edited_message =
-            thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
-        match &edited_message.segments[0] {
-            MessageSegment::Text(text) => {
-                assert_eq!(text, "What is the weather like?");
-            }
-            _ => panic!("Expected text segment"),
-        }
-    }
-
-    fn init_test_settings(cx: &mut TestAppContext) {
-        cx.update(|cx| {
-            let settings_store = SettingsStore::test(cx);
-            cx.set_global(settings_store);
-            language::init(cx);
-            Project::init_settings(cx);
-            AgentSettings::register(cx);
-            prompt_store::init(cx);
-            thread_store::init(cx);
-            workspace::init_settings(cx);
-            language_model::init_settings(cx);
-            ThemeSettings::register(cx);
-            EditorSettings::register(cx);
-            ToolRegistry::default_global(cx);
-        });
-    }
-
-    // Helper to create a test project with test files
-    async fn create_test_project(
-        cx: &mut TestAppContext,
-        files: serde_json::Value,
-    ) -> Entity<Project> {
-        let fs = FakeFs::new(cx.executor());
-        fs.insert_tree(path!("/test"), files).await;
-        Project::test(fs, [path!("/test").as_ref()], cx).await
-    }
-
-    async fn setup_test_environment(
-        cx: &mut TestAppContext,
-        project: Entity<Project>,
-    ) -> (
-        &mut VisualTestContext,
-        Entity<ActiveThread>,
-        Entity<Workspace>,
-        Entity<Thread>,
-        Arc<dyn LanguageModel>,
-    ) {
-        let (workspace, cx) =
-            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
-
-        let thread_store = cx
-            .update(|_, cx| {
-                ThreadStore::load(
-                    project.clone(),
-                    cx.new(|_| ToolWorkingSet::default()),
-                    None,
-                    Arc::new(PromptBuilder::new(None).unwrap()),
-                    cx,
-                )
-            })
-            .await
-            .unwrap();
-
-        let text_thread_store = cx
-            .update(|_, cx| {
-                TextThreadStore::new(
-                    project.clone(),
-                    Arc::new(PromptBuilder::new(None).unwrap()),
-                    Default::default(),
-                    cx,
-                )
-            })
-            .await
-            .unwrap();
-
-        let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
-        let context_store =
-            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
-
-        let model = FakeLanguageModel::default();
-        let model: Arc<dyn LanguageModel> = Arc::new(model);
-
-        let language_registry = LanguageRegistry::new(cx.executor());
-        let language_registry = Arc::new(language_registry);
-
-        let active_thread = cx.update(|window, cx| {
-            cx.new(|cx| {
-                ActiveThread::new(
-                    thread.clone(),
-                    thread_store.clone(),
-                    text_thread_store,
-                    context_store.clone(),
-                    language_registry.clone(),
-                    workspace.downgrade(),
-                    window,
-                    cx,
-                )
-            })
-        });
-
-        (cx, active_thread, workspace, thread, model)
-    }
-}

crates/agent_ui/src/agent_configuration/manage_profiles_modal.rs 🔗

@@ -2,7 +2,7 @@ mod profile_modal_header;
 
 use std::sync::Arc;
 
-use agent_settings::{AgentProfileId, AgentSettings, builtin_profiles};
+use agent_settings::{AgentProfile, AgentProfileId, AgentSettings, builtin_profiles};
 use assistant_tool::ToolWorkingSet;
 use editor::Editor;
 use fs::Fs;
@@ -16,7 +16,6 @@ use workspace::{ModalView, Workspace};
 use crate::agent_configuration::manage_profiles_modal::profile_modal_header::ProfileModalHeader;
 use crate::agent_configuration::tool_picker::{ToolPicker, ToolPickerDelegate};
 use crate::{AgentPanel, ManageProfiles};
-use agent::agent_profile::AgentProfile;
 
 use super::tool_picker::ToolPickerMode;
 

crates/agent_ui/src/agent_diff.rs 🔗

@@ -1,7 +1,6 @@
 use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll};
 use acp_thread::{AcpThread, AcpThreadEvent};
 use action_log::ActionLog;
-use agent::{Thread, ThreadEvent, ThreadSummary};
 use agent_settings::AgentSettings;
 use anyhow::Result;
 use buffer_diff::DiffHunkStatus;
@@ -19,7 +18,6 @@ use gpui::{
 };
 
 use language::{Buffer, Capability, DiskState, OffsetRangeExt, Point};
-use language_model::StopReason;
 use multi_buffer::PathKey;
 use project::{Project, ProjectItem, ProjectPath};
 use settings::{Settings, SettingsStore};
@@ -51,34 +49,29 @@ pub struct AgentDiffPane {
 
 #[derive(PartialEq, Eq, Clone)]
 pub enum AgentDiffThread {
-    Native(Entity<Thread>),
     AcpThread(Entity<AcpThread>),
 }
 
 impl AgentDiffThread {
     fn project(&self, cx: &App) -> Entity<Project> {
         match self {
-            AgentDiffThread::Native(thread) => thread.read(cx).project().clone(),
             AgentDiffThread::AcpThread(thread) => thread.read(cx).project().clone(),
         }
     }
     fn action_log(&self, cx: &App) -> Entity<ActionLog> {
         match self {
-            AgentDiffThread::Native(thread) => thread.read(cx).action_log().clone(),
             AgentDiffThread::AcpThread(thread) => thread.read(cx).action_log().clone(),
         }
     }
 
-    fn summary(&self, cx: &App) -> ThreadSummary {
+    fn title(&self, cx: &App) -> SharedString {
         match self {
-            AgentDiffThread::Native(thread) => thread.read(cx).summary().clone(),
-            AgentDiffThread::AcpThread(thread) => ThreadSummary::Ready(thread.read(cx).title()),
+            AgentDiffThread::AcpThread(thread) => thread.read(cx).title(),
         }
     }
 
     fn is_generating(&self, cx: &App) -> bool {
         match self {
-            AgentDiffThread::Native(thread) => thread.read(cx).is_generating(),
             AgentDiffThread::AcpThread(thread) => {
                 thread.read(cx).status() == acp_thread::ThreadStatus::Generating
             }
@@ -87,14 +80,12 @@ impl AgentDiffThread {
 
     fn has_pending_edit_tool_uses(&self, cx: &App) -> bool {
         match self {
-            AgentDiffThread::Native(thread) => thread.read(cx).has_pending_edit_tool_uses(),
             AgentDiffThread::AcpThread(thread) => thread.read(cx).has_pending_edit_tool_calls(),
         }
     }
 
     fn downgrade(&self) -> WeakAgentDiffThread {
         match self {
-            AgentDiffThread::Native(thread) => WeakAgentDiffThread::Native(thread.downgrade()),
             AgentDiffThread::AcpThread(thread) => {
                 WeakAgentDiffThread::AcpThread(thread.downgrade())
             }
@@ -102,12 +93,6 @@ impl AgentDiffThread {
     }
 }
 
-impl From<Entity<Thread>> for AgentDiffThread {
-    fn from(entity: Entity<Thread>) -> Self {
-        AgentDiffThread::Native(entity)
-    }
-}
-
 impl From<Entity<AcpThread>> for AgentDiffThread {
     fn from(entity: Entity<AcpThread>) -> Self {
         AgentDiffThread::AcpThread(entity)
@@ -116,25 +101,17 @@ impl From<Entity<AcpThread>> for AgentDiffThread {
 
 #[derive(PartialEq, Eq, Clone)]
 pub enum WeakAgentDiffThread {
-    Native(WeakEntity<Thread>),
     AcpThread(WeakEntity<AcpThread>),
 }
 
 impl WeakAgentDiffThread {
     pub fn upgrade(&self) -> Option<AgentDiffThread> {
         match self {
-            WeakAgentDiffThread::Native(weak) => weak.upgrade().map(AgentDiffThread::Native),
             WeakAgentDiffThread::AcpThread(weak) => weak.upgrade().map(AgentDiffThread::AcpThread),
         }
     }
 }
 
-impl From<WeakEntity<Thread>> for WeakAgentDiffThread {
-    fn from(entity: WeakEntity<Thread>) -> Self {
-        WeakAgentDiffThread::Native(entity)
-    }
-}
-
 impl From<WeakEntity<AcpThread>> for WeakAgentDiffThread {
     fn from(entity: WeakEntity<AcpThread>) -> Self {
         WeakAgentDiffThread::AcpThread(entity)
@@ -203,10 +180,6 @@ impl AgentDiffPane {
                     this.update_excerpts(window, cx)
                 }),
                 match &thread {
-                    AgentDiffThread::Native(thread) => cx
-                        .subscribe(thread, |this, _thread, event, cx| {
-                            this.handle_native_thread_event(event, cx)
-                        }),
                     AgentDiffThread::AcpThread(thread) => cx
                         .subscribe(thread, |this, _thread, event, cx| {
                             this.handle_acp_thread_event(event, cx)
@@ -313,19 +286,13 @@ impl AgentDiffPane {
     }
 
     fn update_title(&mut self, cx: &mut Context<Self>) {
-        let new_title = self.thread.summary(cx).unwrap_or("Agent Changes");
+        let new_title = self.thread.title(cx);
         if new_title != self.title {
             self.title = new_title;
             cx.emit(EditorEvent::TitleChanged);
         }
     }
 
-    fn handle_native_thread_event(&mut self, event: &ThreadEvent, cx: &mut Context<Self>) {
-        if let ThreadEvent::SummaryGenerated = event {
-            self.update_title(cx)
-        }
-    }
-
     fn handle_acp_thread_event(&mut self, event: &AcpThreadEvent, cx: &mut Context<Self>) {
         if let AcpThreadEvent::TitleUpdated = event {
             self.update_title(cx)
@@ -569,8 +536,8 @@ impl Item for AgentDiffPane {
     }
 
     fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement {
-        let summary = self.thread.summary(cx).unwrap_or("Agent Changes");
-        Label::new(format!("Review: {}", summary))
+        let title = self.thread.title(cx);
+        Label::new(format!("Review: {}", title))
             .color(if params.selected {
                 Color::Default
             } else {
@@ -1339,12 +1306,6 @@ impl AgentDiff {
         });
 
         let thread_subscription = match &thread {
-            AgentDiffThread::Native(thread) => cx.subscribe_in(thread, window, {
-                let workspace = workspace.clone();
-                move |this, _thread, event, window, cx| {
-                    this.handle_native_thread_event(&workspace, event, window, cx)
-                }
-            }),
             AgentDiffThread::AcpThread(thread) => cx.subscribe_in(thread, window, {
                 let workspace = workspace.clone();
                 move |this, thread, event, window, cx| {
@@ -1447,47 +1408,6 @@ impl AgentDiff {
         });
     }
 
-    fn handle_native_thread_event(
-        &mut self,
-        workspace: &WeakEntity<Workspace>,
-        event: &ThreadEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            ThreadEvent::NewRequest
-            | ThreadEvent::Stopped(Ok(StopReason::EndTurn))
-            | ThreadEvent::Stopped(Ok(StopReason::MaxTokens))
-            | ThreadEvent::Stopped(Ok(StopReason::Refusal))
-            | ThreadEvent::Stopped(Err(_))
-            | ThreadEvent::ShowError(_)
-            | ThreadEvent::CompletionCanceled => {
-                self.update_reviewing_editors(workspace, window, cx);
-            }
-            // intentionally being exhaustive in case we add a variant we should handle
-            ThreadEvent::Stopped(Ok(StopReason::ToolUse))
-            | ThreadEvent::StreamedCompletion
-            | ThreadEvent::ReceivedTextChunk
-            | ThreadEvent::StreamedAssistantText(_, _)
-            | ThreadEvent::StreamedAssistantThinking(_, _)
-            | ThreadEvent::StreamedToolUse { .. }
-            | ThreadEvent::InvalidToolInput { .. }
-            | ThreadEvent::MissingToolUse { .. }
-            | ThreadEvent::MessageAdded(_)
-            | ThreadEvent::MessageEdited(_)
-            | ThreadEvent::MessageDeleted(_)
-            | ThreadEvent::SummaryGenerated
-            | ThreadEvent::SummaryChanged
-            | ThreadEvent::UsePendingTools { .. }
-            | ThreadEvent::ToolFinished { .. }
-            | ThreadEvent::CheckpointChanged
-            | ThreadEvent::ToolConfirmationNeeded
-            | ThreadEvent::ToolUseLimitReached
-            | ThreadEvent::CancelEditing
-            | ThreadEvent::ProfileChanged => {}
-        }
-    }
-
     fn handle_acp_thread_event(
         &mut self,
         workspace: &WeakEntity<Workspace>,
@@ -1890,16 +1810,14 @@ impl editor::Addon for EditorAgentDiffAddon {
 mod tests {
     use super::*;
     use crate::Keep;
-    use agent::thread_store::{self, ThreadStore};
+    use acp_thread::AgentConnection as _;
     use agent_settings::AgentSettings;
-    use assistant_tool::ToolWorkingSet;
     use editor::EditorSettings;
     use gpui::{TestAppContext, UpdateGlobal, VisualTestContext};
     use project::{FakeFs, Project};
-    use prompt_store::PromptBuilder;
     use serde_json::json;
     use settings::{Settings, SettingsStore};
-    use std::sync::Arc;
+    use std::{path::Path, rc::Rc};
     use theme::ThemeSettings;
     use util::path;
 
@@ -1912,7 +1830,6 @@ mod tests {
             Project::init_settings(cx);
             AgentSettings::register(cx);
             prompt_store::init(cx);
-            thread_store::init(cx);
             workspace::init_settings(cx);
             ThemeSettings::register(cx);
             EditorSettings::register(cx);
@@ -1932,21 +1849,17 @@ mod tests {
             })
             .unwrap();
 
-        let prompt_store = None;
-        let thread_store = cx
+        let connection = Rc::new(acp_thread::StubAgentConnection::new());
+        let thread = cx
             .update(|cx| {
-                ThreadStore::load(
-                    project.clone(),
-                    cx.new(|_| ToolWorkingSet::default()),
-                    prompt_store,
-                    Arc::new(PromptBuilder::new(None).unwrap()),
-                    cx,
-                )
+                connection
+                    .clone()
+                    .new_thread(project.clone(), Path::new(path!("/test")), cx)
             })
             .await
             .unwrap();
-        let thread =
-            AgentDiffThread::Native(thread_store.update(cx, |store, cx| store.create_thread(cx)));
+
+        let thread = AgentDiffThread::AcpThread(thread);
         let action_log = cx.read(|cx| thread.action_log(cx));
 
         let (workspace, cx) =
@@ -2069,7 +1982,6 @@ mod tests {
             Project::init_settings(cx);
             AgentSettings::register(cx);
             prompt_store::init(cx);
-            thread_store::init(cx);
             workspace::init_settings(cx);
             ThemeSettings::register(cx);
             EditorSettings::register(cx);
@@ -2098,22 +2010,6 @@ mod tests {
             })
             .unwrap();
 
-        let prompt_store = None;
-        let thread_store = cx
-            .update(|cx| {
-                ThreadStore::load(
-                    project.clone(),
-                    cx.new(|_| ToolWorkingSet::default()),
-                    prompt_store,
-                    Arc::new(PromptBuilder::new(None).unwrap()),
-                    cx,
-                )
-            })
-            .await
-            .unwrap();
-        let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
-        let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
-
         let (workspace, cx) =
             cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
 
@@ -2132,8 +2028,19 @@ mod tests {
             }
         });
 
+        let connection = Rc::new(acp_thread::StubAgentConnection::new());
+        let thread = cx
+            .update(|_, cx| {
+                connection
+                    .clone()
+                    .new_thread(project.clone(), Path::new(path!("/test")), cx)
+            })
+            .await
+            .unwrap();
+        let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone());
+
         // Set the active thread
-        let thread = AgentDiffThread::Native(thread);
+        let thread = AgentDiffThread::AcpThread(thread);
         cx.update(|window, cx| {
             AgentDiff::set_active_thread(&workspace.downgrade(), thread.clone(), window, cx)
         });

crates/agent_ui/src/agent_model_selector.rs 🔗

@@ -5,7 +5,6 @@ use crate::{
 use agent_settings::AgentSettings;
 use fs::Fs;
 use gpui::{Entity, FocusHandle, SharedString};
-use language_model::{ConfiguredModel, LanguageModelRegistry};
 use picker::popover_menu::PickerPopoverMenu;
 use settings::update_settings_file;
 use std::sync::Arc;
@@ -39,28 +38,6 @@ impl AgentModelSelector {
                         let provider = model.provider_id().0.to_string();
                         let model_id = model.id().0.to_string();
                         match &model_usage_context {
-                            ModelUsageContext::Thread(thread) => {
-                                thread.update(cx, |thread, cx| {
-                                    let registry = LanguageModelRegistry::read_global(cx);
-                                    if let Some(provider) = registry.provider(&model.provider_id())
-                                    {
-                                        thread.set_configured_model(
-                                            Some(ConfiguredModel {
-                                                provider,
-                                                model: model.clone(),
-                                            }),
-                                            cx,
-                                        );
-                                    }
-                                });
-                                update_settings_file::<AgentSettings>(
-                                    fs.clone(),
-                                    cx,
-                                    move |settings, _cx| {
-                                        settings.set_model(model.clone());
-                                    },
-                                );
-                            }
                             ModelUsageContext::InlineAssistant => {
                                 update_settings_file::<AgentSettings>(
                                     fs.clone(),

crates/agent_ui/src/agent_panel.rs 🔗

@@ -2,7 +2,6 @@ use std::ops::{Not, Range};
 use std::path::Path;
 use std::rc::Rc;
 use std::sync::Arc;
-use std::time::Duration;
 
 use acp_thread::AcpThread;
 use agent2::{DbThreadMetadata, HistoryEntry};
@@ -15,64 +14,52 @@ use zed_actions::OpenBrowser;
 use zed_actions::agent::{OpenClaudeCodeOnboardingModal, ReauthenticateAgent};
 
 use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
-use crate::agent_diff::AgentDiffThread;
 use crate::ui::{AcpOnboardingModal, ClaudeCodeOnboardingModal};
 use crate::{
-    AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
-    DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
-    NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
-    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu,
-    ToggleNewThreadMenu, ToggleOptionsMenu,
+    AddContextServer, DeleteRecentlyOpenThread, Follow, InlineAssistant, NewTextThread, NewThread,
+    OpenActiveThreadAsMarkdown, OpenHistory, ResetTrialEndUpsell, ResetTrialUpsell,
+    ToggleNavigationMenu, ToggleNewThreadMenu, ToggleOptionsMenu,
     acp::AcpThreadView,
-    active_thread::{self, ActiveThread, ActiveThreadEvent},
     agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
-    agent_diff::AgentDiff,
-    message_editor::{MessageEditor, MessageEditorEvent},
     slash_command::SlashCommandCompletionProvider,
-    text_thread_editor::{
-        AgentPanelDelegate, TextThreadEditor, humanize_token_count, make_lsp_adapter_delegate,
-    },
-    thread_history::{HistoryEntryElement, ThreadHistory},
+    text_thread_editor::{AgentPanelDelegate, TextThreadEditor, make_lsp_adapter_delegate},
     ui::{AgentOnboardingModal, EndTrialUpsell},
 };
 use crate::{
     ExternalAgent, NewExternalAgentThread, NewNativeAgentThreadFromSummary, placeholder_command,
 };
 use agent::{
-    Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
     context_store::ContextStore,
     history_store::{HistoryEntryId, HistoryStore},
     thread_store::{TextThreadStore, ThreadStore},
 };
-use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
+use agent_settings::{AgentDockPosition, AgentSettings, DefaultView};
 use ai_onboarding::AgentPanelOnboarding;
 use anyhow::{Result, anyhow};
 use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
 use assistant_slash_command::SlashCommandWorkingSet;
 use assistant_tool::ToolWorkingSet;
 use client::{UserStore, zed_urls};
-use cloud_llm_client::{CompletionIntent, Plan, PlanV1, PlanV2, UsageLimit};
+use cloud_llm_client::{Plan, PlanV1, PlanV2, UsageLimit};
 use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
-use feature_flags::{self, ClaudeCodeFeatureFlag, FeatureFlagAppExt, GeminiAndNativeFeatureFlag};
 use fs::Fs;
 use gpui::{
-    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
-    Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext,
-    Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
+    Action, AnyElement, App, AsyncWindowContext, Corner, DismissEvent, Entity, EventEmitter,
+    ExternalPaths, FocusHandle, Focusable, KeyContext, Pixels, Subscription, Task, UpdateGlobal,
+    WeakEntity, prelude::*,
 };
 use language::LanguageRegistry;
-use language_model::{ConfigurationError, ConfiguredModel, LanguageModelRegistry};
+use language_model::{ConfigurationError, LanguageModelRegistry};
 use project::{DisableAiSettings, Project, ProjectPath, Worktree};
 use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
 use rules_library::{RulesLibrary, open_rules_library};
 use search::{BufferSearchBar, buffer_search};
 use settings::{Settings, SettingsStore, update_settings_file};
 use theme::ThemeSettings;
-use time::UtcOffset;
 use ui::utils::WithRemSize;
 use ui::{
-    Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu,
-    PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
+    Callout, ContextMenu, ContextMenuEntry, KeyBinding, PopoverMenu, PopoverMenuHandle,
+    ProgressBar, Tab, Tooltip, prelude::*,
 };
 use util::ResultExt as _;
 use workspace::{
@@ -81,10 +68,7 @@ use workspace::{
 };
 use zed_actions::{
     DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
-    agent::{
-        OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding,
-        ToggleModelSelector,
-    },
+    agent::{OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding},
     assistant::{OpenRulesLibrary, ToggleFocus},
 };
 
@@ -150,37 +134,9 @@ pub fn init(cx: &mut App) {
                         });
                     }
                 })
-                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
-                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
-                        workspace.focus_panel::<AgentPanel>(window, cx);
-                        match &panel.read(cx).active_view {
-                            ActiveView::Thread { thread, .. } => {
-                                let thread = thread.read(cx).thread().clone();
-                                AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
-                            }
-                            ActiveView::ExternalAgentThread { .. }
-                            | ActiveView::TextThread { .. }
-                            | ActiveView::History
-                            | ActiveView::Configuration => {}
-                        }
-                    }
-                })
                 .register_action(|workspace, _: &Follow, window, cx| {
                     workspace.follow(CollaboratorId::Agent, window, cx);
                 })
-                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
-                    let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
-                        return;
-                    };
-                    workspace.focus_panel::<AgentPanel>(window, cx);
-                    panel.update(cx, |panel, cx| {
-                        if let Some(message_editor) = panel.active_message_editor() {
-                            message_editor.update(cx, |editor, cx| {
-                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);
-                            });
-                        }
-                    });
-                })
                 .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
                     if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
                         workspace.focus_panel::<AgentPanel>(window, cx);
@@ -230,12 +186,6 @@ pub fn init(cx: &mut App) {
 }
 
 enum ActiveView {
-    Thread {
-        thread: Entity<ActiveThread>,
-        change_title_editor: Entity<Editor>,
-        message_editor: Entity<MessageEditor>,
-        _subscriptions: Vec<gpui::Subscription>,
-    },
     ExternalAgentThread {
         thread_view: Entity<AcpThreadView>,
     },
@@ -305,100 +255,38 @@ impl From<ExternalAgent> for AgentType {
 impl ActiveView {
     pub fn which_font_size_used(&self) -> WhichFontSize {
         match self {
-            ActiveView::Thread { .. }
-            | ActiveView::ExternalAgentThread { .. }
-            | ActiveView::History => WhichFontSize::AgentFont,
+            ActiveView::ExternalAgentThread { .. } | ActiveView::History => {
+                WhichFontSize::AgentFont
+            }
             ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
             ActiveView::Configuration => WhichFontSize::None,
         }
     }
 
-    pub fn thread(
-        active_thread: Entity<ActiveThread>,
-        message_editor: Entity<MessageEditor>,
+    pub fn native_agent(
+        fs: Arc<dyn Fs>,
+        prompt_store: Option<Entity<PromptStore>>,
+        acp_history_store: Entity<agent2::HistoryStore>,
+        project: Entity<Project>,
+        workspace: WeakEntity<Workspace>,
         window: &mut Window,
-        cx: &mut Context<AgentPanel>,
+        cx: &mut App,
     ) -> Self {
-        let summary = active_thread.read(cx).summary(cx).or_default();
-
-        let editor = cx.new(|cx| {
-            let mut editor = Editor::single_line(window, cx);
-            editor.set_text(summary.clone(), window, cx);
-            editor
+        let thread_view = cx.new(|cx| {
+            crate::acp::AcpThreadView::new(
+                ExternalAgent::NativeAgent.server(fs, acp_history_store.clone()),
+                None,
+                None,
+                workspace,
+                project,
+                acp_history_store,
+                prompt_store,
+                window,
+                cx,
+            )
         });
 
-        let subscriptions = vec![
-            cx.subscribe(&message_editor, |this, _, event, cx| match event {
-                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
-                    cx.notify();
-                }
-                MessageEditorEvent::ScrollThreadToBottom => match &this.active_view {
-                    ActiveView::Thread { thread, .. } => {
-                        thread.update(cx, |thread, cx| {
-                            thread.scroll_to_bottom(cx);
-                        });
-                    }
-                    ActiveView::ExternalAgentThread { .. } => {}
-                    ActiveView::TextThread { .. }
-                    | ActiveView::History
-                    | ActiveView::Configuration => {}
-                },
-            }),
-            window.subscribe(&editor, cx, {
-                {
-                    let thread = active_thread.clone();
-                    move |editor, event, window, cx| match event {
-                        EditorEvent::BufferEdited => {
-                            let new_summary = editor.read(cx).text(cx);
-
-                            thread.update(cx, |thread, cx| {
-                                thread.thread().update(cx, |thread, cx| {
-                                    thread.set_summary(new_summary, cx);
-                                });
-                            })
-                        }
-                        EditorEvent::Blurred => {
-                            if editor.read(cx).text(cx).is_empty() {
-                                let summary = thread.read(cx).summary(cx).or_default();
-
-                                editor.update(cx, |editor, cx| {
-                                    editor.set_text(summary, window, cx);
-                                });
-                            }
-                        }
-                        _ => {}
-                    }
-                }
-            }),
-            cx.subscribe(&active_thread, |_, _, event, cx| match &event {
-                ActiveThreadEvent::EditingMessageTokenCountChanged => {
-                    cx.notify();
-                }
-            }),
-            cx.subscribe_in(&active_thread.read(cx).thread().clone(), window, {
-                let editor = editor.clone();
-                move |_, thread, event, window, cx| match event {
-                    ThreadEvent::SummaryGenerated => {
-                        let summary = thread.read(cx).summary().or_default();
-
-                        editor.update(cx, |editor, cx| {
-                            editor.set_text(summary, window, cx);
-                        })
-                    }
-                    ThreadEvent::MessageAdded(_) => {
-                        cx.notify();
-                    }
-                    _ => {}
-                }
-            }),
-        ];
-
-        Self::Thread {
-            change_title_editor: editor,
-            thread: active_thread,
-            message_editor,
-            _subscriptions: subscriptions,
-        }
+        Self::ExternalAgentThread { thread_view }
     }
 
     pub fn prompt_editor(
@@ -524,18 +412,14 @@ pub struct AgentPanel {
     thread_store: Entity<ThreadStore>,
     acp_history: Entity<AcpThreadHistory>,
     acp_history_store: Entity<agent2::HistoryStore>,
-    _default_model_subscription: Subscription,
     context_store: Entity<TextThreadStore>,
     prompt_store: Option<Entity<PromptStore>>,
     inline_assist_context_store: Entity<ContextStore>,
     configuration: Option<Entity<AgentConfiguration>>,
     configuration_subscription: Option<Subscription>,
-    local_timezone: UtcOffset,
     active_view: ActiveView,
     previous_view: Option<ActiveView>,
     history_store: Entity<HistoryStore>,
-    history: Entity<ThreadHistory>,
-    hovered_recent_history_item: Option<usize>,
     new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
     agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
     assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
@@ -655,45 +539,17 @@ impl AgentPanel {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> Self {
-        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
         let fs = workspace.app_state().fs.clone();
         let user_store = workspace.app_state().user_store.clone();
         let project = workspace.project();
         let language_registry = project.read(cx).languages().clone();
         let client = workspace.client().clone();
         let workspace = workspace.weak_handle();
-        let weak_self = cx.entity().downgrade();
 
-        let message_editor_context_store =
-            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
         let inline_assist_context_store =
             cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 
-        let thread_id = thread.read(cx).id().clone();
-
-        let history_store = cx.new(|cx| {
-            HistoryStore::new(
-                thread_store.clone(),
-                context_store.clone(),
-                [HistoryEntryId::Thread(thread_id)],
-                cx,
-            )
-        });
-
-        let message_editor = cx.new(|cx| {
-            MessageEditor::new(
-                fs.clone(),
-                workspace.clone(),
-                message_editor_context_store.clone(),
-                prompt_store.clone(),
-                thread_store.downgrade(),
-                context_store.downgrade(),
-                Some(history_store.downgrade()),
-                thread.clone(),
-                window,
-                cx,
-            )
-        });
+        let history_store = cx.new(|cx| HistoryStore::new(context_store.clone(), [], cx));
 
         let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
         let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx));
@@ -720,22 +576,17 @@ impl AgentPanel {
 
         cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 
-        let active_thread = cx.new(|cx| {
-            ActiveThread::new(
-                thread.clone(),
-                thread_store.clone(),
-                context_store.clone(),
-                message_editor_context_store.clone(),
-                language_registry.clone(),
+        let panel_type = AgentSettings::get_global(cx).default_view;
+        let active_view = match panel_type {
+            DefaultView::Thread => ActiveView::native_agent(
+                fs.clone(),
+                prompt_store.clone(),
+                acp_history_store.clone(),
+                project.clone(),
                 workspace.clone(),
                 window,
                 cx,
-            )
-        });
-
-        let panel_type = AgentSettings::get_global(cx).default_view;
-        let active_view = match panel_type {
-            DefaultView::Thread => ActiveView::thread(active_thread, message_editor, window, cx),
+            ),
             DefaultView::TextThread => {
                 let context =
                     context_store.update(cx, |context_store, cx| context_store.create(cx));
@@ -764,20 +615,14 @@ impl AgentPanel {
             }
         };
 
-        AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
-
-        let weak_panel = weak_self.clone();
+        let weak_panel = cx.entity().downgrade();
 
         window.defer(cx, move |window, cx| {
             let panel = weak_panel.clone();
             let assistant_navigation_menu =
                 ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
                     if let Some(panel) = panel.upgrade() {
-                        if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
-                            menu = Self::populate_recently_opened_menu_section_new(menu, panel, cx);
-                        } else {
-                            menu = Self::populate_recently_opened_menu_section_old(menu, panel, cx);
-                        }
+                        menu = Self::populate_recently_opened_menu_section(menu, panel, cx);
                     }
                     menu.action("View All", Box::new(OpenHistory))
                         .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
@@ -803,26 +648,6 @@ impl AgentPanel {
                 .ok();
         });
 
-        let _default_model_subscription =
-            cx.subscribe(
-                &LanguageModelRegistry::global(cx),
-                |this, _, event: &language_model::Event, cx| {
-                    if let language_model::Event::DefaultModelChanged = event {
-                        match &this.active_view {
-                            ActiveView::Thread { thread, .. } => {
-                                thread.read(cx).thread().clone().update(cx, |thread, cx| {
-                                    thread.get_or_init_configured_model(cx)
-                                });
-                            }
-                            ActiveView::ExternalAgentThread { .. }
-                            | ActiveView::TextThread { .. }
-                            | ActiveView::History
-                            | ActiveView::Configuration => {}
-                        }
-                    }
-                },
-            );
-
         let onboarding = cx.new(|cx| {
             AgentPanelOnboarding::new(
                 user_store.clone(),
@@ -842,20 +667,15 @@ impl AgentPanel {
             fs: fs.clone(),
             language_registry,
             thread_store: thread_store.clone(),
-            _default_model_subscription,
             context_store,
             prompt_store,
             configuration: None,
             configuration_subscription: None,
-            local_timezone: UtcOffset::from_whole_seconds(
-                chrono::Local::now().offset().local_minus_utc(),
-            )
-            .unwrap(),
+
             inline_assist_context_store,
             previous_view: None,
             history_store: history_store.clone(),
-            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
-            hovered_recent_history_item: None,
+
             new_thread_menu_handle: PopoverMenuHandle::default(),
             agent_panel_menu_handle: PopoverMenuHandle::default(),
             assistant_navigation_menu_handle: PopoverMenuHandle::default(),
@@ -886,10 +706,6 @@ impl AgentPanel {
         }
     }
 
-    pub(crate) fn local_timezone(&self) -> UtcOffset {
-        self.local_timezone
-    }
-
     pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
         &self.prompt_store
     }
@@ -906,118 +722,15 @@ impl AgentPanel {
         &self.context_store
     }
 
-    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
-        match &self.active_view {
-            ActiveView::Thread { thread, .. } => {
-                thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
-            }
-            ActiveView::ExternalAgentThread { .. }
-            | ActiveView::TextThread { .. }
-            | ActiveView::History
-            | ActiveView::Configuration => {}
-        }
-    }
-
-    fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
-        match &self.active_view {
-            ActiveView::Thread { message_editor, .. } => Some(message_editor),
-            ActiveView::ExternalAgentThread { .. }
-            | ActiveView::TextThread { .. }
-            | ActiveView::History
-            | ActiveView::Configuration => None,
-        }
-    }
-
     fn active_thread_view(&self) -> Option<&Entity<AcpThreadView>> {
         match &self.active_view {
             ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),
-            ActiveView::Thread { .. }
-            | ActiveView::TextThread { .. }
-            | ActiveView::History
-            | ActiveView::Configuration => None,
+            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => None,
         }
     }
 
-    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
-        if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
-            return self.new_agent_thread(AgentType::NativeAgent, window, cx);
-        }
-        // Preserve chat box text when using creating new thread
-        let preserved_text = self
-            .active_message_editor()
-            .map(|editor| editor.read(cx).get_text(cx).trim().to_string());
-
-        let thread = self
-            .thread_store
-            .update(cx, |this, cx| this.create_thread(cx));
-
-        let context_store = cx.new(|_cx| {
-            ContextStore::new(
-                self.project.downgrade(),
-                Some(self.thread_store.downgrade()),
-            )
-        });
-
-        if let Some(other_thread_id) = action.from_thread_id.clone() {
-            let other_thread_task = self.thread_store.update(cx, |this, cx| {
-                this.open_thread(&other_thread_id, window, cx)
-            });
-
-            cx.spawn({
-                let context_store = context_store.clone();
-
-                async move |_panel, cx| {
-                    let other_thread = other_thread_task.await?;
-
-                    context_store.update(cx, |this, cx| {
-                        this.add_thread(other_thread, false, cx);
-                    })?;
-                    anyhow::Ok(())
-                }
-            })
-            .detach_and_log_err(cx);
-        }
-
-        let active_thread = cx.new(|cx| {
-            ActiveThread::new(
-                thread.clone(),
-                self.thread_store.clone(),
-                self.context_store.clone(),
-                context_store.clone(),
-                self.language_registry.clone(),
-                self.workspace.clone(),
-                window,
-                cx,
-            )
-        });
-
-        let message_editor = cx.new(|cx| {
-            MessageEditor::new(
-                self.fs.clone(),
-                self.workspace.clone(),
-                context_store.clone(),
-                self.prompt_store.clone(),
-                self.thread_store.downgrade(),
-                self.context_store.downgrade(),
-                Some(self.history_store.downgrade()),
-                thread.clone(),
-                window,
-                cx,
-            )
-        });
-
-        if let Some(text) = preserved_text {
-            message_editor.update(cx, |editor, cx| {
-                editor.set_text(text, window, cx);
-            });
-        }
-
-        message_editor.focus_handle(cx).focus(window);
-
-        let thread_view = ActiveView::thread(active_thread, message_editor, window, cx);
-        self.set_active_view(thread_view, window, cx);
-
-        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
+    fn new_thread(&mut self, _action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
+        self.new_agent_thread(AgentType::NativeAgent, window, cx);
     }
 
     fn new_native_agent_thread_from_summary(
@@ -1153,21 +866,6 @@ impl AgentPanel {
             let server = ext_agent.server(fs, history);
 
             this.update_in(cx, |this, window, cx| {
-                match ext_agent {
-                    crate::ExternalAgent::Gemini
-                    | crate::ExternalAgent::NativeAgent
-                    | crate::ExternalAgent::Custom { .. } => {
-                        if !cx.has_flag::<GeminiAndNativeFeatureFlag>() {
-                            return;
-                        }
-                    }
-                    crate::ExternalAgent::ClaudeCode => {
-                        if !cx.has_flag::<ClaudeCodeFeatureFlag>() {
-                            return;
-                        }
-                    }
-                }
-
                 let selected_agent = ext_agent.into();
                 if this.selected_agent != selected_agent {
                     this.selected_agent = selected_agent;
@@ -1289,72 +987,6 @@ impl AgentPanel {
         );
     }
 
-    pub(crate) fn open_thread_by_id(
-        &mut self,
-        thread_id: &ThreadId,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Task<Result<()>> {
-        let open_thread_task = self
-            .thread_store
-            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
-        cx.spawn_in(window, async move |this, cx| {
-            let thread = open_thread_task.await?;
-            this.update_in(cx, |this, window, cx| {
-                this.open_thread(thread, window, cx);
-                anyhow::Ok(())
-            })??;
-            Ok(())
-        })
-    }
-
-    pub(crate) fn open_thread(
-        &mut self,
-        thread: Entity<Thread>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let context_store = cx.new(|_cx| {
-            ContextStore::new(
-                self.project.downgrade(),
-                Some(self.thread_store.downgrade()),
-            )
-        });
-
-        let active_thread = cx.new(|cx| {
-            ActiveThread::new(
-                thread.clone(),
-                self.thread_store.clone(),
-                self.context_store.clone(),
-                context_store.clone(),
-                self.language_registry.clone(),
-                self.workspace.clone(),
-                window,
-                cx,
-            )
-        });
-
-        let message_editor = cx.new(|cx| {
-            MessageEditor::new(
-                self.fs.clone(),
-                self.workspace.clone(),
-                context_store,
-                self.prompt_store.clone(),
-                self.thread_store.downgrade(),
-                self.context_store.downgrade(),
-                Some(self.history_store.downgrade()),
-                thread.clone(),
-                window,
-                cx,
-            )
-        });
-        message_editor.focus_handle(cx).focus(window);
-
-        let thread_view = ActiveView::thread(active_thread, message_editor, window, cx);
-        self.set_active_view(thread_view, window, cx);
-        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
-    }
-
     pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
         match self.active_view {
             ActiveView::Configuration | ActiveView::History => {
@@ -1362,9 +994,6 @@ impl AgentPanel {
                     self.active_view = previous_view;
 
                     match &self.active_view {
-                        ActiveView::Thread { message_editor, .. } => {
-                            message_editor.focus_handle(cx).focus(window);
-                        }
                         ActiveView::ExternalAgentThread { thread_view } => {
                             thread_view.focus_handle(cx).focus(window);
                         }
@@ -1479,33 +1108,6 @@ impl AgentPanel {
         }
     }
 
-    pub fn open_agent_diff(
-        &mut self,
-        _: &OpenAgentDiff,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match &self.active_view {
-            ActiveView::Thread { thread, .. } => {
-                let thread = thread.read(cx).thread().clone();
-                self.workspace
-                    .update(cx, |workspace, cx| {
-                        AgentDiffPane::deploy_in_workspace(
-                            AgentDiffThread::Native(thread),
-                            workspace,
-                            window,
-                            cx,
-                        )
-                    })
-                    .log_err();
-            }
-            ActiveView::ExternalAgentThread { .. }
-            | ActiveView::TextThread { .. }
-            | ActiveView::History
-            | ActiveView::Configuration => {}
-        }
-    }
-
     pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
         let agent_server_store = self.project.read(cx).agent_server_store().clone();
         let context_server_store = self.project.read(cx).context_server_store();
@@ -1548,15 +1150,6 @@ impl AgentPanel {
         };
 
         match &self.active_view {
-            ActiveView::Thread { thread, .. } => {
-                active_thread::open_active_thread_as_markdown(
-                    thread.read(cx).thread().clone(),
-                    workspace,
-                    window,
-                    cx,
-                )
-                .detach_and_log_err(cx);
-            }
             ActiveView::ExternalAgentThread { thread_view } => {
                 thread_view
                     .update(cx, |thread_view, cx| {
@@ -1590,29 +1183,18 @@ impl AgentPanel {
                 }
 
                 self.new_thread(&NewThread::default(), window, cx);
-                if let Some((thread, model)) =
-                    self.active_thread(cx).zip(provider.default_model(cx))
+                if let Some((thread, model)) = self
+                    .active_native_agent_thread(cx)
+                    .zip(provider.default_model(cx))
                 {
                     thread.update(cx, |thread, cx| {
-                        thread.set_configured_model(
-                            Some(ConfiguredModel {
-                                provider: provider.clone(),
-                                model,
-                            }),
-                            cx,
-                        );
+                        thread.set_model(model, cx);
                     });
                 }
             }
         }
     }
 
-    pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
-        match &self.active_view {
-            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
-            _ => None,
-        }
-    }
     pub(crate) fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
         match &self.active_view {
             ActiveView::ExternalAgentThread { thread_view, .. } => {
@@ -1622,90 +1204,30 @@ impl AgentPanel {
         }
     }
 
-    pub(crate) fn delete_thread(
-        &mut self,
-        thread_id: &ThreadId,
-        cx: &mut Context<Self>,
-    ) -> Task<Result<()>> {
-        self.thread_store
-            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
-    }
-
-    fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let ActiveView::Thread { thread, .. } = &self.active_view else {
-            return;
-        };
-
-        let thread_state = thread.read(cx).thread().read(cx);
-        if !thread_state.tool_use_limit_reached() {
-            return;
+    pub(crate) fn active_native_agent_thread(&self, cx: &App) -> Option<Entity<agent2::Thread>> {
+        match &self.active_view {
+            ActiveView::ExternalAgentThread { thread_view, .. } => {
+                thread_view.read(cx).as_native_thread(cx)
+            }
+            _ => None,
         }
+    }
 
-        let model = thread_state.configured_model().map(|cm| cm.model);
-        if let Some(model) = model {
-            thread.update(cx, |active_thread, cx| {
-                active_thread.thread().update(cx, |thread, cx| {
-                    thread.insert_invisible_continue_message(cx);
-                    thread.advance_prompt_id();
-                    thread.send_to_model(
-                        model,
-                        CompletionIntent::UserPrompt,
-                        Some(window.window_handle()),
-                        cx,
-                    );
-                });
-            });
-        } else {
-            log::warn!("No configured model available for continuation");
+    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
+        match &self.active_view {
+            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
+            _ => None,
         }
     }
 
-    fn toggle_burn_mode(
+    fn set_active_view(
         &mut self,
-        _: &ToggleBurnMode,
-        _window: &mut Window,
+        new_view: ActiveView,
+        window: &mut Window,
         cx: &mut Context<Self>,
     ) {
-        let ActiveView::Thread { thread, .. } = &self.active_view else {
-            return;
-        };
-
-        thread.update(cx, |active_thread, cx| {
-            active_thread.thread().update(cx, |thread, _cx| {
-                let current_mode = thread.completion_mode();
-
-                thread.set_completion_mode(match current_mode {
-                    CompletionMode::Burn => CompletionMode::Normal,
-                    CompletionMode::Normal => CompletionMode::Burn,
-                });
-            });
-        });
-    }
-
-    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
-        match &self.active_view {
-            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
-            _ => None,
-        }
-    }
-
-    pub(crate) fn delete_context(
-        &mut self,
-        path: Arc<Path>,
-        cx: &mut Context<Self>,
-    ) -> Task<Result<()>> {
-        self.context_store
-            .update(cx, |this, cx| this.delete_local_context(path, cx))
-    }
-
-    fn set_active_view(
-        &mut self,
-        new_view: ActiveView,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let current_is_history = matches!(self.active_view, ActiveView::History);
-        let new_is_history = matches!(new_view, ActiveView::History);
+        let current_is_history = matches!(self.active_view, ActiveView::History);
+        let new_is_history = matches!(new_view, ActiveView::History);
 
         let current_is_config = matches!(self.active_view, ActiveView::Configuration);
         let new_is_config = matches!(new_view, ActiveView::Configuration);
@@ -1713,21 +1235,7 @@ impl AgentPanel {
         let current_is_special = current_is_history || current_is_config;
         let new_is_special = new_is_history || new_is_config;
 
-        if let ActiveView::Thread { thread, .. } = &self.active_view {
-            let thread = thread.read(cx);
-            if thread.is_empty() {
-                let id = thread.thread().read(cx).id().clone();
-                self.history_store.update(cx, |store, cx| {
-                    store.remove_recently_opened_thread(id, cx);
-                });
-            }
-        }
-
         match &new_view {
-            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
-                let id = thread.read(cx).thread().read(cx).id().clone();
-                store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
-            }),
             ActiveView::TextThread { context_editor, .. } => {
                 self.history_store.update(cx, |store, cx| {
                     if let Some(path) = context_editor.read(cx).context().read(cx).path() {
@@ -1761,71 +1269,7 @@ impl AgentPanel {
         self.focus_handle(cx).focus(window);
     }
 
-    fn populate_recently_opened_menu_section_old(
-        mut menu: ContextMenu,
-        panel: Entity<Self>,
-        cx: &mut Context<ContextMenu>,
-    ) -> ContextMenu {
-        let entries = panel
-            .read(cx)
-            .history_store
-            .read(cx)
-            .recently_opened_entries(cx);
-
-        if entries.is_empty() {
-            return menu;
-        }
-
-        menu = menu.header("Recently Opened");
-
-        for entry in entries {
-            let title = entry.title().clone();
-            let id = entry.id();
-
-            menu = menu.entry_with_end_slot_on_hover(
-                title,
-                None,
-                {
-                    let panel = panel.downgrade();
-                    let id = id.clone();
-                    move |window, cx| {
-                        let id = id.clone();
-                        panel
-                            .update(cx, move |this, cx| match id {
-                                HistoryEntryId::Thread(id) => this
-                                    .open_thread_by_id(&id, window, cx)
-                                    .detach_and_log_err(cx),
-                                HistoryEntryId::Context(path) => this
-                                    .open_saved_prompt_editor(path, window, cx)
-                                    .detach_and_log_err(cx),
-                            })
-                            .ok();
-                    }
-                },
-                IconName::Close,
-                "Close Entry".into(),
-                {
-                    let panel = panel.downgrade();
-                    let id = id.clone();
-                    move |_window, cx| {
-                        panel
-                            .update(cx, |this, cx| {
-                                this.history_store.update(cx, |history_store, cx| {
-                                    history_store.remove_recently_opened_entry(&id, cx);
-                                });
-                            })
-                            .ok();
-                    }
-                },
-            );
-        }
-
-        menu = menu.separator();
-
-        menu
-    }
-
-    fn populate_recently_opened_menu_section_new(
+    fn populate_recently_opened_menu_section(
         mut menu: ContextMenu,
         panel: Entity<Self>,
         cx: &mut Context<ContextMenu>,
@@ -1965,15 +1409,8 @@ impl AgentPanel {
 impl Focusable for AgentPanel {
     fn focus_handle(&self, cx: &App) -> FocusHandle {
         match &self.active_view {
-            ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
             ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
-            ActiveView::History => {
-                if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>() {
-                    self.acp_history.focus_handle(cx)
-                } else {
-                    self.history.focus_handle(cx)
-                }
-            }
+            ActiveView::History => self.acp_history.focus_handle(cx),
             ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
             ActiveView::Configuration => {
                 if let Some(configuration) = self.configuration.as_ref() {
@@ -2080,58 +1517,6 @@ impl AgentPanel {
         const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
 
         let content = match &self.active_view {
-            ActiveView::Thread {
-                thread: active_thread,
-                change_title_editor,
-                ..
-            } => {
-                let state = {
-                    let active_thread = active_thread.read(cx);
-                    if active_thread.is_empty() {
-                        &ThreadSummary::Pending
-                    } else {
-                        active_thread.summary(cx)
-                    }
-                };
-
-                match state {
-                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT)
-                        .truncate()
-                        .color(Color::Muted)
-                        .into_any_element(),
-                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
-                        .truncate()
-                        .color(Color::Muted)
-                        .into_any_element(),
-                    ThreadSummary::Ready(_) => div()
-                        .w_full()
-                        .child(change_title_editor.clone())
-                        .into_any_element(),
-                    ThreadSummary::Error => h_flex()
-                        .w_full()
-                        .child(change_title_editor.clone())
-                        .child(
-                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
-                                .icon_size(IconSize::Small)
-                                .on_click({
-                                    let active_thread = active_thread.clone();
-                                    move |_, _window, cx| {
-                                        active_thread.update(cx, |thread, cx| {
-                                            thread.regenerate_summary(cx);
-                                        });
-                                    }
-                                })
-                                .tooltip(move |_window, cx| {
-                                    cx.new(|_| {
-                                        Tooltip::new("Failed to generate title")
-                                            .meta("Click to try again")
-                                    })
-                                    .into()
-                                }),
-                        )
-                        .into_any_element(),
-                }
-            }
             ActiveView::ExternalAgentThread { thread_view } => {
                 if let Some(title_editor) = thread_view.read(cx).title_editor() {
                     div()

crates/agent_ui/src/agent_ui.rs 🔗

@@ -1,5 +1,4 @@
 mod acp;
-mod active_thread;
 mod agent_configuration;
 mod agent_diff;
 mod agent_model_selector;
@@ -8,7 +7,6 @@ mod buffer_codegen;
 mod context_picker;
 mod context_server_configuration;
 mod context_strip;
-mod debug;
 mod inline_assistant;
 mod inline_prompt_editor;
 mod language_model_selector;
@@ -20,14 +18,12 @@ mod slash_command_settings;
 mod terminal_codegen;
 mod terminal_inline_assistant;
 mod text_thread_editor;
-mod thread_history;
-mod tool_compatibility;
 mod ui;
 
 use std::rc::Rc;
 use std::sync::Arc;
 
-use agent::{Thread, ThreadId};
+use agent::ThreadId;
 use agent_settings::{AgentProfileId, AgentSettings, LanguageModelSelection};
 use assistant_slash_command::SlashCommandRegistry;
 use client::Client;
@@ -47,14 +43,12 @@ use serde::{Deserialize, Serialize};
 use settings::{Settings as _, SettingsStore};
 use std::any::TypeId;
 
-pub use crate::active_thread::ActiveThread;
 use crate::agent_configuration::{ConfigureContextServerModal, ManageProfilesModal};
 pub use crate::agent_panel::{AgentPanel, ConcreteAssistantPanelDelegate};
 pub use crate::inline_assistant::InlineAssistant;
 use crate::slash_command_settings::SlashCommandSettings;
 pub use agent_diff::{AgentDiffPane, AgentDiffToolbar};
 pub use text_thread_editor::{AgentPanelDelegate, TextThreadEditor};
-pub use ui::preview::{all_agent_previews, get_agent_preview};
 use zed_actions;
 
 actions!(
@@ -235,14 +229,12 @@ impl ManageProfiles {
 
 #[derive(Clone)]
 pub(crate) enum ModelUsageContext {
-    Thread(Entity<Thread>),
     InlineAssistant,
 }
 
 impl ModelUsageContext {
     pub fn configured_model(&self, cx: &App) -> Option<ConfiguredModel> {
         match self {
-            Self::Thread(thread) => thread.read(cx).configured_model(),
             Self::InlineAssistant => {
                 LanguageModelRegistry::read_global(cx).inline_assistant_model()
             }

crates/agent_ui/src/context_picker.rs 🔗

@@ -6,7 +6,7 @@ pub(crate) mod symbol_context_picker;
 pub(crate) mod thread_context_picker;
 
 use std::ops::Range;
-use std::path::{Path, PathBuf};
+use std::path::PathBuf;
 use std::sync::Arc;
 
 use anyhow::{Result, anyhow};
@@ -23,9 +23,8 @@ use gpui::{
 };
 use language::Buffer;
 use multi_buffer::MultiBufferRow;
-use paths::contexts_dir;
-use project::{Entry, ProjectPath};
-use prompt_store::{PromptStore, UserPromptId};
+use project::ProjectPath;
+use prompt_store::PromptStore;
 use rules_context_picker::{RulesContextEntry, RulesContextPicker};
 use symbol_context_picker::SymbolContextPicker;
 use thread_context_picker::{
@@ -34,10 +33,8 @@ use thread_context_picker::{
 use ui::{
     ButtonLike, ContextMenu, ContextMenuEntry, ContextMenuItem, Disclosure, TintColor, prelude::*,
 };
-use uuid::Uuid;
 use workspace::{Workspace, notifications::NotifyResultExt};
 
-use crate::AgentPanel;
 use agent::{
     ThreadId,
     context::RULES_ICON,
@@ -664,7 +661,7 @@ pub(crate) fn recent_context_picker_entries(
     text_thread_store: Option<WeakEntity<TextThreadStore>>,
     workspace: Entity<Workspace>,
     exclude_paths: &HashSet<PathBuf>,
-    exclude_threads: &HashSet<ThreadId>,
+    _exclude_threads: &HashSet<ThreadId>,
     cx: &App,
 ) -> Vec<RecentEntry> {
     let mut recent = Vec::with_capacity(6);
@@ -690,19 +687,13 @@ pub(crate) fn recent_context_picker_entries(
             }),
     );
 
-    let active_thread_id = workspace
-        .panel::<AgentPanel>(cx)
-        .and_then(|panel| Some(panel.read(cx).active_thread(cx)?.read(cx).id()));
-
     if let Some((thread_store, text_thread_store)) = thread_store
         .and_then(|store| store.upgrade())
         .zip(text_thread_store.and_then(|store| store.upgrade()))
     {
         let mut threads = unordered_thread_entries(thread_store, text_thread_store, cx)
             .filter(|(_, thread)| match thread {
-                ThreadContextEntry::Thread { id, .. } => {
-                    Some(id) != active_thread_id && !exclude_threads.contains(id)
-                }
+                ThreadContextEntry::Thread { .. } => false,
                 ThreadContextEntry::Context { .. } => true,
             })
             .collect::<Vec<_>>();
@@ -874,15 +865,7 @@ fn fold_toggle(
     }
 }
 
-pub enum MentionLink {
-    File(ProjectPath, Entry),
-    Symbol(ProjectPath, String),
-    Selection(ProjectPath, Range<usize>),
-    Fetch(String),
-    Thread(ThreadId),
-    TextThread(Arc<Path>),
-    Rule(UserPromptId),
-}
+pub struct MentionLink;
 
 impl MentionLink {
     const FILE: &str = "@file";
@@ -894,17 +877,6 @@ impl MentionLink {
 
     const TEXT_THREAD_URL_PREFIX: &str = "text-thread://";
 
-    const SEPARATOR: &str = ":";
-
-    pub fn is_valid(url: &str) -> bool {
-        url.starts_with(Self::FILE)
-            || url.starts_with(Self::SYMBOL)
-            || url.starts_with(Self::FETCH)
-            || url.starts_with(Self::SELECTION)
-            || url.starts_with(Self::THREAD)
-            || url.starts_with(Self::RULE)
-    }
-
     pub fn for_file(file_name: &str, full_path: &str) -> String {
         format!("[@{}]({}:{})", file_name, Self::FILE, full_path)
     }
@@ -958,75 +930,4 @@ impl MentionLink {
     pub fn for_rule(rule: &RulesContextEntry) -> String {
         format!("[@{}]({}:{})", rule.title, Self::RULE, rule.prompt_id.0)
     }
-
-    pub fn try_parse(link: &str, workspace: &Entity<Workspace>, cx: &App) -> Option<Self> {
-        fn extract_project_path_from_link(
-            path: &str,
-            workspace: &Entity<Workspace>,
-            cx: &App,
-        ) -> Option<ProjectPath> {
-            let path = PathBuf::from(path);
-            let worktree_name = path.iter().next()?;
-            let path: PathBuf = path.iter().skip(1).collect();
-            let worktree_id = workspace
-                .read(cx)
-                .visible_worktrees(cx)
-                .find(|worktree| worktree.read(cx).root_name() == worktree_name)
-                .map(|worktree| worktree.read(cx).id())?;
-            Some(ProjectPath {
-                worktree_id,
-                path: path.into(),
-            })
-        }
-
-        let (prefix, argument) = link.split_once(Self::SEPARATOR)?;
-        match prefix {
-            Self::FILE => {
-                let project_path = extract_project_path_from_link(argument, workspace, cx)?;
-                let entry = workspace
-                    .read(cx)
-                    .project()
-                    .read(cx)
-                    .entry_for_path(&project_path, cx)?
-                    .clone();
-                Some(MentionLink::File(project_path, entry))
-            }
-            Self::SYMBOL => {
-                let (path, symbol) = argument.split_once(Self::SEPARATOR)?;
-                let project_path = extract_project_path_from_link(path, workspace, cx)?;
-                Some(MentionLink::Symbol(project_path, symbol.to_string()))
-            }
-            Self::SELECTION => {
-                let (path, line_args) = argument.split_once(Self::SEPARATOR)?;
-                let project_path = extract_project_path_from_link(path, workspace, cx)?;
-
-                let line_range = {
-                    let (start, end) = line_args
-                        .trim_start_matches('(')
-                        .trim_end_matches(')')
-                        .split_once('-')?;
-                    start.parse::<usize>().ok()?..end.parse::<usize>().ok()?
-                };
-
-                Some(MentionLink::Selection(project_path, line_range))
-            }
-            Self::THREAD => {
-                if let Some(encoded_filename) = argument.strip_prefix(Self::TEXT_THREAD_URL_PREFIX)
-                {
-                    let filename = urlencoding::decode(encoded_filename).ok()?;
-                    let path = contexts_dir().join(filename.as_ref()).into();
-                    Some(MentionLink::TextThread(path))
-                } else {
-                    let thread_id = ThreadId::from(argument);
-                    Some(MentionLink::Thread(thread_id))
-                }
-            }
-            Self::FETCH => Some(MentionLink::Fetch(argument.to_string())),
-            Self::RULE => {
-                let prompt_id = UserPromptId(Uuid::try_parse(argument).ok()?);
-                Some(MentionLink::Rule(prompt_id))
-            }
-            _ => None,
-        }
-    }
 }

crates/agent_ui/src/context_strip.rs 🔗

@@ -12,16 +12,19 @@ use agent::{
 };
 use collections::HashSet;
 use editor::Editor;
-use file_icons::FileIcons;
 use gpui::{
     App, Bounds, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
-    Subscription, WeakEntity,
+    Subscription, Task, WeakEntity,
 };
 use itertools::Itertools;
 use project::ProjectItem;
-use std::{path::Path, rc::Rc};
+use rope::Point;
+use std::rc::Rc;
+use text::ToPoint as _;
 use ui::{PopoverMenu, PopoverMenuHandle, Tooltip, prelude::*};
+use util::ResultExt as _;
 use workspace::Workspace;
+use zed_actions::assistant::OpenRulesLibrary;
 
 pub struct ContextStrip {
     context_store: Entity<ContextStore>,
@@ -121,38 +124,10 @@ impl ContextStrip {
 
     fn suggested_context(&self, cx: &App) -> Option<SuggestedContext> {
         match self.suggest_context_kind {
-            SuggestContextKind::File => self.suggested_file(cx),
             SuggestContextKind::Thread => self.suggested_thread(cx),
         }
     }
 
-    fn suggested_file(&self, cx: &App) -> Option<SuggestedContext> {
-        let workspace = self.workspace.upgrade()?;
-        let active_item = workspace.read(cx).active_item(cx)?;
-
-        let editor = active_item.to_any().downcast::<Editor>().ok()?.read(cx);
-        let active_buffer_entity = editor.buffer().read(cx).as_singleton()?;
-        let active_buffer = active_buffer_entity.read(cx);
-        let project_path = active_buffer.project_path(cx)?;
-
-        if self
-            .context_store
-            .read(cx)
-            .file_path_included(&project_path, cx)
-            .is_some()
-        {
-            return None;
-        }
-
-        let file_name = active_buffer.file()?.file_name(cx);
-        let icon_path = FileIcons::get_icon(Path::new(&file_name), cx);
-        Some(SuggestedContext::File {
-            name: file_name.to_string_lossy().into_owned().into(),
-            buffer: active_buffer_entity.downgrade(),
-            icon_path,
-        })
-    }
-
     fn suggested_thread(&self, cx: &App) -> Option<SuggestedContext> {
         if !self.context_picker.read(cx).allow_threads() {
             return None;
@@ -161,24 +136,7 @@ impl ContextStrip {
         let workspace = self.workspace.upgrade()?;
         let panel = workspace.read(cx).panel::<AgentPanel>(cx)?.read(cx);
 
-        if let Some(active_thread) = panel.active_thread(cx) {
-            let weak_active_thread = active_thread.downgrade();
-
-            let active_thread = active_thread.read(cx);
-
-            if self
-                .context_store
-                .read(cx)
-                .includes_thread(active_thread.id())
-            {
-                return None;
-            }
-
-            Some(SuggestedContext::Thread {
-                name: active_thread.summary().or_default(),
-                thread: weak_active_thread,
-            })
-        } else if let Some(active_context_editor) = panel.active_context_editor() {
+        if let Some(active_context_editor) = panel.active_context_editor() {
             let context = active_context_editor.read(cx).context();
             let weak_context = context.downgrade();
             let context = context.read(cx);
@@ -328,7 +286,75 @@ impl ContextStrip {
             return;
         };
 
-        crate::active_thread::open_context(context, workspace, window, cx);
+        match context {
+            AgentContextHandle::File(file_context) => {
+                if let Some(project_path) = file_context.project_path(cx) {
+                    workspace.update(cx, |workspace, cx| {
+                        workspace
+                            .open_path(project_path, None, true, window, cx)
+                            .detach_and_log_err(cx);
+                    });
+                }
+            }
+
+            AgentContextHandle::Directory(directory_context) => {
+                let entry_id = directory_context.entry_id;
+                workspace.update(cx, |workspace, cx| {
+                    workspace.project().update(cx, |_project, cx| {
+                        cx.emit(project::Event::RevealInProjectPanel(entry_id));
+                    })
+                })
+            }
+
+            AgentContextHandle::Symbol(symbol_context) => {
+                let buffer = symbol_context.buffer.read(cx);
+                if let Some(project_path) = buffer.project_path(cx) {
+                    let snapshot = buffer.snapshot();
+                    let target_position = symbol_context.range.start.to_point(&snapshot);
+                    open_editor_at_position(project_path, target_position, &workspace, window, cx)
+                        .detach();
+                }
+            }
+
+            AgentContextHandle::Selection(selection_context) => {
+                let buffer = selection_context.buffer.read(cx);
+                if let Some(project_path) = buffer.project_path(cx) {
+                    let snapshot = buffer.snapshot();
+                    let target_position = selection_context.range.start.to_point(&snapshot);
+
+                    open_editor_at_position(project_path, target_position, &workspace, window, cx)
+                        .detach();
+                }
+            }
+
+            AgentContextHandle::FetchedUrl(fetched_url_context) => {
+                cx.open_url(&fetched_url_context.url);
+            }
+
+            AgentContextHandle::Thread(_thread_context) => {}
+
+            AgentContextHandle::TextThread(text_thread_context) => {
+                workspace.update(cx, |workspace, cx| {
+                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
+                        let context = text_thread_context.context.clone();
+                        window.defer(cx, move |window, cx| {
+                            panel.update(cx, |panel, cx| {
+                                panel.open_prompt_editor(context, window, cx)
+                            });
+                        });
+                    }
+                })
+            }
+
+            AgentContextHandle::Rules(rules_context) => window.dispatch_action(
+                Box::new(OpenRulesLibrary {
+                    prompt_to_select: Some(rules_context.prompt_id.0),
+                }),
+                cx,
+            ),
+
+            AgentContextHandle::Image(_) => {}
+        }
     }
 
     fn remove_focused_context(
@@ -569,6 +595,31 @@ pub enum ContextStripEvent {
 impl EventEmitter<ContextStripEvent> for ContextStrip {}
 
 pub enum SuggestContextKind {
-    File,
     Thread,
 }
+
+fn open_editor_at_position(
+    project_path: project::ProjectPath,
+    target_position: Point,
+    workspace: &Entity<Workspace>,
+    window: &mut Window,
+    cx: &mut App,
+) -> Task<()> {
+    let open_task = workspace.update(cx, |workspace, cx| {
+        workspace.open_path(project_path, None, true, window, cx)
+    });
+    window.spawn(cx, async move |cx| {
+        if let Some(active_editor) = open_task
+            .await
+            .log_err()
+            .and_then(|item| item.downcast::<Editor>())
+        {
+            active_editor
+                .downgrade()
+                .update_in(cx, |editor, window, cx| {
+                    editor.go_to_singleton_buffer_point(target_position, window, cx);
+                })
+                .log_err();
+        }
+    })
+}

crates/agent_ui/src/debug.rs 🔗

@@ -1,124 +0,0 @@
-#![allow(unused, dead_code)]
-
-use client::{ModelRequestUsage, RequestUsage};
-use cloud_llm_client::{Plan, PlanV1, UsageLimit};
-use gpui::Global;
-use std::ops::{Deref, DerefMut};
-use ui::prelude::*;
-
-/// Debug only: Used for testing various account states
-///
-/// Use this by initializing it with
-/// `cx.set_global(DebugAccountState::default());` somewhere
-///
-/// Then call `cx.debug_account()` to get access
-#[derive(Clone, Debug)]
-pub struct DebugAccountState {
-    pub enabled: bool,
-    pub trial_expired: bool,
-    pub plan: Plan,
-    pub custom_prompt_usage: ModelRequestUsage,
-    pub usage_based_billing_enabled: bool,
-    pub monthly_spending_cap: i32,
-    pub custom_edit_prediction_usage: UsageLimit,
-}
-
-impl DebugAccountState {
-    pub fn enabled(&self) -> bool {
-        self.enabled
-    }
-
-    pub fn set_enabled(&mut self, enabled: bool) -> &mut Self {
-        self.enabled = enabled;
-        self
-    }
-
-    pub fn set_trial_expired(&mut self, trial_expired: bool) -> &mut Self {
-        self.trial_expired = trial_expired;
-        self
-    }
-
-    pub fn set_plan(&mut self, plan: Plan) -> &mut Self {
-        self.plan = plan;
-        self
-    }
-
-    pub fn set_custom_prompt_usage(&mut self, custom_prompt_usage: ModelRequestUsage) -> &mut Self {
-        self.custom_prompt_usage = custom_prompt_usage;
-        self
-    }
-
-    pub fn set_usage_based_billing_enabled(
-        &mut self,
-        usage_based_billing_enabled: bool,
-    ) -> &mut Self {
-        self.usage_based_billing_enabled = usage_based_billing_enabled;
-        self
-    }
-
-    pub fn set_monthly_spending_cap(&mut self, monthly_spending_cap: i32) -> &mut Self {
-        self.monthly_spending_cap = monthly_spending_cap;
-        self
-    }
-
-    pub fn set_custom_edit_prediction_usage(
-        &mut self,
-        custom_edit_prediction_usage: UsageLimit,
-    ) -> &mut Self {
-        self.custom_edit_prediction_usage = custom_edit_prediction_usage;
-        self
-    }
-}
-
-impl Default for DebugAccountState {
-    fn default() -> Self {
-        Self {
-            enabled: false,
-            trial_expired: false,
-            plan: Plan::V1(PlanV1::ZedFree),
-            custom_prompt_usage: ModelRequestUsage(RequestUsage {
-                limit: UsageLimit::Unlimited,
-                amount: 0,
-            }),
-            usage_based_billing_enabled: false,
-            // $50.00
-            monthly_spending_cap: 5000,
-            custom_edit_prediction_usage: UsageLimit::Unlimited,
-        }
-    }
-}
-
-impl DebugAccountState {
-    pub fn get_global(cx: &App) -> &Self {
-        &cx.global::<GlobalDebugAccountState>().0
-    }
-}
-
-#[derive(Clone, Debug)]
-pub struct GlobalDebugAccountState(pub DebugAccountState);
-
-impl Global for GlobalDebugAccountState {}
-
-impl Deref for GlobalDebugAccountState {
-    type Target = DebugAccountState;
-
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl DerefMut for GlobalDebugAccountState {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-pub trait DebugAccount {
-    fn debug_account(&self) -> &DebugAccountState;
-}
-
-impl DebugAccount for App {
-    fn debug_account(&self) -> &DebugAccountState {
-        &self.global::<GlobalDebugAccountState>().0
-    }
-}

crates/agent_ui/src/inline_prompt_editor.rs 🔗

@@ -11,8 +11,8 @@ use editor::{
 };
 use fs::Fs;
 use gpui::{
-    AnyElement, App, Context, CursorStyle, Entity, EventEmitter, FocusHandle, Focusable,
-    Subscription, TextStyle, WeakEntity, Window,
+    AnyElement, App, ClipboardEntry, Context, CursorStyle, Entity, EventEmitter, FocusHandle,
+    Focusable, Subscription, TextStyle, WeakEntity, Window,
 };
 use language_model::{LanguageModel, LanguageModelRegistry};
 use parking_lot::Mutex;
@@ -272,7 +272,31 @@ impl<T: 'static> PromptEditor<T> {
     }
 
     fn paste(&mut self, _: &Paste, _window: &mut Window, cx: &mut Context<Self>) {
-        crate::active_thread::attach_pasted_images_as_context(&self.context_store, cx);
+        let images = cx
+            .read_from_clipboard()
+            .map(|item| {
+                item.into_entries()
+                    .filter_map(|entry| {
+                        if let ClipboardEntry::Image(image) = entry {
+                            Some(image)
+                        } else {
+                            None
+                        }
+                    })
+                    .collect::<Vec<_>>()
+            })
+            .unwrap_or_default();
+
+        if images.is_empty() {
+            return;
+        }
+        cx.stop_propagation();
+
+        self.context_store.update(cx, |store, cx| {
+            for image in images {
+                store.add_image_instance(Arc::new(image), cx);
+            }
+        });
     }
 
     fn handle_prompt_editor_events(

crates/agent_ui/src/message_editor.rs 🔗

@@ -1,157 +1,14 @@
-use std::collections::BTreeMap;
-use std::rc::Rc;
-use std::sync::Arc;
-
-use crate::agent_diff::AgentDiffThread;
-use crate::agent_model_selector::AgentModelSelector;
-use crate::tool_compatibility::{IncompatibleToolsState, IncompatibleToolsTooltip};
-use crate::ui::{
-    BurnModeTooltip,
-    preview::{AgentPreview, UsageCallout},
-};
-use agent::history_store::HistoryStore;
-use agent::{
-    context::{AgentContextKey, ContextLoadResult, load_context},
-    context_store::ContextStoreEvent,
-};
-use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};
-use ai_onboarding::ApiKeysWithProviders;
-use buffer_diff::BufferDiff;
-use cloud_llm_client::{CompletionIntent, PlanV1};
-use collections::{HashMap, HashSet};
-use editor::actions::{MoveUp, Paste};
+use agent::{context::AgentContextKey, context_store::ContextStoreEvent};
+use agent_settings::AgentProfileId;
+use collections::HashMap;
 use editor::display_map::CreaseId;
-use editor::{
-    Addon, AnchorRangeExt, ContextMenuOptions, ContextMenuPlacement, Editor, EditorElement,
-    EditorEvent, EditorMode, EditorStyle, MultiBuffer,
-};
-use file_icons::FileIcons;
-use fs::Fs;
-use futures::future::Shared;
-use futures::{FutureExt as _, future};
-use gpui::{
-    Animation, AnimationExt, App, Entity, EventEmitter, Focusable, IntoElement, KeyContext,
-    Subscription, Task, TextStyle, WeakEntity, linear_color_stop, linear_gradient, point,
-    pulsating_between,
-};
-use language::{Buffer, Language, Point};
-use language_model::{
-    ConfiguredModel, LanguageModelRegistry, LanguageModelRequestMessage, MessageContent,
-    ZED_CLOUD_PROVIDER_ID,
-};
-use multi_buffer;
-use project::Project;
-use prompt_store::PromptStore;
-use settings::Settings;
-use std::time::Duration;
-use theme::ThemeSettings;
-use ui::{
-    Callout, Disclosure, Divider, DividerColor, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*,
-};
-use util::ResultExt as _;
-use workspace::{CollaboratorId, Workspace};
-use zed_actions::agent::Chat;
-use zed_actions::agent::ToggleModelSelector;
-
-use crate::context_picker::{ContextPicker, ContextPickerCompletionProvider, crease_for_mention};
-use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
-use crate::profile_selector::{ProfileProvider, ProfileSelector};
-use crate::{
-    ActiveThread, AgentDiffPane, ChatWithFollow, ExpandMessageEditor, Follow, KeepAll,
-    ModelUsageContext, NewThread, OpenAgentDiff, RejectAll, RemoveAllContext, ToggleBurnMode,
-    ToggleContextPicker, ToggleProfileSelector, register_agent_preview,
-};
-use agent::{
-    MessageCrease, Thread, TokenUsageRatio,
-    context_store::ContextStore,
-    thread_store::{TextThreadStore, ThreadStore},
-};
-
-pub const MIN_EDITOR_LINES: usize = 4;
-pub const MAX_EDITOR_LINES: usize = 8;
-
-#[derive(RegisterComponent)]
-pub struct MessageEditor {
-    thread: Entity<Thread>,
-    incompatible_tools_state: Entity<IncompatibleToolsState>,
-    editor: Entity<Editor>,
-    workspace: WeakEntity<Workspace>,
-    project: Entity<Project>,
-    context_store: Entity<ContextStore>,
-    prompt_store: Option<Entity<PromptStore>>,
-    history_store: Option<WeakEntity<HistoryStore>>,
-    context_strip: Entity<ContextStrip>,
-    context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
-    model_selector: Entity<AgentModelSelector>,
-    last_loaded_context: Option<ContextLoadResult>,
-    load_context_task: Option<Shared<Task<()>>>,
-    profile_selector: Entity<ProfileSelector>,
-    edits_expanded: bool,
-    editor_is_expanded: bool,
-    last_estimated_token_count: Option<u64>,
-    update_token_count_task: Option<Task<()>>,
-    _subscriptions: Vec<Subscription>,
-}
-
-pub(crate) fn create_editor(
-    workspace: WeakEntity<Workspace>,
-    context_store: WeakEntity<ContextStore>,
-    thread_store: WeakEntity<ThreadStore>,
-    text_thread_store: WeakEntity<TextThreadStore>,
-    min_lines: usize,
-    max_lines: Option<usize>,
-    window: &mut Window,
-    cx: &mut App,
-) -> Entity<Editor> {
-    let language = Language::new(
-        language::LanguageConfig {
-            completion_query_characters: HashSet::from_iter(['.', '-', '_', '@']),
-            ..Default::default()
-        },
-        None,
-    );
-
-    let editor = cx.new(|cx| {
-        let buffer = cx.new(|cx| Buffer::local("", cx).with_language(Arc::new(language), cx));
-        let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
-        let mut editor = Editor::new(
-            editor::EditorMode::AutoHeight {
-                min_lines,
-                max_lines,
-            },
-            buffer,
-            None,
-            window,
-            cx,
-        );
-        editor.set_placeholder_text("Message the agent – @ to include context", window, cx);
-        editor.disable_word_completions();
-        editor.set_show_indent_guides(false, cx);
-        editor.set_soft_wrap();
-        editor.set_use_modal_editing(true);
-        editor.set_context_menu_options(ContextMenuOptions {
-            min_entries_visible: 12,
-            max_entries_visible: 12,
-            placement: Some(ContextMenuPlacement::Above),
-        });
-        editor.register_addon(ContextCreasesAddon::new());
-        editor.register_addon(MessageEditorAddon::new());
-        editor
-    });
+use editor::{Addon, AnchorRangeExt, Editor};
+use gpui::{App, Entity, Subscription};
+use ui::prelude::*;
 
-    let editor_entity = editor.downgrade();
-    editor.update(cx, |editor, _| {
-        editor.set_completion_provider(Some(Rc::new(ContextPickerCompletionProvider::new(
-            workspace,
-            context_store,
-            Some(thread_store),
-            Some(text_thread_store),
-            editor_entity,
-            None,
-        ))));
-    });
-    editor
-}
+use crate::context_picker::crease_for_mention;
+use crate::profile_selector::ProfileProvider;
+use agent::{MessageCrease, Thread, context_store::ContextStore};
 
 impl ProfileProvider for Entity<Thread> {
     fn profiles_supported(&self, cx: &App) -> bool {
@@ -171,1362 +28,12 @@ impl ProfileProvider for Entity<Thread> {
     }
 }
 
-impl MessageEditor {
-    pub fn new(
-        fs: Arc<dyn Fs>,
-        workspace: WeakEntity<Workspace>,
-        context_store: Entity<ContextStore>,
-        prompt_store: Option<Entity<PromptStore>>,
-        thread_store: WeakEntity<ThreadStore>,
-        text_thread_store: WeakEntity<TextThreadStore>,
-        history_store: Option<WeakEntity<HistoryStore>>,
-        thread: Entity<Thread>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let context_picker_menu_handle = PopoverMenuHandle::default();
-        let model_selector_menu_handle = PopoverMenuHandle::default();
-
-        let editor = create_editor(
-            workspace.clone(),
-            context_store.downgrade(),
-            thread_store.clone(),
-            text_thread_store.clone(),
-            MIN_EDITOR_LINES,
-            Some(MAX_EDITOR_LINES),
-            window,
-            cx,
-        );
-
-        let context_strip = cx.new(|cx| {
-            ContextStrip::new(
-                context_store.clone(),
-                workspace.clone(),
-                Some(thread_store.clone()),
-                Some(text_thread_store.clone()),
-                context_picker_menu_handle.clone(),
-                SuggestContextKind::File,
-                ModelUsageContext::Thread(thread.clone()),
-                window,
-                cx,
-            )
-        });
-
-        let incompatible_tools = cx.new(|cx| IncompatibleToolsState::new(thread.clone(), cx));
-
-        let subscriptions = vec![
-            cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event),
-            cx.subscribe(&editor, |this, _, event: &EditorEvent, cx| {
-                if event == &EditorEvent::BufferEdited {
-                    this.handle_message_changed(cx)
-                }
-            }),
-            cx.observe(&context_store, |this, _, cx| {
-                // When context changes, reload it for token counting.
-                let _ = this.reload_context(cx);
-            }),
-            cx.observe(&thread.read(cx).action_log().clone(), |_, _, cx| {
-                cx.notify()
-            }),
-        ];
-
-        let model_selector = cx.new(|cx| {
-            AgentModelSelector::new(
-                fs.clone(),
-                model_selector_menu_handle,
-                editor.focus_handle(cx),
-                ModelUsageContext::Thread(thread.clone()),
-                window,
-                cx,
-            )
-        });
-
-        let profile_selector = cx.new(|cx| {
-            ProfileSelector::new(fs, Arc::new(thread.clone()), editor.focus_handle(cx), cx)
-        });
-
-        Self {
-            editor: editor.clone(),
-            project: thread.read(cx).project().clone(),
-            thread,
-            incompatible_tools_state: incompatible_tools,
-            workspace,
-            context_store,
-            prompt_store,
-            history_store,
-            context_strip,
-            context_picker_menu_handle,
-            load_context_task: None,
-            last_loaded_context: None,
-            model_selector,
-            edits_expanded: false,
-            editor_is_expanded: false,
-            profile_selector,
-            last_estimated_token_count: None,
-            update_token_count_task: None,
-            _subscriptions: subscriptions,
-        }
-    }
-
-    pub fn context_store(&self) -> &Entity<ContextStore> {
-        &self.context_store
-    }
-
-    pub fn get_text(&self, cx: &App) -> String {
-        self.editor.read(cx).text(cx)
-    }
-
-    pub fn set_text(
-        &mut self,
-        text: impl Into<Arc<str>>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.editor.update(cx, |editor, cx| {
-            editor.set_text(text, window, cx);
-        });
-    }
-
-    pub fn expand_message_editor(
-        &mut self,
-        _: &ExpandMessageEditor,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.set_editor_is_expanded(!self.editor_is_expanded, cx);
-    }
-
-    fn set_editor_is_expanded(&mut self, is_expanded: bool, cx: &mut Context<Self>) {
-        self.editor_is_expanded = is_expanded;
-        self.editor.update(cx, |editor, _| {
-            if self.editor_is_expanded {
-                editor.set_mode(EditorMode::Full {
-                    scale_ui_elements_with_buffer_font_size: false,
-                    show_active_line_background: false,
-                    sized_by_content: false,
-                })
-            } else {
-                editor.set_mode(EditorMode::AutoHeight {
-                    min_lines: MIN_EDITOR_LINES,
-                    max_lines: Some(MAX_EDITOR_LINES),
-                })
-            }
-        });
-        cx.notify();
-    }
-
-    fn toggle_context_picker(
-        &mut self,
-        _: &ToggleContextPicker,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.context_picker_menu_handle.toggle(window, cx);
-    }
-
-    pub fn remove_all_context(
-        &mut self,
-        _: &RemoveAllContext,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.context_store.update(cx, |store, cx| store.clear(cx));
-        cx.notify();
-    }
-
-    fn chat(&mut self, _: &Chat, window: &mut Window, cx: &mut Context<Self>) {
-        if self.is_editor_empty(cx) {
-            return;
-        }
-
-        self.thread.update(cx, |thread, cx| {
-            thread.cancel_editing(cx);
-        });
-
-        if self.thread.read(cx).is_generating() {
-            self.stop_current_and_send_new_message(window, cx);
-            return;
-        }
-
-        self.set_editor_is_expanded(false, cx);
-        self.send_to_model(window, cx);
-
-        cx.emit(MessageEditorEvent::ScrollThreadToBottom);
-        cx.notify();
-    }
-
-    fn chat_with_follow(
-        &mut self,
-        _: &ChatWithFollow,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.workspace
-            .update(cx, |this, cx| {
-                this.follow(CollaboratorId::Agent, window, cx)
-            })
-            .log_err();
-
-        self.chat(&Chat, window, cx);
-    }
-
-    fn is_editor_empty(&self, cx: &App) -> bool {
-        self.editor.read(cx).text(cx).trim().is_empty()
-    }
-
-    pub fn is_editor_fully_empty(&self, cx: &App) -> bool {
-        self.editor.read(cx).is_empty(cx)
-    }
-
-    fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        let Some(ConfiguredModel { model, .. }) = self
-            .thread
-            .update(cx, |thread, cx| thread.get_or_init_configured_model(cx))
-        else {
-            return;
-        };
-
-        let (user_message, user_message_creases) = self.editor.update(cx, |editor, cx| {
-            let creases = extract_message_creases(editor, cx);
-            let text = editor.text(cx);
-            editor.clear(window, cx);
-            (text, creases)
-        });
-
-        self.last_estimated_token_count.take();
-        cx.emit(MessageEditorEvent::EstimatedTokenCount);
-
-        let thread = self.thread.clone();
-        let git_store = self.project.read(cx).git_store().clone();
-        let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
-        let context_task = self.reload_context(cx);
-        let window_handle = window.window_handle();
-
-        cx.spawn(async move |_this, cx| {
-            let (checkpoint, loaded_context) = future::join(checkpoint, context_task).await;
-            let loaded_context = loaded_context.unwrap_or_default();
-
-            thread
-                .update(cx, |thread, cx| {
-                    thread.insert_user_message(
-                        user_message,
-                        loaded_context,
-                        checkpoint.ok(),
-                        user_message_creases,
-                        cx,
-                    );
-                })
-                .log_err();
-
-            thread
-                .update(cx, |thread, cx| {
-                    thread.advance_prompt_id();
-                    thread.send_to_model(
-                        model,
-                        CompletionIntent::UserPrompt,
-                        Some(window_handle),
-                        cx,
-                    );
-                })
-                .log_err();
-        })
-        .detach();
-    }
-
-    fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.thread.update(cx, |thread, cx| {
-            thread.cancel_editing(cx);
-        });
-
-        let canceled = self.thread.update(cx, |thread, cx| {
-            thread.cancel_last_completion(Some(window.window_handle()), cx)
-        });
-
-        if canceled {
-            self.set_editor_is_expanded(false, cx);
-            self.send_to_model(window, cx);
-        }
-    }
-
-    fn handle_context_strip_event(
-        &mut self,
-        _context_strip: &Entity<ContextStrip>,
-        event: &ContextStripEvent,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            ContextStripEvent::PickerDismissed
-            | ContextStripEvent::BlurredEmpty
-            | ContextStripEvent::BlurredDown => {
-                let editor_focus_handle = self.editor.focus_handle(cx);
-                window.focus(&editor_focus_handle);
-            }
-            ContextStripEvent::BlurredUp => {}
-        }
-    }
-
-    fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
-        if self.context_picker_menu_handle.is_deployed() {
-            cx.propagate();
-        } else if self.context_strip.read(cx).has_context_items(cx) {
-            self.context_strip.focus_handle(cx).focus(window);
-        }
-    }
-
-    fn paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
-        crate::active_thread::attach_pasted_images_as_context(&self.context_store, cx);
-    }
-
-    fn handle_review_click(&mut self, window: &mut Window, cx: &mut Context<Self>) {
-        self.edits_expanded = true;
-        AgentDiffPane::deploy(self.thread.clone(), self.workspace.clone(), window, cx).log_err();
-        cx.notify();
-    }
-
-    fn handle_edit_bar_expand(&mut self, cx: &mut Context<Self>) {
-        self.edits_expanded = !self.edits_expanded;
-        cx.notify();
-    }
-
-    fn handle_file_click(
-        &self,
-        buffer: Entity<Buffer>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if let Ok(diff) = AgentDiffPane::deploy(
-            AgentDiffThread::Native(self.thread.clone()),
-            self.workspace.clone(),
-            window,
-            cx,
-        ) {
-            let path_key = multi_buffer::PathKey::for_buffer(&buffer, cx);
-            diff.update(cx, |diff, cx| diff.move_to_path(path_key, window, cx));
-        }
-    }
-
-    pub fn toggle_burn_mode(
-        &mut self,
-        _: &ToggleBurnMode,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        self.thread.update(cx, |thread, _cx| {
-            let active_completion_mode = thread.completion_mode();
-
-            thread.set_completion_mode(match active_completion_mode {
-                CompletionMode::Burn => CompletionMode::Normal,
-                CompletionMode::Normal => CompletionMode::Burn,
-            });
-        });
-    }
-
-    fn handle_accept_all(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        if self.thread.read(cx).has_pending_edit_tool_uses() {
-            return;
-        }
-
-        self.thread.update(cx, |thread, cx| {
-            thread.keep_all_edits(cx);
-        });
-        cx.notify();
-    }
-
-    fn handle_reject_all(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
-        if self.thread.read(cx).has_pending_edit_tool_uses() {
-            return;
-        }
-
-        // Since there's no reject_all_edits method in the thread API,
-        // we need to iterate through all buffers and reject their edits
-        let action_log = self.thread.read(cx).action_log().clone();
-        let changed_buffers = action_log.read(cx).changed_buffers(cx);
-
-        for (buffer, _) in changed_buffers {
-            self.thread.update(cx, |thread, cx| {
-                let buffer_snapshot = buffer.read(cx);
-                let start = buffer_snapshot.anchor_before(Point::new(0, 0));
-                let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
-                thread
-                    .reject_edits_in_ranges(buffer, vec![start..end], cx)
-                    .detach();
-            });
-        }
-        cx.notify();
-    }
-
-    fn handle_reject_file_changes(
-        &mut self,
-        buffer: Entity<Buffer>,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if self.thread.read(cx).has_pending_edit_tool_uses() {
-            return;
-        }
-
-        self.thread.update(cx, |thread, cx| {
-            let buffer_snapshot = buffer.read(cx);
-            let start = buffer_snapshot.anchor_before(Point::new(0, 0));
-            let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
-            thread
-                .reject_edits_in_ranges(buffer, vec![start..end], cx)
-                .detach();
-        });
-        cx.notify();
-    }
-
-    fn handle_accept_file_changes(
-        &mut self,
-        buffer: Entity<Buffer>,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if self.thread.read(cx).has_pending_edit_tool_uses() {
-            return;
-        }
-
-        self.thread.update(cx, |thread, cx| {
-            let buffer_snapshot = buffer.read(cx);
-            let start = buffer_snapshot.anchor_before(Point::new(0, 0));
-            let end = buffer_snapshot.anchor_after(buffer_snapshot.max_point());
-            thread.keep_edits_in_range(buffer, start..end, cx);
-        });
-        cx.notify();
-    }
-
-    fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
-        let thread = self.thread.read(cx);
-        let model = thread.configured_model();
-        if !model?.model.supports_burn_mode() {
-            return None;
-        }
-
-        let active_completion_mode = thread.completion_mode();
-        let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
-        let icon = if burn_mode_enabled {
-            IconName::ZedBurnModeOn
-        } else {
-            IconName::ZedBurnMode
-        };
-
-        Some(
-            IconButton::new("burn-mode", icon)
-                .icon_size(IconSize::Small)
-                .icon_color(Color::Muted)
-                .toggle_state(burn_mode_enabled)
-                .selected_icon_color(Color::Error)
-                .on_click(cx.listener(|this, _event, window, cx| {
-                    this.toggle_burn_mode(&ToggleBurnMode, window, cx);
-                }))
-                .tooltip(move |_window, cx| {
-                    cx.new(|_| BurnModeTooltip::new().selected(burn_mode_enabled))
-                        .into()
-                })
-                .into_any_element(),
-        )
-    }
-
-    fn render_follow_toggle(
-        &self,
-        is_model_selected: bool,
-        cx: &mut Context<Self>,
-    ) -> impl IntoElement {
-        let following = self
-            .workspace
-            .read_with(cx, |workspace, _| {
-                workspace.is_being_followed(CollaboratorId::Agent)
-            })
-            .unwrap_or(false);
-
-        IconButton::new("follow-agent", IconName::Crosshair)
-            .disabled(!is_model_selected)
-            .icon_size(IconSize::Small)
-            .icon_color(Color::Muted)
-            .toggle_state(following)
-            .selected_icon_color(Some(Color::Custom(cx.theme().players().agent().cursor)))
-            .tooltip(move |window, cx| {
-                if following {
-                    Tooltip::for_action("Stop Following Agent", &Follow, window, cx)
-                } else {
-                    Tooltip::with_meta(
-                        "Follow Agent",
-                        Some(&Follow),
-                        "Track the agent's location as it reads and edits files.",
-                        window,
-                        cx,
-                    )
-                }
-            })
-            .on_click(cx.listener(move |this, _, window, cx| {
-                this.workspace
-                    .update(cx, |workspace, cx| {
-                        if following {
-                            workspace.unfollow(CollaboratorId::Agent, window, cx);
-                        } else {
-                            workspace.follow(CollaboratorId::Agent, window, cx);
-                        }
-                    })
-                    .ok();
-            }))
-    }
-
-    fn render_editor(&self, window: &mut Window, cx: &mut Context<Self>) -> Div {
-        let thread = self.thread.read(cx);
-        let model = thread.configured_model();
-
-        let editor_bg_color = cx.theme().colors().editor_background;
-        let is_generating = thread.is_generating();
-        let focus_handle = self.editor.focus_handle(cx);
-
-        let is_model_selected = model.is_some();
-        let is_editor_empty = self.is_editor_empty(cx);
-
-        let incompatible_tools = model
-            .as_ref()
-            .map(|model| {
-                self.incompatible_tools_state.update(cx, |state, cx| {
-                    state.incompatible_tools(&model.model, cx).to_vec()
-                })
-            })
-            .unwrap_or_default();
-
-        let is_editor_expanded = self.editor_is_expanded;
-        let expand_icon = if is_editor_expanded {
-            IconName::Minimize
-        } else {
-            IconName::Maximize
-        };
-
-        v_flex()
-            .key_context("MessageEditor")
-            .on_action(cx.listener(Self::chat))
-            .on_action(cx.listener(Self::chat_with_follow))
-            .on_action(cx.listener(|this, _: &ToggleProfileSelector, window, cx| {
-                this.profile_selector
-                    .read(cx)
-                    .menu_handle()
-                    .toggle(window, cx);
-            }))
-            .on_action(cx.listener(|this, _: &ToggleModelSelector, window, cx| {
-                this.model_selector
-                    .update(cx, |model_selector, cx| model_selector.toggle(window, cx));
-            }))
-            .on_action(cx.listener(Self::toggle_context_picker))
-            .on_action(cx.listener(Self::remove_all_context))
-            .on_action(cx.listener(Self::move_up))
-            .on_action(cx.listener(Self::expand_message_editor))
-            .on_action(cx.listener(Self::toggle_burn_mode))
-            .on_action(
-                cx.listener(|this, _: &KeepAll, window, cx| this.handle_accept_all(window, cx)),
-            )
-            .on_action(
-                cx.listener(|this, _: &RejectAll, window, cx| this.handle_reject_all(window, cx)),
-            )
-            .capture_action(cx.listener(Self::paste))
-            .p_2()
-            .gap_2()
-            .border_t_1()
-            .border_color(cx.theme().colors().border)
-            .bg(editor_bg_color)
-            .child(
-                h_flex()
-                    .justify_between()
-                    .child(self.context_strip.clone())
-                    .when(focus_handle.is_focused(window), |this| {
-                        this.child(
-                            IconButton::new("toggle-height", expand_icon)
-                                .icon_size(IconSize::Small)
-                                .icon_color(Color::Muted)
-                                .tooltip({
-                                    let focus_handle = focus_handle.clone();
-                                    move |window, cx| {
-                                        let expand_label = if is_editor_expanded {
-                                            "Minimize Message Editor".to_string()
-                                        } else {
-                                            "Expand Message Editor".to_string()
-                                        };
-
-                                        Tooltip::for_action_in(
-                                            expand_label,
-                                            &ExpandMessageEditor,
-                                            &focus_handle,
-                                            window,
-                                            cx,
-                                        )
-                                    }
-                                })
-                                .on_click(cx.listener(|_, _, window, cx| {
-                                    window.dispatch_action(Box::new(ExpandMessageEditor), cx);
-                                })),
-                        )
-                    }),
-            )
-            .child(
-                v_flex()
-                    .size_full()
-                    .gap_1()
-                    .when(is_editor_expanded, |this| {
-                        this.h(vh(0.8, window)).justify_between()
-                    })
-                    .child({
-                        let settings = ThemeSettings::get_global(cx);
-                        let font_size = TextSize::Small
-                            .rems(cx)
-                            .to_pixels(settings.agent_font_size(cx));
-                        let line_height = settings.buffer_line_height.value() * font_size;
-
-                        let text_style = TextStyle {
-                            color: cx.theme().colors().text,
-                            font_family: settings.buffer_font.family.clone(),
-                            font_fallbacks: settings.buffer_font.fallbacks.clone(),
-                            font_features: settings.buffer_font.features.clone(),
-                            font_size: font_size.into(),
-                            line_height: line_height.into(),
-                            ..Default::default()
-                        };
-
-                        EditorElement::new(
-                            &self.editor,
-                            EditorStyle {
-                                background: editor_bg_color,
-                                local_player: cx.theme().players().local(),
-                                text: text_style,
-                                syntax: cx.theme().syntax().clone(),
-                                ..Default::default()
-                            },
-                        )
-                        .into_any()
-                    })
-                    .child(
-                        h_flex()
-                            .flex_none()
-                            .flex_wrap()
-                            .justify_between()
-                            .child(
-                                h_flex()
-                                    .child(self.render_follow_toggle(is_model_selected, cx))
-                                    .children(self.render_burn_mode_toggle(cx)),
-                            )
-                            .child(
-                                h_flex()
-                                    .gap_1()
-                                    .flex_wrap()
-                                    .when(!incompatible_tools.is_empty(), |this| {
-                                        this.child(
-                                            IconButton::new(
-                                                "tools-incompatible-warning",
-                                                IconName::Warning,
-                                            )
-                                            .icon_color(Color::Warning)
-                                            .icon_size(IconSize::Small)
-                                            .tooltip({
-                                                move |_, cx| {
-                                                    cx.new(|_| IncompatibleToolsTooltip {
-                                                        incompatible_tools: incompatible_tools
-                                                            .clone(),
-                                                    })
-                                                    .into()
-                                                }
-                                            }),
-                                        )
-                                    })
-                                    .child(self.profile_selector.clone())
-                                    .child(self.model_selector.clone())
-                                    .map({
-                                        move |parent| {
-                                            if is_generating {
-                                                parent
-                                                    .when(is_editor_empty, |parent| {
-                                                        parent.child(
-                                                            IconButton::new(
-                                                                "stop-generation",
-                                                                IconName::Stop,
-                                                            )
-                                                            .icon_color(Color::Error)
-                                                            .style(ButtonStyle::Tinted(
-                                                                ui::TintColor::Error,
-                                                            ))
-                                                            .tooltip(move |window, cx| {
-                                                                Tooltip::for_action(
-                                                                    "Stop Generation",
-                                                                    &editor::actions::Cancel,
-                                                                    window,
-                                                                    cx,
-                                                                )
-                                                            })
-                                                            .on_click({
-                                                                let focus_handle =
-                                                                    focus_handle.clone();
-                                                                move |_event, window, cx| {
-                                                                    focus_handle.dispatch_action(
-                                                                        &editor::actions::Cancel,
-                                                                        window,
-                                                                        cx,
-                                                                    );
-                                                                }
-                                                            })
-                                                            .with_animation(
-                                                                "pulsating-label",
-                                                                Animation::new(
-                                                                    Duration::from_secs(2),
-                                                                )
-                                                                .repeat()
-                                                                .with_easing(pulsating_between(
-                                                                    0.4, 1.0,
-                                                                )),
-                                                                |icon_button, delta| {
-                                                                    icon_button.alpha(delta)
-                                                                },
-                                                            ),
-                                                        )
-                                                    })
-                                                    .when(!is_editor_empty, |parent| {
-                                                        parent.child(
-                                                            IconButton::new(
-                                                                "send-message",
-                                                                IconName::Send,
-                                                            )
-                                                            .icon_color(Color::Accent)
-                                                            .style(ButtonStyle::Filled)
-                                                            .disabled(!is_model_selected)
-                                                            .on_click({
-                                                                let focus_handle =
-                                                                    focus_handle.clone();
-                                                                move |_event, window, cx| {
-                                                                    focus_handle.dispatch_action(
-                                                                        &Chat, window, cx,
-                                                                    );
-                                                                }
-                                                            })
-                                                            .tooltip(move |window, cx| {
-                                                                Tooltip::for_action(
-                                                                    "Stop and Send New Message",
-                                                                    &Chat,
-                                                                    window,
-                                                                    cx,
-                                                                )
-                                                            }),
-                                                        )
-                                                    })
-                                            } else {
-                                                parent.child(
-                                                    IconButton::new("send-message", IconName::Send)
-                                                        .icon_color(Color::Accent)
-                                                        .style(ButtonStyle::Filled)
-                                                        .disabled(
-                                                            is_editor_empty || !is_model_selected,
-                                                        )
-                                                        .on_click({
-                                                            let focus_handle = focus_handle.clone();
-                                                            move |_event, window, cx| {
-                                                                telemetry::event!(
-                                                                    "Agent Message Sent",
-                                                                    agent = "zed",
-                                                                );
-                                                                focus_handle.dispatch_action(
-                                                                    &Chat, window, cx,
-                                                                );
-                                                            }
-                                                        })
-                                                        .when(
-                                                            !is_editor_empty && is_model_selected,
-                                                            |button| {
-                                                                button.tooltip(move |window, cx| {
-                                                                    Tooltip::for_action(
-                                                                        "Send", &Chat, window, cx,
-                                                                    )
-                                                                })
-                                                            },
-                                                        )
-                                                        .when(is_editor_empty, |button| {
-                                                            button.tooltip(Tooltip::text(
-                                                                "Type a message to submit",
-                                                            ))
-                                                        })
-                                                        .when(!is_model_selected, |button| {
-                                                            button.tooltip(Tooltip::text(
-                                                                "Select a model to continue",
-                                                            ))
-                                                        }),
-                                                )
-                                            }
-                                        }
-                                    }),
-                            ),
-                    ),
-            )
-    }
-
-    fn render_edits_bar(
-        &self,
-        changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Div {
-        let focus_handle = self.editor.focus_handle(cx);
-
-        let editor_bg_color = cx.theme().colors().editor_background;
-        let border_color = cx.theme().colors().border;
-        let active_color = cx.theme().colors().element_selected;
-        let bg_edit_files_disclosure = editor_bg_color.blend(active_color.opacity(0.3));
-
-        let is_edit_changes_expanded = self.edits_expanded;
-        let thread = self.thread.read(cx);
-        let pending_edits = thread.has_pending_edit_tool_uses();
-
-        const EDIT_NOT_READY_TOOLTIP_LABEL: &str = "Wait until file edits are complete.";
-
-        v_flex()
-            .mt_1()
-            .mx_2()
-            .bg(bg_edit_files_disclosure)
-            .border_1()
-            .border_b_0()
-            .border_color(border_color)
-            .rounded_t_md()
-            .shadow(vec![gpui::BoxShadow {
-                color: gpui::black().opacity(0.15),
-                offset: point(px(1.), px(-1.)),
-                blur_radius: px(3.),
-                spread_radius: px(0.),
-            }])
-            .child(
-                h_flex()
-                    .p_1()
-                    .justify_between()
-                    .when(is_edit_changes_expanded, |this| {
-                        this.border_b_1().border_color(border_color)
-                    })
-                    .child(
-                        h_flex()
-                            .id("edits-container")
-                            .cursor_pointer()
-                            .w_full()
-                            .gap_1()
-                            .child(
-                                Disclosure::new("edits-disclosure", is_edit_changes_expanded)
-                                    .on_click(cx.listener(|this, _, _, cx| {
-                                        this.handle_edit_bar_expand(cx)
-                                    })),
-                            )
-                            .map(|this| {
-                                if pending_edits {
-                                    this.child(
-                                        Label::new(format!(
-                                            "Editing {} {}…",
-                                            changed_buffers.len(),
-                                            if changed_buffers.len() == 1 {
-                                                "file"
-                                            } else {
-                                                "files"
-                                            }
-                                        ))
-                                        .color(Color::Muted)
-                                        .size(LabelSize::Small)
-                                        .with_animation(
-                                            "edit-label",
-                                            Animation::new(Duration::from_secs(2))
-                                                .repeat()
-                                                .with_easing(pulsating_between(0.3, 0.7)),
-                                            |label, delta| label.alpha(delta),
-                                        ),
-                                    )
-                                } else {
-                                    this.child(
-                                        Label::new("Edits")
-                                            .size(LabelSize::Small)
-                                            .color(Color::Muted),
-                                    )
-                                    .child(
-                                        Label::new("•").size(LabelSize::XSmall).color(Color::Muted),
-                                    )
-                                    .child(
-                                        Label::new(format!(
-                                            "{} {}",
-                                            changed_buffers.len(),
-                                            if changed_buffers.len() == 1 {
-                                                "file"
-                                            } else {
-                                                "files"
-                                            }
-                                        ))
-                                        .size(LabelSize::Small)
-                                        .color(Color::Muted),
-                                    )
-                                }
-                            })
-                            .on_click(
-                                cx.listener(|this, _, _, cx| this.handle_edit_bar_expand(cx)),
-                            ),
-                    )
-                    .child(
-                        h_flex()
-                            .gap_1()
-                            .child(
-                                IconButton::new("review-changes", IconName::ListTodo)
-                                    .icon_size(IconSize::Small)
-                                    .tooltip({
-                                        let focus_handle = focus_handle.clone();
-                                        move |window, cx| {
-                                            Tooltip::for_action_in(
-                                                "Review Changes",
-                                                &OpenAgentDiff,
-                                                &focus_handle,
-                                                window,
-                                                cx,
-                                            )
-                                        }
-                                    })
-                                    .on_click(cx.listener(|this, _, window, cx| {
-                                        this.handle_review_click(window, cx)
-                                    })),
-                            )
-                            .child(Divider::vertical().color(DividerColor::Border))
-                            .child(
-                                Button::new("reject-all-changes", "Reject All")
-                                    .label_size(LabelSize::Small)
-                                    .disabled(pending_edits)
-                                    .when(pending_edits, |this| {
-                                        this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
-                                    })
-                                    .key_binding(
-                                        KeyBinding::for_action_in(
-                                            &RejectAll,
-                                            &focus_handle.clone(),
-                                            window,
-                                            cx,
-                                        )
-                                        .map(|kb| kb.size(rems_from_px(10.))),
-                                    )
-                                    .on_click(cx.listener(|this, _, window, cx| {
-                                        this.handle_reject_all(window, cx)
-                                    })),
-                            )
-                            .child(
-                                Button::new("accept-all-changes", "Accept All")
-                                    .label_size(LabelSize::Small)
-                                    .disabled(pending_edits)
-                                    .when(pending_edits, |this| {
-                                        this.tooltip(Tooltip::text(EDIT_NOT_READY_TOOLTIP_LABEL))
-                                    })
-                                    .key_binding(
-                                        KeyBinding::for_action_in(
-                                            &KeepAll,
-                                            &focus_handle,
-                                            window,
-                                            cx,
-                                        )
-                                        .map(|kb| kb.size(rems_from_px(10.))),
-                                    )
-                                    .on_click(cx.listener(|this, _, window, cx| {
-                                        this.handle_accept_all(window, cx)
-                                    })),
-                            ),
-                    ),
-            )
-            .when(is_edit_changes_expanded, |parent| {
-                parent.child(
-                    v_flex().children(changed_buffers.iter().enumerate().flat_map(
-                        |(index, (buffer, _diff))| {
-                            let file = buffer.read(cx).file()?;
-                            let path = file.path();
-
-                            let file_path = path.parent().and_then(|parent| {
-                                let parent_str = parent.to_string_lossy();
-
-                                if parent_str.is_empty() {
-                                    None
-                                } else {
-                                    Some(
-                                        Label::new(format!(
-                                            "/{}{}",
-                                            parent_str,
-                                            std::path::MAIN_SEPARATOR_STR
-                                        ))
-                                        .color(Color::Muted)
-                                        .size(LabelSize::XSmall)
-                                        .buffer_font(cx),
-                                    )
-                                }
-                            });
-
-                            let file_name = path.file_name().map(|name| {
-                                Label::new(name.to_string_lossy().to_string())
-                                    .size(LabelSize::XSmall)
-                                    .buffer_font(cx)
-                            });
-
-                            let file_icon = FileIcons::get_icon(path, cx)
-                                .map(Icon::from_path)
-                                .map(|icon| icon.color(Color::Muted).size(IconSize::Small))
-                                .unwrap_or_else(|| {
-                                    Icon::new(IconName::File)
-                                        .color(Color::Muted)
-                                        .size(IconSize::Small)
-                                });
-
-                            let overlay_gradient = linear_gradient(
-                                90.,
-                                linear_color_stop(editor_bg_color, 1.),
-                                linear_color_stop(editor_bg_color.opacity(0.2), 0.),
-                            );
-
-                            let element = h_flex()
-                                .group("edited-code")
-                                .id(("file-container", index))
-                                .relative()
-                                .py_1()
-                                .pl_2()
-                                .pr_1()
-                                .gap_2()
-                                .justify_between()
-                                .bg(editor_bg_color)
-                                .when(index < changed_buffers.len() - 1, |parent| {
-                                    parent.border_color(border_color).border_b_1()
-                                })
-                                .child(
-                                    h_flex()
-                                        .id(("file-name", index))
-                                        .pr_8()
-                                        .gap_1p5()
-                                        .max_w_full()
-                                        .overflow_x_scroll()
-                                        .child(file_icon)
-                                        .child(
-                                            h_flex()
-                                                .gap_0p5()
-                                                .children(file_name)
-                                                .children(file_path),
-                                        )
-                                        .on_click({
-                                            let buffer = buffer.clone();
-                                            cx.listener(move |this, _, window, cx| {
-                                                this.handle_file_click(buffer.clone(), window, cx);
-                                            })
-                                        }), // TODO: Implement line diff
-                                            // .child(Label::new("+").color(Color::Created))
-                                            // .child(Label::new("-").color(Color::Deleted)),
-                                            //
-                                )
-                                .child(
-                                    h_flex()
-                                        .gap_1()
-                                        .visible_on_hover("edited-code")
-                                        .child(
-                                            Button::new("review", "Review")
-                                                .label_size(LabelSize::Small)
-                                                .on_click({
-                                                    let buffer = buffer.clone();
-                                                    cx.listener(move |this, _, window, cx| {
-                                                        this.handle_file_click(
-                                                            buffer.clone(),
-                                                            window,
-                                                            cx,
-                                                        );
-                                                    })
-                                                }),
-                                        )
-                                        .child(
-                                            Divider::vertical().color(DividerColor::BorderVariant),
-                                        )
-                                        .child(
-                                            Button::new("reject-file", "Reject")
-                                                .label_size(LabelSize::Small)
-                                                .disabled(pending_edits)
-                                                .on_click({
-                                                    let buffer = buffer.clone();
-                                                    cx.listener(move |this, _, window, cx| {
-                                                        this.handle_reject_file_changes(
-                                                            buffer.clone(),
-                                                            window,
-                                                            cx,
-                                                        );
-                                                    })
-                                                }),
-                                        )
-                                        .child(
-                                            Button::new("accept-file", "Accept")
-                                                .label_size(LabelSize::Small)
-                                                .disabled(pending_edits)
-                                                .on_click({
-                                                    let buffer = buffer.clone();
-                                                    cx.listener(move |this, _, window, cx| {
-                                                        this.handle_accept_file_changes(
-                                                            buffer.clone(),
-                                                            window,
-                                                            cx,
-                                                        );
-                                                    })
-                                                }),
-                                        ),
-                                )
-                                .child(
-                                    div()
-                                        .id("gradient-overlay")
-                                        .absolute()
-                                        .h_full()
-                                        .w_12()
-                                        .top_0()
-                                        .bottom_0()
-                                        .right(px(152.))
-                                        .bg(overlay_gradient),
-                                );
-
-                            Some(element)
-                        },
-                    )),
-                )
-            })
-    }
-
-    fn is_using_zed_provider(&self, cx: &App) -> bool {
-        self.thread
-            .read(cx)
-            .configured_model()
-            .is_some_and(|model| model.provider.id() == ZED_CLOUD_PROVIDER_ID)
-    }
-
-    fn render_usage_callout(&self, line_height: Pixels, cx: &mut Context<Self>) -> Option<Div> {
-        if !self.is_using_zed_provider(cx) {
-            return None;
-        }
-
-        let user_store = self.project.read(cx).user_store().read(cx);
-        if user_store.is_usage_based_billing_enabled() {
-            return None;
-        }
-
-        let plan = user_store
-            .plan()
-            .unwrap_or(cloud_llm_client::Plan::V1(PlanV1::ZedFree));
-
-        let usage = user_store.model_request_usage()?;
-
-        Some(
-            div()
-                .child(UsageCallout::new(plan, usage))
-                .line_height(line_height),
-        )
-    }
-
-    fn render_token_limit_callout(
-        &self,
-        line_height: Pixels,
-        token_usage_ratio: TokenUsageRatio,
-        cx: &mut Context<Self>,
-    ) -> Option<Div> {
-        let (icon, severity) = if token_usage_ratio == TokenUsageRatio::Exceeded {
-            (IconName::Close, Severity::Error)
-        } else {
-            (IconName::Warning, Severity::Warning)
-        };
-
-        let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
-            "Thread reached the token limit"
-        } else {
-            "Thread reaching the token limit soon"
-        };
-
-        let description = if self.is_using_zed_provider(cx) {
-            "To continue, start a new thread from a summary or turn burn mode on."
-        } else {
-            "To continue, start a new thread from a summary."
-        };
-
-        let callout = Callout::new()
-            .line_height(line_height)
-            .severity(severity)
-            .icon(icon)
-            .title(title)
-            .description(description)
-            .actions_slot(
-                h_flex()
-                    .gap_0p5()
-                    .when(self.is_using_zed_provider(cx), |this| {
-                        this.child(
-                            IconButton::new("burn-mode-callout", IconName::ZedBurnMode)
-                                .icon_size(IconSize::XSmall)
-                                .on_click(cx.listener(|this, _event, window, cx| {
-                                    this.toggle_burn_mode(&ToggleBurnMode, window, cx);
-                                })),
-                        )
-                    })
-                    .child(
-                        Button::new("start-new-thread", "Start New Thread")
-                            .label_size(LabelSize::Small)
-                            .on_click(cx.listener(|this, _, window, cx| {
-                                let from_thread_id = Some(this.thread.read(cx).id().clone());
-                                window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
-                            })),
-                    ),
-            );
-
-        Some(
-            div()
-                .border_t_1()
-                .border_color(cx.theme().colors().border)
-                .child(callout),
-        )
-    }
-
-    pub fn last_estimated_token_count(&self) -> Option<u64> {
-        self.last_estimated_token_count
-    }
-
-    pub fn is_waiting_to_update_token_count(&self) -> bool {
-        self.update_token_count_task.is_some()
-    }
-
-    fn reload_context(&mut self, cx: &mut Context<Self>) -> Task<Option<ContextLoadResult>> {
-        let load_task = cx.spawn(async move |this, cx| {
-            let Ok(load_task) = this.update(cx, |this, cx| {
-                let new_context = this
-                    .context_store
-                    .read(cx)
-                    .new_context_for_thread(this.thread.read(cx), None);
-                load_context(new_context, &this.project, &this.prompt_store, cx)
-            }) else {
-                return;
-            };
-            let result = load_task.await;
-            this.update(cx, |this, cx| {
-                this.last_loaded_context = Some(result);
-                this.load_context_task = None;
-                this.message_or_context_changed(false, cx);
-            })
-            .ok();
-        });
-        // Replace existing load task, if any, causing it to be canceled.
-        let load_task = load_task.shared();
-        self.load_context_task = Some(load_task.clone());
-        cx.spawn(async move |this, cx| {
-            load_task.await;
-            this.read_with(cx, |this, _cx| this.last_loaded_context.clone())
-                .ok()
-                .flatten()
-        })
-    }
-
-    fn handle_message_changed(&mut self, cx: &mut Context<Self>) {
-        self.message_or_context_changed(true, cx);
-    }
-
-    fn message_or_context_changed(&mut self, debounce: bool, cx: &mut Context<Self>) {
-        cx.emit(MessageEditorEvent::Changed);
-        self.update_token_count_task.take();
-
-        let Some(model) = self.thread.read(cx).configured_model() else {
-            self.last_estimated_token_count.take();
-            return;
-        };
-
-        let editor = self.editor.clone();
-
-        self.update_token_count_task = Some(cx.spawn(async move |this, cx| {
-            if debounce {
-                cx.background_executor()
-                    .timer(Duration::from_millis(200))
-                    .await;
-            }
-
-            let token_count = if let Some(task) = this
-                .update(cx, |this, cx| {
-                    let loaded_context = this
-                        .last_loaded_context
-                        .as_ref()
-                        .map(|context_load_result| &context_load_result.loaded_context);
-                    let message_text = editor.read(cx).text(cx);
-
-                    if message_text.is_empty()
-                        && loaded_context.is_none_or(|loaded_context| loaded_context.is_empty())
-                    {
-                        return None;
-                    }
-
-                    let mut request_message = LanguageModelRequestMessage {
-                        role: language_model::Role::User,
-                        content: Vec::new(),
-                        cache: false,
-                    };
-
-                    if let Some(loaded_context) = loaded_context {
-                        loaded_context.add_to_request_message(&mut request_message);
-                    }
-
-                    if !message_text.is_empty() {
-                        request_message
-                            .content
-                            .push(MessageContent::Text(message_text));
-                    }
-
-                    let request = language_model::LanguageModelRequest {
-                        thread_id: None,
-                        prompt_id: None,
-                        intent: None,
-                        mode: None,
-                        messages: vec![request_message],
-                        tools: vec![],
-                        tool_choice: None,
-                        stop: vec![],
-                        temperature: AgentSettings::temperature_for_model(&model.model, cx),
-                        thinking_allowed: true,
-                    };
-
-                    Some(model.model.count_tokens(request, cx))
-                })
-                .ok()
-                .flatten()
-            {
-                task.await.log_err()
-            } else {
-                Some(0)
-            };
-
-            this.update(cx, |this, cx| {
-                if let Some(token_count) = token_count {
-                    this.last_estimated_token_count = Some(token_count);
-                    cx.emit(MessageEditorEvent::EstimatedTokenCount);
-                }
-                this.update_token_count_task.take();
-            })
-            .ok();
-        }));
-    }
-}
-
 #[derive(Default)]
 pub struct ContextCreasesAddon {
     creases: HashMap<AgentContextKey, Vec<(CreaseId, SharedString)>>,
     _subscription: Option<Subscription>,
 }
 
-pub struct MessageEditorAddon {}
-
-impl MessageEditorAddon {
-    pub fn new() -> Self {
-        Self {}
-    }
-}
-
-impl Addon for MessageEditorAddon {
-    fn to_any(&self) -> &dyn std::any::Any {
-        self
-    }
-
-    fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
-        Some(self)
-    }
-
-    fn extend_key_context(&self, key_context: &mut KeyContext, cx: &App) {
-        let settings = agent_settings::AgentSettings::get_global(cx);
-        if settings.use_modifier_to_send {
-            key_context.add("use_modifier_to_send");
-        }
-    }
-}
-
 impl Addon for ContextCreasesAddon {
     fn to_any(&self) -> &dyn std::any::Any {
         self

crates/agent_ui/src/profile_selector.rs 🔗

@@ -1,6 +1,8 @@
 use crate::{ManageProfiles, ToggleProfileSelector};
-use agent::agent_profile::{AgentProfile, AvailableProfiles};
-use agent_settings::{AgentDockPosition, AgentProfileId, AgentSettings, builtin_profiles};
+use agent_settings::{
+    AgentDockPosition, AgentProfile, AgentProfileId, AgentSettings, AvailableProfiles,
+    builtin_profiles,
+};
 use fs::Fs;
 use gpui::{Action, Entity, FocusHandle, Subscription, prelude::*};
 use settings::{Settings as _, SettingsStore, update_settings_file};

crates/agent_ui/src/thread_history.rs 🔗

@@ -1,912 +0,0 @@
-use crate::{AgentPanel, RemoveSelectedThread};
-use agent::history_store::{HistoryEntry, HistoryStore};
-use chrono::{Datelike as _, Local, NaiveDate, TimeDelta};
-use editor::{Editor, EditorEvent};
-use fuzzy::{StringMatch, StringMatchCandidate};
-use gpui::{
-    App, ClickEvent, Empty, Entity, FocusHandle, Focusable, ScrollStrategy, Stateful, Task,
-    UniformListScrollHandle, WeakEntity, Window, uniform_list,
-};
-use std::{fmt::Display, ops::Range, sync::Arc};
-use time::{OffsetDateTime, UtcOffset};
-use ui::{
-    HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, Scrollbar, ScrollbarState,
-    Tooltip, prelude::*,
-};
-use util::ResultExt;
-
-pub struct ThreadHistory {
-    agent_panel: WeakEntity<AgentPanel>,
-    history_store: Entity<HistoryStore>,
-    scroll_handle: UniformListScrollHandle,
-    selected_index: usize,
-    hovered_index: Option<usize>,
-    search_editor: Entity<Editor>,
-    all_entries: Arc<Vec<HistoryEntry>>,
-    // When the search is empty, we display date separators between history entries
-    // This vector contains an enum of either a separator or an actual entry
-    separated_items: Vec<ListItemType>,
-    // Maps entry indexes to list item indexes
-    separated_item_indexes: Vec<u32>,
-    _separated_items_task: Option<Task<()>>,
-    search_state: SearchState,
-    scrollbar_visibility: bool,
-    scrollbar_state: ScrollbarState,
-    _subscriptions: Vec<gpui::Subscription>,
-}
-
-enum SearchState {
-    Empty,
-    Searching {
-        query: SharedString,
-        _task: Task<()>,
-    },
-    Searched {
-        query: SharedString,
-        matches: Vec<StringMatch>,
-    },
-}
-
-enum ListItemType {
-    BucketSeparator(TimeBucket),
-    Entry {
-        index: usize,
-        format: EntryTimeFormat,
-    },
-}
-
-impl ListItemType {
-    fn entry_index(&self) -> Option<usize> {
-        match self {
-            ListItemType::BucketSeparator(_) => None,
-            ListItemType::Entry { index, .. } => Some(*index),
-        }
-    }
-}
-
-impl ThreadHistory {
-    pub(crate) fn new(
-        agent_panel: WeakEntity<AgentPanel>,
-        history_store: Entity<HistoryStore>,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Self {
-        let search_editor = cx.new(|cx| {
-            let mut editor = Editor::single_line(window, cx);
-            editor.set_placeholder_text("Search threads...", window, cx);
-            editor
-        });
-
-        let search_editor_subscription =
-            cx.subscribe(&search_editor, |this, search_editor, event, cx| {
-                if let EditorEvent::BufferEdited = event {
-                    let query = search_editor.read(cx).text(cx);
-                    this.search(query.into(), cx);
-                }
-            });
-
-        let history_store_subscription = cx.observe(&history_store, |this, _, cx| {
-            this.update_all_entries(cx);
-        });
-
-        let scroll_handle = UniformListScrollHandle::default();
-        let scrollbar_state = ScrollbarState::new(scroll_handle.clone());
-
-        let mut this = Self {
-            agent_panel,
-            history_store,
-            scroll_handle,
-            selected_index: 0,
-            hovered_index: None,
-            search_state: SearchState::Empty,
-            all_entries: Default::default(),
-            separated_items: Default::default(),
-            separated_item_indexes: Default::default(),
-            search_editor,
-            scrollbar_visibility: true,
-            scrollbar_state,
-            _subscriptions: vec![search_editor_subscription, history_store_subscription],
-            _separated_items_task: None,
-        };
-        this.update_all_entries(cx);
-        this
-    }
-
-    fn update_all_entries(&mut self, cx: &mut Context<Self>) {
-        let new_entries: Arc<Vec<HistoryEntry>> = self
-            .history_store
-            .update(cx, |store, cx| store.entries(cx))
-            .into();
-
-        self._separated_items_task.take();
-
-        let mut items = Vec::with_capacity(new_entries.len() + 1);
-        let mut indexes = Vec::with_capacity(new_entries.len() + 1);
-
-        let bg_task = cx.background_spawn(async move {
-            let mut bucket = None;
-            let today = Local::now().naive_local().date();
-
-            for (index, entry) in new_entries.iter().enumerate() {
-                let entry_date = entry
-                    .updated_at()
-                    .with_timezone(&Local)
-                    .naive_local()
-                    .date();
-                let entry_bucket = TimeBucket::from_dates(today, entry_date);
-
-                if Some(entry_bucket) != bucket {
-                    bucket = Some(entry_bucket);
-                    items.push(ListItemType::BucketSeparator(entry_bucket));
-                }
-
-                indexes.push(items.len() as u32);
-                items.push(ListItemType::Entry {
-                    index,
-                    format: entry_bucket.into(),
-                });
-            }
-            (new_entries, items, indexes)
-        });
-
-        let task = cx.spawn(async move |this, cx| {
-            let (new_entries, items, indexes) = bg_task.await;
-            this.update(cx, |this, cx| {
-                let previously_selected_entry =
-                    this.all_entries.get(this.selected_index).map(|e| e.id());
-
-                this.all_entries = new_entries;
-                this.separated_items = items;
-                this.separated_item_indexes = indexes;
-
-                match &this.search_state {
-                    SearchState::Empty => {
-                        if this.selected_index >= this.all_entries.len() {
-                            this.set_selected_entry_index(
-                                this.all_entries.len().saturating_sub(1),
-                                cx,
-                            );
-                        } else if let Some(prev_id) = previously_selected_entry
-                            && let Some(new_ix) = this
-                                .all_entries
-                                .iter()
-                                .position(|probe| probe.id() == prev_id)
-                        {
-                            this.set_selected_entry_index(new_ix, cx);
-                        }
-                    }
-                    SearchState::Searching { query, .. } | SearchState::Searched { query, .. } => {
-                        this.search(query.clone(), cx);
-                    }
-                }
-
-                cx.notify();
-            })
-            .log_err();
-        });
-        self._separated_items_task = Some(task);
-    }
-
-    fn search(&mut self, query: SharedString, cx: &mut Context<Self>) {
-        if query.is_empty() {
-            self.search_state = SearchState::Empty;
-            cx.notify();
-            return;
-        }
-
-        let all_entries = self.all_entries.clone();
-
-        let fuzzy_search_task = cx.background_spawn({
-            let query = query.clone();
-            let executor = cx.background_executor().clone();
-            async move {
-                let mut candidates = Vec::with_capacity(all_entries.len());
-
-                for (idx, entry) in all_entries.iter().enumerate() {
-                    match entry {
-                        HistoryEntry::Thread(thread) => {
-                            candidates.push(StringMatchCandidate::new(idx, &thread.summary));
-                        }
-                        HistoryEntry::Context(context) => {
-                            candidates.push(StringMatchCandidate::new(idx, &context.title));
-                        }
-                    }
-                }
-
-                const MAX_MATCHES: usize = 100;
-
-                fuzzy::match_strings(
-                    &candidates,
-                    &query,
-                    false,
-                    true,
-                    MAX_MATCHES,
-                    &Default::default(),
-                    executor,
-                )
-                .await
-            }
-        });
-
-        let task = cx.spawn({
-            let query = query.clone();
-            async move |this, cx| {
-                let matches = fuzzy_search_task.await;
-
-                this.update(cx, |this, cx| {
-                    let SearchState::Searching {
-                        query: current_query,
-                        _task,
-                    } = &this.search_state
-                    else {
-                        return;
-                    };
-
-                    if &query == current_query {
-                        this.search_state = SearchState::Searched {
-                            query: query.clone(),
-                            matches,
-                        };
-
-                        this.set_selected_entry_index(0, cx);
-                        cx.notify();
-                    };
-                })
-                .log_err();
-            }
-        });
-
-        self.search_state = SearchState::Searching { query, _task: task };
-        cx.notify();
-    }
-
-    fn matched_count(&self) -> usize {
-        match &self.search_state {
-            SearchState::Empty => self.all_entries.len(),
-            SearchState::Searching { .. } => 0,
-            SearchState::Searched { matches, .. } => matches.len(),
-        }
-    }
-
-    fn list_item_count(&self) -> usize {
-        match &self.search_state {
-            SearchState::Empty => self.separated_items.len(),
-            SearchState::Searching { .. } => 0,
-            SearchState::Searched { matches, .. } => matches.len(),
-        }
-    }
-
-    fn search_produced_no_matches(&self) -> bool {
-        match &self.search_state {
-            SearchState::Empty => false,
-            SearchState::Searching { .. } => false,
-            SearchState::Searched { matches, .. } => matches.is_empty(),
-        }
-    }
-
-    fn get_match(&self, ix: usize) -> Option<&HistoryEntry> {
-        match &self.search_state {
-            SearchState::Empty => self.all_entries.get(ix),
-            SearchState::Searching { .. } => None,
-            SearchState::Searched { matches, .. } => matches
-                .get(ix)
-                .and_then(|m| self.all_entries.get(m.candidate_id)),
-        }
-    }
-
-    pub fn select_previous(
-        &mut self,
-        _: &menu::SelectPrevious,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let count = self.matched_count();
-        if count > 0 {
-            if self.selected_index == 0 {
-                self.set_selected_entry_index(count - 1, cx);
-            } else {
-                self.set_selected_entry_index(self.selected_index - 1, cx);
-            }
-        }
-    }
-
-    pub fn select_next(
-        &mut self,
-        _: &menu::SelectNext,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let count = self.matched_count();
-        if count > 0 {
-            if self.selected_index == count - 1 {
-                self.set_selected_entry_index(0, cx);
-            } else {
-                self.set_selected_entry_index(self.selected_index + 1, cx);
-            }
-        }
-    }
-
-    fn select_first(
-        &mut self,
-        _: &menu::SelectFirst,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        let count = self.matched_count();
-        if count > 0 {
-            self.set_selected_entry_index(0, cx);
-        }
-    }
-
-    fn select_last(&mut self, _: &menu::SelectLast, _window: &mut Window, cx: &mut Context<Self>) {
-        let count = self.matched_count();
-        if count > 0 {
-            self.set_selected_entry_index(count - 1, cx);
-        }
-    }
-
-    fn set_selected_entry_index(&mut self, entry_index: usize, cx: &mut Context<Self>) {
-        self.selected_index = entry_index;
-
-        let scroll_ix = match self.search_state {
-            SearchState::Empty | SearchState::Searching { .. } => self
-                .separated_item_indexes
-                .get(entry_index)
-                .map(|ix| *ix as usize)
-                .unwrap_or(entry_index + 1),
-            SearchState::Searched { .. } => entry_index,
-        };
-
-        self.scroll_handle
-            .scroll_to_item(scroll_ix, ScrollStrategy::Top);
-
-        cx.notify();
-    }
-
-    fn render_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
-        if !(self.scrollbar_visibility || self.scrollbar_state.is_dragging()) {
-            return None;
-        }
-
-        Some(
-            div()
-                .occlude()
-                .id("thread-history-scroll")
-                .h_full()
-                .bg(cx.theme().colors().panel_background.opacity(0.8))
-                .border_l_1()
-                .border_color(cx.theme().colors().border_variant)
-                .absolute()
-                .right_1()
-                .top_0()
-                .bottom_0()
-                .w_4()
-                .pl_1()
-                .cursor_default()
-                .on_mouse_move(cx.listener(|_, _, _window, cx| {
-                    cx.notify();
-                    cx.stop_propagation()
-                }))
-                .on_hover(|_, _window, cx| {
-                    cx.stop_propagation();
-                })
-                .on_any_mouse_down(|_, _window, cx| {
-                    cx.stop_propagation();
-                })
-                .on_scroll_wheel(cx.listener(|_, _, _window, cx| {
-                    cx.notify();
-                }))
-                .children(Scrollbar::vertical(self.scrollbar_state.clone())),
-        )
-    }
-
-    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
-        if let Some(entry) = self.get_match(self.selected_index) {
-            let task_result = match entry {
-                HistoryEntry::Thread(thread) => self.agent_panel.update(cx, move |this, cx| {
-                    this.open_thread_by_id(&thread.id, window, cx)
-                }),
-                HistoryEntry::Context(context) => self.agent_panel.update(cx, move |this, cx| {
-                    this.open_saved_prompt_editor(context.path.clone(), window, cx)
-                }),
-            };
-
-            if let Some(task) = task_result.log_err() {
-                task.detach_and_log_err(cx);
-            };
-
-            cx.notify();
-        }
-    }
-
-    fn remove_selected_thread(
-        &mut self,
-        _: &RemoveSelectedThread,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some(entry) = self.get_match(self.selected_index) {
-            let task_result = match entry {
-                HistoryEntry::Thread(thread) => self
-                    .agent_panel
-                    .update(cx, |this, cx| this.delete_thread(&thread.id, cx)),
-                HistoryEntry::Context(context) => self
-                    .agent_panel
-                    .update(cx, |this, cx| this.delete_context(context.path.clone(), cx)),
-            };
-
-            if let Some(task) = task_result.log_err() {
-                task.detach_and_log_err(cx);
-            };
-
-            cx.notify();
-        }
-    }
-
-    fn list_items(
-        &mut self,
-        range: Range<usize>,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Vec<AnyElement> {
-        let range_start = range.start;
-
-        match &self.search_state {
-            SearchState::Empty => self
-                .separated_items
-                .get(range)
-                .iter()
-                .flat_map(|items| {
-                    items
-                        .iter()
-                        .map(|item| self.render_list_item(item.entry_index(), item, vec![], cx))
-                })
-                .collect(),
-            SearchState::Searched { matches, .. } => matches[range]
-                .iter()
-                .enumerate()
-                .map(|(ix, m)| {
-                    self.render_list_item(
-                        Some(range_start + ix),
-                        &ListItemType::Entry {
-                            index: m.candidate_id,
-                            format: EntryTimeFormat::DateAndTime,
-                        },
-                        m.positions.clone(),
-                        cx,
-                    )
-                })
-                .collect(),
-            SearchState::Searching { .. } => {
-                vec![]
-            }
-        }
-    }
-
-    fn render_list_item(
-        &self,
-        list_entry_ix: Option<usize>,
-        item: &ListItemType,
-        highlight_positions: Vec<usize>,
-        cx: &Context<Self>,
-    ) -> AnyElement {
-        match item {
-            ListItemType::Entry { index, format } => match self.all_entries.get(*index) {
-                Some(entry) => h_flex()
-                    .w_full()
-                    .pb_1()
-                    .child(
-                        HistoryEntryElement::new(entry.clone(), self.agent_panel.clone())
-                            .highlight_positions(highlight_positions)
-                            .timestamp_format(*format)
-                            .selected(list_entry_ix == Some(self.selected_index))
-                            .hovered(list_entry_ix == self.hovered_index)
-                            .on_hover(cx.listener(move |this, is_hovered, _window, cx| {
-                                if *is_hovered {
-                                    this.hovered_index = list_entry_ix;
-                                } else if this.hovered_index == list_entry_ix {
-                                    this.hovered_index = None;
-                                }
-
-                                cx.notify();
-                            }))
-                            .into_any_element(),
-                    )
-                    .into_any(),
-                None => Empty.into_any_element(),
-            },
-            ListItemType::BucketSeparator(bucket) => div()
-                .px(DynamicSpacing::Base06.rems(cx))
-                .pt_2()
-                .pb_1()
-                .child(
-                    Label::new(bucket.to_string())
-                        .size(LabelSize::XSmall)
-                        .color(Color::Muted),
-                )
-                .into_any_element(),
-        }
-    }
-}
-
-impl Focusable for ThreadHistory {
-    fn focus_handle(&self, cx: &App) -> FocusHandle {
-        self.search_editor.focus_handle(cx)
-    }
-}
-
-impl Render for ThreadHistory {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        v_flex()
-            .key_context("ThreadHistory")
-            .size_full()
-            .bg(cx.theme().colors().panel_background)
-            .on_action(cx.listener(Self::select_previous))
-            .on_action(cx.listener(Self::select_next))
-            .on_action(cx.listener(Self::select_first))
-            .on_action(cx.listener(Self::select_last))
-            .on_action(cx.listener(Self::confirm))
-            .on_action(cx.listener(Self::remove_selected_thread))
-            .when(!self.all_entries.is_empty(), |parent| {
-                parent.child(
-                    h_flex()
-                        .h(px(41.)) // Match the toolbar perfectly
-                        .w_full()
-                        .py_1()
-                        .px_2()
-                        .gap_2()
-                        .justify_between()
-                        .border_b_1()
-                        .border_color(cx.theme().colors().border)
-                        .child(
-                            Icon::new(IconName::MagnifyingGlass)
-                                .color(Color::Muted)
-                                .size(IconSize::Small),
-                        )
-                        .child(self.search_editor.clone()),
-                )
-            })
-            .child({
-                let view = v_flex()
-                    .id("list-container")
-                    .relative()
-                    .overflow_hidden()
-                    .flex_grow();
-
-                if self.all_entries.is_empty() {
-                    view.justify_center()
-                        .child(
-                            h_flex().w_full().justify_center().child(
-                                Label::new("You don't have any past threads yet.")
-                                    .size(LabelSize::Small),
-                            ),
-                        )
-                } else if self.search_produced_no_matches() {
-                    view.justify_center().child(
-                        h_flex().w_full().justify_center().child(
-                            Label::new("No threads match your search.").size(LabelSize::Small),
-                        ),
-                    )
-                } else {
-                    view.pr_5()
-                        .child(
-                            uniform_list(
-                                "thread-history",
-                                self.list_item_count(),
-                                cx.processor(|this, range: Range<usize>, window, cx| {
-                                    this.list_items(range, window, cx)
-                                }),
-                            )
-                            .p_1()
-                            .track_scroll(self.scroll_handle.clone())
-                            .flex_grow(),
-                        )
-                        .when_some(self.render_scrollbar(cx), |div, scrollbar| {
-                            div.child(scrollbar)
-                        })
-                }
-            })
-    }
-}
-
-#[derive(IntoElement)]
-pub struct HistoryEntryElement {
-    entry: HistoryEntry,
-    agent_panel: WeakEntity<AgentPanel>,
-    selected: bool,
-    hovered: bool,
-    highlight_positions: Vec<usize>,
-    timestamp_format: EntryTimeFormat,
-    on_hover: Box<dyn Fn(&bool, &mut Window, &mut App) + 'static>,
-}
-
-impl HistoryEntryElement {
-    pub fn new(entry: HistoryEntry, agent_panel: WeakEntity<AgentPanel>) -> Self {
-        Self {
-            entry,
-            agent_panel,
-            selected: false,
-            hovered: false,
-            highlight_positions: vec![],
-            timestamp_format: EntryTimeFormat::DateAndTime,
-            on_hover: Box::new(|_, _, _| {}),
-        }
-    }
-
-    pub fn selected(mut self, selected: bool) -> Self {
-        self.selected = selected;
-        self
-    }
-
-    pub fn hovered(mut self, hovered: bool) -> Self {
-        self.hovered = hovered;
-        self
-    }
-
-    pub fn highlight_positions(mut self, positions: Vec<usize>) -> Self {
-        self.highlight_positions = positions;
-        self
-    }
-
-    pub fn on_hover(mut self, on_hover: impl Fn(&bool, &mut Window, &mut App) + 'static) -> Self {
-        self.on_hover = Box::new(on_hover);
-        self
-    }
-
-    pub fn timestamp_format(mut self, format: EntryTimeFormat) -> Self {
-        self.timestamp_format = format;
-        self
-    }
-}
-
-impl RenderOnce for HistoryEntryElement {
-    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
-        let (id, summary, timestamp) = match &self.entry {
-            HistoryEntry::Thread(thread) => (
-                thread.id.to_string(),
-                thread.summary.clone(),
-                thread.updated_at.timestamp(),
-            ),
-            HistoryEntry::Context(context) => (
-                context.path.to_string_lossy().to_string(),
-                context.title.clone(),
-                context.mtime.timestamp(),
-            ),
-        };
-
-        let thread_timestamp =
-            self.timestamp_format
-                .format_timestamp(&self.agent_panel, timestamp, cx);
-
-        ListItem::new(SharedString::from(id))
-            .rounded()
-            .toggle_state(self.selected)
-            .spacing(ListItemSpacing::Sparse)
-            .start_slot(
-                h_flex()
-                    .w_full()
-                    .gap_2()
-                    .justify_between()
-                    .child(
-                        HighlightedLabel::new(summary, self.highlight_positions)
-                            .size(LabelSize::Small)
-                            .truncate(),
-                    )
-                    .child(
-                        Label::new(thread_timestamp)
-                            .color(Color::Muted)
-                            .size(LabelSize::XSmall),
-                    ),
-            )
-            .on_hover(self.on_hover)
-            .end_slot::<IconButton>(if self.hovered || self.selected {
-                Some(
-                    IconButton::new("delete", IconName::Trash)
-                        .shape(IconButtonShape::Square)
-                        .icon_size(IconSize::XSmall)
-                        .icon_color(Color::Muted)
-                        .tooltip(move |window, cx| {
-                            Tooltip::for_action("Delete", &RemoveSelectedThread, window, cx)
-                        })
-                        .on_click({
-                            let agent_panel = self.agent_panel.clone();
-
-                            let f: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static> =
-                                match &self.entry {
-                                    HistoryEntry::Thread(thread) => {
-                                        let id = thread.id.clone();
-
-                                        Box::new(move |_event, _window, cx| {
-                                            agent_panel
-                                                .update(cx, |this, cx| {
-                                                    this.delete_thread(&id, cx)
-                                                        .detach_and_log_err(cx);
-                                                })
-                                                .ok();
-                                        })
-                                    }
-                                    HistoryEntry::Context(context) => {
-                                        let path = context.path.clone();
-
-                                        Box::new(move |_event, _window, cx| {
-                                            agent_panel
-                                                .update(cx, |this, cx| {
-                                                    this.delete_context(path.clone(), cx)
-                                                        .detach_and_log_err(cx);
-                                                })
-                                                .ok();
-                                        })
-                                    }
-                                };
-                            f
-                        }),
-                )
-            } else {
-                None
-            })
-            .on_click({
-                let agent_panel = self.agent_panel.clone();
-
-                let f: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static> = match &self.entry
-                {
-                    HistoryEntry::Thread(thread) => {
-                        let id = thread.id.clone();
-                        Box::new(move |_event, window, cx| {
-                            agent_panel
-                                .update(cx, |this, cx| {
-                                    this.open_thread_by_id(&id, window, cx)
-                                        .detach_and_log_err(cx);
-                                })
-                                .ok();
-                        })
-                    }
-                    HistoryEntry::Context(context) => {
-                        let path = context.path.clone();
-                        Box::new(move |_event, window, cx| {
-                            agent_panel
-                                .update(cx, |this, cx| {
-                                    this.open_saved_prompt_editor(path.clone(), window, cx)
-                                        .detach_and_log_err(cx);
-                                })
-                                .ok();
-                        })
-                    }
-                };
-                f
-            })
-    }
-}
-
-#[derive(Clone, Copy)]
-pub enum EntryTimeFormat {
-    DateAndTime,
-    TimeOnly,
-}
-
-impl EntryTimeFormat {
-    fn format_timestamp(
-        &self,
-        agent_panel: &WeakEntity<AgentPanel>,
-        timestamp: i64,
-        cx: &App,
-    ) -> String {
-        let timestamp = OffsetDateTime::from_unix_timestamp(timestamp).unwrap();
-        let timezone = agent_panel
-            .read_with(cx, |this, _cx| this.local_timezone())
-            .unwrap_or(UtcOffset::UTC);
-
-        match &self {
-            EntryTimeFormat::DateAndTime => time_format::format_localized_timestamp(
-                timestamp,
-                OffsetDateTime::now_utc(),
-                timezone,
-                time_format::TimestampFormat::EnhancedAbsolute,
-            ),
-            EntryTimeFormat::TimeOnly => time_format::format_time(timestamp),
-        }
-    }
-}
-
-impl From<TimeBucket> for EntryTimeFormat {
-    fn from(bucket: TimeBucket) -> Self {
-        match bucket {
-            TimeBucket::Today => EntryTimeFormat::TimeOnly,
-            TimeBucket::Yesterday => EntryTimeFormat::TimeOnly,
-            TimeBucket::ThisWeek => EntryTimeFormat::DateAndTime,
-            TimeBucket::PastWeek => EntryTimeFormat::DateAndTime,
-            TimeBucket::All => EntryTimeFormat::DateAndTime,
-        }
-    }
-}
-
-#[derive(PartialEq, Eq, Clone, Copy, Debug)]
-enum TimeBucket {
-    Today,
-    Yesterday,
-    ThisWeek,
-    PastWeek,
-    All,
-}
-
-impl TimeBucket {
-    fn from_dates(reference: NaiveDate, date: NaiveDate) -> Self {
-        if date == reference {
-            return TimeBucket::Today;
-        }
-
-        if date == reference - TimeDelta::days(1) {
-            return TimeBucket::Yesterday;
-        }
-
-        let week = date.iso_week();
-
-        if reference.iso_week() == week {
-            return TimeBucket::ThisWeek;
-        }
-
-        let last_week = (reference - TimeDelta::days(7)).iso_week();
-
-        if week == last_week {
-            return TimeBucket::PastWeek;
-        }
-
-        TimeBucket::All
-    }
-}
-
-impl Display for TimeBucket {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            TimeBucket::Today => write!(f, "Today"),
-            TimeBucket::Yesterday => write!(f, "Yesterday"),
-            TimeBucket::ThisWeek => write!(f, "This Week"),
-            TimeBucket::PastWeek => write!(f, "Past Week"),
-            TimeBucket::All => write!(f, "All"),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use chrono::NaiveDate;
-
-    #[test]
-    fn test_time_bucket_from_dates() {
-        let today = NaiveDate::from_ymd_opt(2023, 1, 15).unwrap();
-
-        let date = today;
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::Today);
-
-        let date = NaiveDate::from_ymd_opt(2023, 1, 14).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::Yesterday);
-
-        let date = NaiveDate::from_ymd_opt(2023, 1, 13).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::ThisWeek);
-
-        let date = NaiveDate::from_ymd_opt(2023, 1, 11).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::ThisWeek);
-
-        let date = NaiveDate::from_ymd_opt(2023, 1, 8).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::PastWeek);
-
-        let date = NaiveDate::from_ymd_opt(2023, 1, 5).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::PastWeek);
-
-        // All: not in this week or last week
-        let date = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
-        assert_eq!(TimeBucket::from_dates(today, date), TimeBucket::All);
-
-        // Test year boundary cases
-        let new_year = NaiveDate::from_ymd_opt(2023, 1, 1).unwrap();
-
-        let date = NaiveDate::from_ymd_opt(2022, 12, 31).unwrap();
-        assert_eq!(
-            TimeBucket::from_dates(new_year, date),
-            TimeBucket::Yesterday
-        );
-
-        let date = NaiveDate::from_ymd_opt(2022, 12, 28).unwrap();
-        assert_eq!(TimeBucket::from_dates(new_year, date), TimeBucket::ThisWeek);
-    }
-}

crates/agent_ui/src/tool_compatibility.rs 🔗

@@ -1,94 +0,0 @@
-use agent::{Thread, ThreadEvent};
-use assistant_tool::{Tool, ToolSource};
-use collections::HashMap;
-use gpui::{App, Context, Entity, IntoElement, Render, Subscription, Window};
-use language_model::{LanguageModel, LanguageModelToolSchemaFormat};
-use std::sync::Arc;
-use ui::prelude::*;
-
-pub struct IncompatibleToolsState {
-    cache: HashMap<LanguageModelToolSchemaFormat, Vec<Arc<dyn Tool>>>,
-    thread: Entity<Thread>,
-    _thread_subscription: Subscription,
-}
-
-impl IncompatibleToolsState {
-    pub fn new(thread: Entity<Thread>, cx: &mut Context<Self>) -> Self {
-        let _tool_working_set_subscription = cx.subscribe(&thread, |this, _, event, _| {
-            if let ThreadEvent::ProfileChanged = event {
-                this.cache.clear();
-            }
-        });
-
-        Self {
-            cache: HashMap::default(),
-            thread,
-            _thread_subscription: _tool_working_set_subscription,
-        }
-    }
-
-    pub fn incompatible_tools(
-        &mut self,
-        model: &Arc<dyn LanguageModel>,
-        cx: &App,
-    ) -> &[Arc<dyn Tool>] {
-        self.cache
-            .entry(model.tool_input_format())
-            .or_insert_with(|| {
-                self.thread
-                    .read(cx)
-                    .profile()
-                    .enabled_tools(cx)
-                    .iter()
-                    .filter(|(_, tool)| tool.input_schema(model.tool_input_format()).is_err())
-                    .map(|(_, tool)| tool.clone())
-                    .collect()
-            })
-    }
-}
-
-pub struct IncompatibleToolsTooltip {
-    pub incompatible_tools: Vec<Arc<dyn Tool>>,
-}
-
-impl Render for IncompatibleToolsTooltip {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        ui::tooltip_container(window, cx, |container, _, cx| {
-            container
-                .w_72()
-                .child(Label::new("Incompatible Tools").size(LabelSize::Small))
-                .child(
-                    Label::new(
-                        "This model is incompatible with the following tools from your MCPs:",
-                    )
-                    .size(LabelSize::Small)
-                    .color(Color::Muted),
-                )
-                .child(
-                    v_flex()
-                        .my_1p5()
-                        .py_0p5()
-                        .border_b_1()
-                        .border_color(cx.theme().colors().border_variant)
-                        .children(
-                            self.incompatible_tools
-                                .iter()
-                                .map(|tool| h_flex().gap_4().child(Label::new(tool.name()).size(LabelSize::Small)).map(|parent|
-                                    match tool.source() {
-                                        ToolSource::Native => parent,
-                                        ToolSource::ContextServer { id } => parent.child(Label::new(id).size(LabelSize::Small).color(Color::Muted)),
-                                    }
-                                )),
-                        ),
-                )
-                .child(Label::new("What To Do Instead").size(LabelSize::Small))
-                .child(
-                    Label::new(
-                        "Every other tool continues to work with this model, but to specifically use those, switch to another model.",
-                    )
-                    .size(LabelSize::Small)
-                    .color(Color::Muted),
-                )
-        })
-    }
-}

crates/agent_ui/src/ui.rs 🔗

@@ -5,8 +5,8 @@ mod claude_code_onboarding_modal;
 mod context_pill;
 mod end_trial_upsell;
 mod onboarding_modal;
-pub mod preview;
 mod unavailable_editing_tooltip;
+mod usage_callout;
 
 pub use acp_onboarding_modal::*;
 pub use agent_notification::*;
@@ -16,3 +16,4 @@ pub use context_pill::*;
 pub use end_trial_upsell::*;
 pub use onboarding_modal::*;
 pub use unavailable_editing_tooltip::*;
+pub use usage_callout::*;

crates/agent_ui/src/ui/context_pill.rs 🔗

@@ -13,11 +13,9 @@ use rope::Point;
 use ui::{IconButtonShape, Tooltip, prelude::*, tooltip_container};
 
 use agent::context::{
-    AgentContext, AgentContextHandle, ContextId, ContextKind, DirectoryContext,
-    DirectoryContextHandle, FetchedUrlContext, FileContext, FileContextHandle, ImageContext,
-    ImageStatus, RulesContext, RulesContextHandle, SelectionContext, SelectionContextHandle,
-    SymbolContext, SymbolContextHandle, TextThreadContext, TextThreadContextHandle, ThreadContext,
-    ThreadContextHandle,
+    AgentContextHandle, ContextId, ContextKind, DirectoryContextHandle, FetchedUrlContext,
+    FileContextHandle, ImageContext, ImageStatus, RulesContextHandle, SelectionContextHandle,
+    SymbolContextHandle, TextThreadContextHandle, ThreadContextHandle,
 };
 
 #[derive(IntoElement)]
@@ -317,33 +315,11 @@ impl AddedContext {
         }
     }
 
-    pub fn new_attached(
-        context: &AgentContext,
-        model: Option<&Arc<dyn language_model::LanguageModel>>,
-        cx: &App,
-    ) -> AddedContext {
-        match context {
-            AgentContext::File(context) => Self::attached_file(context, cx),
-            AgentContext::Directory(context) => Self::attached_directory(context),
-            AgentContext::Symbol(context) => Self::attached_symbol(context, cx),
-            AgentContext::Selection(context) => Self::attached_selection(context, cx),
-            AgentContext::FetchedUrl(context) => Self::fetched_url(context.clone()),
-            AgentContext::Thread(context) => Self::attached_thread(context),
-            AgentContext::TextThread(context) => Self::attached_text_thread(context),
-            AgentContext::Rules(context) => Self::attached_rules(context),
-            AgentContext::Image(context) => Self::image(context.clone(), model, cx),
-        }
-    }
-
     fn pending_file(handle: FileContextHandle, cx: &App) -> Option<AddedContext> {
         let full_path = handle.buffer.read(cx).file()?.full_path(cx);
         Some(Self::file(handle, &full_path, cx))
     }
 
-    fn attached_file(context: &FileContext, cx: &App) -> AddedContext {
-        Self::file(context.handle.clone(), &context.full_path, cx)
-    }
-
     fn file(handle: FileContextHandle, full_path: &Path, cx: &App) -> AddedContext {
         let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
         let (name, parent) =
@@ -371,10 +347,6 @@ impl AddedContext {
         Some(Self::directory(handle, &full_path))
     }
 
-    fn attached_directory(context: &DirectoryContext) -> AddedContext {
-        Self::directory(context.handle.clone(), &context.full_path)
-    }
-
     fn directory(handle: DirectoryContextHandle, full_path: &Path) -> AddedContext {
         let full_path_string: SharedString = full_path.to_string_lossy().into_owned().into();
         let (name, parent) =
@@ -411,25 +383,6 @@ impl AddedContext {
         })
     }
 
-    fn attached_symbol(context: &SymbolContext, cx: &App) -> AddedContext {
-        let excerpt = ContextFileExcerpt::new(&context.full_path, context.line_range.clone(), cx);
-        AddedContext {
-            kind: ContextKind::Symbol,
-            name: context.handle.symbol.clone(),
-            parent: Some(excerpt.file_name_and_range.clone()),
-            tooltip: None,
-            icon_path: None,
-            status: ContextStatus::Ready,
-            render_hover: {
-                let text = context.text.clone();
-                Some(Rc::new(move |_, cx| {
-                    excerpt.hover_view(text.clone(), cx).into()
-                }))
-            },
-            handle: AgentContextHandle::Symbol(context.handle.clone()),
-        }
-    }
-
     fn pending_selection(handle: SelectionContextHandle, cx: &App) -> Option<AddedContext> {
         let excerpt = ContextFileExcerpt::new(&handle.full_path(cx)?, handle.line_range(cx), cx);
         Some(AddedContext {
@@ -449,25 +402,6 @@ impl AddedContext {
         })
     }
 
-    fn attached_selection(context: &SelectionContext, cx: &App) -> AddedContext {
-        let excerpt = ContextFileExcerpt::new(&context.full_path, context.line_range.clone(), cx);
-        AddedContext {
-            kind: ContextKind::Selection,
-            name: excerpt.file_name_and_range.clone(),
-            parent: excerpt.parent_name.clone(),
-            tooltip: None,
-            icon_path: excerpt.icon_path.clone(),
-            status: ContextStatus::Ready,
-            render_hover: {
-                let text = context.text.clone();
-                Some(Rc::new(move |_, cx| {
-                    excerpt.hover_view(text.clone(), cx).into()
-                }))
-            },
-            handle: AgentContextHandle::Selection(context.handle.clone()),
-        }
-    }
-
     fn fetched_url(context: FetchedUrlContext) -> AddedContext {
         AddedContext {
             kind: ContextKind::FetchedUrl,
@@ -506,24 +440,6 @@ impl AddedContext {
         }
     }
 
-    fn attached_thread(context: &ThreadContext) -> AddedContext {
-        AddedContext {
-            kind: ContextKind::Thread,
-            name: context.title.clone(),
-            parent: None,
-            tooltip: None,
-            icon_path: None,
-            status: ContextStatus::Ready,
-            render_hover: {
-                let text = context.text.clone();
-                Some(Rc::new(move |_, cx| {
-                    ContextPillHover::new_text(text.clone(), cx).into()
-                }))
-            },
-            handle: AgentContextHandle::Thread(context.handle.clone()),
-        }
-    }
-
     fn pending_text_thread(handle: TextThreadContextHandle, cx: &App) -> AddedContext {
         AddedContext {
             kind: ContextKind::TextThread,
@@ -543,24 +459,6 @@ impl AddedContext {
         }
     }
 
-    fn attached_text_thread(context: &TextThreadContext) -> AddedContext {
-        AddedContext {
-            kind: ContextKind::TextThread,
-            name: context.title.clone(),
-            parent: None,
-            tooltip: None,
-            icon_path: None,
-            status: ContextStatus::Ready,
-            render_hover: {
-                let text = context.text.clone();
-                Some(Rc::new(move |_, cx| {
-                    ContextPillHover::new_text(text.clone(), cx).into()
-                }))
-            },
-            handle: AgentContextHandle::TextThread(context.handle.clone()),
-        }
-    }
-
     fn pending_rules(
         handle: RulesContextHandle,
         prompt_store: Option<&Entity<PromptStore>>,
@@ -584,28 +482,6 @@ impl AddedContext {
         })
     }
 
-    fn attached_rules(context: &RulesContext) -> AddedContext {
-        let title = context
-            .title
-            .clone()
-            .unwrap_or_else(|| "Unnamed Rule".into());
-        AddedContext {
-            kind: ContextKind::Rules,
-            name: title,
-            parent: None,
-            tooltip: None,
-            icon_path: None,
-            status: ContextStatus::Ready,
-            render_hover: {
-                let text = context.text.clone();
-                Some(Rc::new(move |_, cx| {
-                    ContextPillHover::new_text(text.clone(), cx).into()
-                }))
-            },
-            handle: AgentContextHandle::Rules(context.handle.clone()),
-        }
-    }
-
     fn image(
         context: ImageContext,
         model: Option<&Arc<dyn language_model::LanguageModel>>,

crates/agent_ui/src/ui/preview/agent_preview.rs 🔗

@@ -1,89 +0,0 @@
-use std::sync::OnceLock;
-
-use collections::HashMap;
-use component::ComponentId;
-use gpui::{App, Entity, WeakEntity};
-use ui::{AnyElement, Component, ComponentScope, Window};
-use workspace::Workspace;
-
-use crate::ActiveThread;
-
-/// Function type for creating agent component previews
-pub type PreviewFn =
-    fn(WeakEntity<Workspace>, Entity<ActiveThread>, &mut Window, &mut App) -> Option<AnyElement>;
-
-pub struct AgentPreviewFn(fn() -> (ComponentId, PreviewFn));
-
-impl AgentPreviewFn {
-    pub const fn new(f: fn() -> (ComponentId, PreviewFn)) -> Self {
-        Self(f)
-    }
-}
-
-inventory::collect!(AgentPreviewFn);
-
-/// Trait that must be implemented by components that provide agent previews.
-pub trait AgentPreview: Component + Sized {
-    #[allow(unused)] // We can't know this is used due to the distributed slice
-    fn scope(&self) -> ComponentScope {
-        ComponentScope::Agent
-    }
-
-    /// Static method to create a preview for this component type
-    fn agent_preview(
-        workspace: WeakEntity<Workspace>,
-        active_thread: Entity<ActiveThread>,
-        window: &mut Window,
-        cx: &mut App,
-    ) -> Option<AnyElement>;
-}
-
-/// Register an agent preview for the given component type
-#[macro_export]
-macro_rules! register_agent_preview {
-    ($type:ty) => {
-        inventory::submit! {
-            $crate::ui::preview::AgentPreviewFn::new(|| {
-                (
-                    <$type as component::Component>::id(),
-                    <$type as $crate::ui::preview::AgentPreview>::agent_preview,
-                )
-            })
-        }
-    };
-}
-
-/// Lazy initialized registry of preview functions
-static AGENT_PREVIEW_REGISTRY: OnceLock<HashMap<ComponentId, PreviewFn>> = OnceLock::new();
-
-/// Initialize the agent preview registry if needed
-fn get_or_init_registry() -> &'static HashMap<ComponentId, PreviewFn> {
-    AGENT_PREVIEW_REGISTRY.get_or_init(|| {
-        let mut map = HashMap::default();
-        for register_fn in inventory::iter::<AgentPreviewFn>() {
-            let (id, preview_fn) = (register_fn.0)();
-            map.insert(id, preview_fn);
-        }
-        map
-    })
-}
-
-/// Get a specific agent preview by component ID.
-pub fn get_agent_preview(
-    id: &ComponentId,
-    workspace: WeakEntity<Workspace>,
-    active_thread: Entity<ActiveThread>,
-    window: &mut Window,
-    cx: &mut App,
-) -> Option<AnyElement> {
-    let registry = get_or_init_registry();
-    registry
-        .get(id)
-        .and_then(|preview_fn| preview_fn(workspace, active_thread, window, cx))
-}
-
-/// Get all registered agent previews.
-pub fn all_agent_previews() -> Vec<ComponentId> {
-    let registry = get_or_init_registry();
-    registry.keys().cloned().collect()
-}

crates/feature_flags/src/flags.rs 🔗

@@ -29,28 +29,3 @@ pub struct JjUiFeatureFlag {}
 impl FeatureFlag for JjUiFeatureFlag {
     const NAME: &'static str = "jj-ui";
 }
-
-pub struct GeminiAndNativeFeatureFlag;
-
-impl FeatureFlag for GeminiAndNativeFeatureFlag {
-    // This was previously called "acp".
-    //
-    // We renamed it because existing builds used it to enable the Claude Code
-    // integration too, and we'd like to turn Gemini/Native on in new builds
-    // without enabling Claude Code in old builds.
-    const NAME: &'static str = "gemini-and-native";
-
-    fn enabled_for_all() -> bool {
-        true
-    }
-}
-
-pub struct ClaudeCodeFeatureFlag;
-
-impl FeatureFlag for ClaudeCodeFeatureFlag {
-    const NAME: &'static str = "claude-code";
-
-    fn enabled_for_all() -> bool {
-        true
-    }
-}

crates/zed/Cargo.toml 🔗

@@ -27,7 +27,6 @@ agent_settings.workspace = true
 anyhow.workspace = true
 askpass.workspace = true
 assets.workspace = true
-assistant_tool.workspace = true
 assistant_tools.workspace = true
 audio.workspace = true
 auto_update.workspace = true
@@ -77,7 +76,6 @@ gpui_tokio.workspace = true
 
 http_client.workspace = true
 image_viewer.workspace = true
-indoc.workspace = true
 edit_prediction_button.workspace = true
 inspector_ui.workspace = true
 install_cli.workspace = true

crates/zed/src/zed/component_preview.rs 🔗

@@ -3,10 +3,7 @@
 //! A view for exploring Zed components.
 
 mod persistence;
-mod preview_support;
 
-use agent::{TextThreadStore, ThreadStore};
-use agent_ui::ActiveThread;
 use client::UserStore;
 use collections::HashMap;
 use component::{ComponentId, ComponentMetadata, ComponentStatus, components};
@@ -17,14 +14,10 @@ use gpui::{ListState, ScrollHandle, ScrollStrategy, UniformListScrollHandle};
 use languages::LanguageRegistry;
 use notifications::status_toast::{StatusToast, ToastIcon};
 use persistence::COMPONENT_PREVIEW_DB;
-use preview_support::active_thread::{
-    load_preview_text_thread_store, load_preview_thread_store, static_active_thread,
-};
 use project::Project;
 use std::{iter::Iterator, ops::Range, sync::Arc};
 use ui::{ButtonLike, Divider, HighlightedLabel, ListItem, ListSubHeader, Tooltip, prelude::*};
 use ui_input::SingleLineInput;
-use util::ResultExt as _;
 use workspace::{
     AppState, Item, ItemId, SerializableItem, Workspace, WorkspaceId, delete_unloaded_items,
     item::ItemEvent,
@@ -74,7 +67,6 @@ pub fn init(app_state: Arc<AppState>, cx: &mut App) {
 
 enum PreviewEntry {
     AllComponents,
-    ActiveThread,
     Separator,
     Component(ComponentMetadata, Option<Vec<usize>>),
     SectionHeader(SharedString),
@@ -97,12 +89,10 @@ enum PreviewPage {
     #[default]
     AllComponents,
     Component(ComponentId),
-    ActiveThread,
 }
 
 struct ComponentPreview {
     active_page: PreviewPage,
-    active_thread: Option<Entity<ActiveThread>>,
     reset_key: usize,
     component_list: ListState,
     entries: Vec<PreviewEntry>,
@@ -115,8 +105,6 @@ struct ComponentPreview {
     language_registry: Arc<LanguageRegistry>,
     nav_scroll_handle: UniformListScrollHandle,
     project: Entity<Project>,
-    text_thread_store: Option<Entity<TextThreadStore>>,
-    thread_store: Option<Entity<ThreadStore>>,
     user_store: Entity<UserStore>,
     workspace: WeakEntity<Workspace>,
     workspace_id: Option<WorkspaceId>,
@@ -134,32 +122,6 @@ impl ComponentPreview {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> anyhow::Result<Self> {
-        let workspace_clone = workspace.clone();
-        let project_clone = project.clone();
-
-        cx.spawn_in(window, async move |entity, cx| {
-            let thread_store_future = load_preview_thread_store(project_clone.clone(), cx);
-            let text_thread_store_future =
-                load_preview_text_thread_store(workspace_clone.clone(), project_clone.clone(), cx);
-
-            let (thread_store_result, text_thread_store_result) =
-                futures::join!(thread_store_future, text_thread_store_future);
-
-            if let (Some(thread_store), Some(text_thread_store)) = (
-                thread_store_result.log_err(),
-                text_thread_store_result.log_err(),
-            ) {
-                entity
-                    .update_in(cx, |this, window, cx| {
-                        this.thread_store = Some(thread_store.clone());
-                        this.text_thread_store = Some(text_thread_store.clone());
-                        this.create_active_thread(window, cx);
-                    })
-                    .ok();
-            }
-        })
-        .detach();
-
         let component_registry = Arc::new(components());
         let sorted_components = component_registry.sorted_components();
         let selected_index = selected_index.into().unwrap_or(0);
@@ -175,7 +137,6 @@ impl ComponentPreview {
 
         let mut component_preview = Self {
             active_page,
-            active_thread: None,
             reset_key: 0,
             component_list,
             entries: Vec::new(),
@@ -188,8 +149,6 @@ impl ComponentPreview {
             language_registry,
             nav_scroll_handle: UniformListScrollHandle::new(),
             project,
-            text_thread_store: None,
-            thread_store: None,
             user_store,
             workspace,
             workspace_id: None,
@@ -208,43 +167,10 @@ impl ComponentPreview {
         Ok(component_preview)
     }
 
-    pub fn create_active_thread(
-        &mut self,
-        window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> &mut Self {
-        let workspace = self.workspace.clone();
-        let language_registry = self.language_registry.clone();
-        let weak_handle = self.workspace.clone();
-        if let Some(workspace) = workspace.upgrade() {
-            let project = workspace.read(cx).project().clone();
-            if let Some((thread_store, text_thread_store)) = self
-                .thread_store
-                .clone()
-                .zip(self.text_thread_store.clone())
-            {
-                let active_thread = static_active_thread(
-                    weak_handle,
-                    project,
-                    language_registry,
-                    thread_store,
-                    text_thread_store,
-                    window,
-                    cx,
-                );
-                self.active_thread = Some(active_thread);
-                cx.notify();
-            }
-        }
-
-        self
-    }
-
     pub fn active_page_id(&self, _cx: &App) -> ActivePageId {
         match &self.active_page {
             PreviewPage::AllComponents => ActivePageId::default(),
             PreviewPage::Component(component_id) => ActivePageId(component_id.0.to_string()),
-            PreviewPage::ActiveThread => ActivePageId("active_thread".to_string()),
         }
     }
 
@@ -359,7 +285,6 @@ impl ComponentPreview {
 
         // Always show all components first
         entries.push(PreviewEntry::AllComponents);
-        entries.push(PreviewEntry::ActiveThread);
 
         let mut scopes: Vec<_> = scope_groups
             .keys()
@@ -492,19 +417,6 @@ impl ComponentPreview {
                     }))
                     .into_any_element()
             }
-            PreviewEntry::ActiveThread => {
-                let selected = self.active_page == PreviewPage::ActiveThread;
-
-                ListItem::new(ix)
-                    .child(Label::new("Active Thread"))
-                    .selectable(true)
-                    .toggle_state(selected)
-                    .inset(true)
-                    .on_click(cx.listener(move |this, _, _, cx| {
-                        this.set_active_page(PreviewPage::ActiveThread, cx);
-                    }))
-                    .into_any_element()
-            }
             PreviewEntry::Separator => ListItem::new(ix)
                 .disabled(true)
                 .child(div().w_full().py_2().child(Divider::horizontal()))
@@ -572,22 +484,7 @@ impl ComponentPreview {
                 ),
         );
 
-        // Check if the component's scope is Agent
-        if scope == ComponentScope::Agent {
-            if let Some(active_thread) = self.active_thread.clone() {
-                if let Some(element) = agent_ui::get_agent_preview(
-                    &component.id(),
-                    self.workspace.clone(),
-                    active_thread,
-                    window,
-                    cx,
-                ) {
-                    preview_container = preview_container.child(element);
-                } else if let Some(preview) = component.preview() {
-                    preview_container = preview_container.children(preview(window, cx));
-                }
-            }
-        } else if let Some(preview) = component.preview() {
+        if let Some(preview) = component.preview() {
             preview_container = preview_container.children(preview(window, cx));
         }
 
@@ -629,9 +526,6 @@ impl ComponentPreview {
                                 PreviewEntry::AllComponents => {
                                     div().w_full().h_0().into_any_element()
                                 }
-                                PreviewEntry::ActiveThread => {
-                                    div().w_full().h_0().into_any_element()
-                                }
                                 PreviewEntry::Separator => div().w_full().h_0().into_any_element(),
                             }
                         }),
@@ -655,12 +549,7 @@ impl ComponentPreview {
             v_flex()
                 .id("render-component-page")
                 .flex_1()
-                .child(ComponentPreviewPage::new(
-                    component.clone(),
-                    self.workspace.clone(),
-                    self.active_thread.clone(),
-                    self.reset_key,
-                ))
+                .child(ComponentPreviewPage::new(component.clone(), self.reset_key))
                 .into_any_element()
         } else {
             v_flex()
@@ -672,25 +561,6 @@ impl ComponentPreview {
         }
     }
 
-    fn render_active_thread(&self, cx: &mut Context<Self>) -> impl IntoElement {
-        v_flex()
-            .id("render-active-thread")
-            .size_full()
-            .child(
-                div()
-                    .mx_auto()
-                    .w(px(640.))
-                    .h_full()
-                    .py_8()
-                    .bg(cx.theme().colors().panel_background)
-                    .children(self.active_thread.clone())
-                    .when_none(&self.active_thread.clone(), |this| {
-                        this.child("No active thread")
-                    }),
-            )
-            .into_any_element()
-    }
-
     fn test_status_toast(&self, cx: &mut Context<Self>) {
         if let Some(workspace) = self.workspace.upgrade() {
             workspace.update(cx, |workspace, cx| {
@@ -803,9 +673,6 @@ impl Render for ComponentPreview {
                         PreviewPage::Component(id) => self
                             .render_component_page(&id, window, cx)
                             .into_any_element(),
-                        PreviewPage::ActiveThread => {
-                            self.render_active_thread(cx).into_any_element()
-                        }
                     }),
             )
     }
@@ -1009,24 +876,18 @@ impl SerializableItem for ComponentPreview {
 pub struct ComponentPreviewPage {
     // languages: Arc<LanguageRegistry>,
     component: ComponentMetadata,
-    workspace: WeakEntity<Workspace>,
-    active_thread: Option<Entity<ActiveThread>>,
     reset_key: usize,
 }
 
 impl ComponentPreviewPage {
     pub fn new(
         component: ComponentMetadata,
-        workspace: WeakEntity<Workspace>,
-        active_thread: Option<Entity<ActiveThread>>,
         reset_key: usize,
         // languages: Arc<LanguageRegistry>
     ) -> Self {
         Self {
             // languages,
             component,
-            workspace,
-            active_thread,
             reset_key,
         }
     }
@@ -1100,23 +961,7 @@ impl ComponentPreviewPage {
     }
 
     fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
-        // Try to get agent preview first if we have an active thread
-        let maybe_agent_preview = if let Some(active_thread) = self.active_thread.as_ref() {
-            agent_ui::get_agent_preview(
-                &self.component.id(),
-                self.workspace.clone(),
-                active_thread.clone(),
-                window,
-                cx,
-            )
-        } else {
-            None
-        };
-
-        let content = if let Some(ag_preview) = maybe_agent_preview {
-            // Use agent preview if available
-            ag_preview
-        } else if let Some(preview) = self.component.preview() {
+        let content = if let Some(preview) = self.component.preview() {
             // Fall back to component preview
             preview(window, cx).unwrap_or_else(|| {
                 div()

crates/zed/src/zed/component_preview/preview_support/active_thread.rs 🔗

@@ -1,100 +0,0 @@
-use agent::{ContextStore, MessageSegment, TextThreadStore, ThreadStore};
-use agent_ui::ActiveThread;
-use anyhow::{Result, anyhow};
-use assistant_tool::ToolWorkingSet;
-use gpui::{AppContext, AsyncApp, Entity, Task, WeakEntity};
-use indoc::indoc;
-use languages::LanguageRegistry;
-use project::Project;
-use prompt_store::PromptBuilder;
-use std::sync::Arc;
-use ui::{App, Window};
-use workspace::Workspace;
-
-pub fn load_preview_thread_store(
-    project: Entity<Project>,
-    cx: &mut AsyncApp,
-) -> Task<Result<Entity<ThreadStore>>> {
-    cx.update(|cx| {
-        ThreadStore::load(
-            project.clone(),
-            cx.new(|_| ToolWorkingSet::default()),
-            None,
-            Arc::new(PromptBuilder::new(None).unwrap()),
-            cx,
-        )
-    })
-    .unwrap_or(Task::ready(Err(anyhow!("workspace dropped"))))
-}
-
-pub fn load_preview_text_thread_store(
-    workspace: WeakEntity<Workspace>,
-    project: Entity<Project>,
-    cx: &mut AsyncApp,
-) -> Task<Result<Entity<TextThreadStore>>> {
-    workspace
-        .update(cx, |_, cx| {
-            TextThreadStore::new(
-                project.clone(),
-                Arc::new(PromptBuilder::new(None).unwrap()),
-                Default::default(),
-                cx,
-            )
-        })
-        .unwrap_or(Task::ready(Err(anyhow!("workspace dropped"))))
-}
-
-pub fn static_active_thread(
-    workspace: WeakEntity<Workspace>,
-    project: Entity<Project>,
-    language_registry: Arc<LanguageRegistry>,
-    thread_store: Entity<ThreadStore>,
-    text_thread_store: Entity<TextThreadStore>,
-    window: &mut Window,
-    cx: &mut App,
-) -> Entity<ActiveThread> {
-    let context_store =
-        cx.new(|_| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
-
-    let thread = thread_store.update(cx, |thread_store, cx| thread_store.create_thread(cx));
-    thread.update(cx, |thread, cx| {
-        thread.insert_assistant_message(vec![
-            MessageSegment::Text(indoc! {"
-                I'll help you fix the lifetime error in your `cx.spawn` call. When working with async operations in GPUI, there are specific patterns to follow for proper lifetime management.
-
-                Let's look at what's happening in your code:
-
-                ---
-
-                Let's check the current state of the active_thread.rs file to understand what might have changed:
-
-                ---
-
-                Looking at the implementation of `load_preview_thread_store` and understanding GPUI's async patterns, here's the issue:
-
-                1. `load_preview_thread_store` returns a `Task<anyhow::Result<Entity<ThreadStore>>>`, which means it's already a task.
-                2. When you call this function inside another `spawn` call, you're nesting tasks incorrectly.
-
-                Here's the correct way to implement this:
-
-                ---
-
-                The problem is in how you're setting up the async closure and trying to reference variables like `window` and `language_registry` that aren't accessible in that scope.
-
-                Here's how to fix it:
-            "}.to_string()),
-        ], cx);
-    });
-    cx.new(|cx| {
-        ActiveThread::new(
-            thread,
-            thread_store,
-            text_thread_store,
-            context_store,
-            language_registry,
-            workspace.clone(),
-            window,
-            cx,
-        )
-    })
-}