agent_panel.rs

   1use std::ops::{Not, Range};
   2use std::path::Path;
   3use std::rc::Rc;
   4use std::sync::Arc;
   5use std::time::Duration;
   6
   7use acp_thread::AcpThread;
   8use agent_servers::AgentServerCommand;
   9use agent2::{DbThreadMetadata, HistoryEntry};
  10use db::kvp::{Dismissable, KEY_VALUE_STORE};
  11use serde::{Deserialize, Serialize};
  12use zed_actions::OpenBrowser;
  13use zed_actions::agent::ReauthenticateAgent;
  14
  15use crate::acp::{AcpThreadHistory, ThreadHistoryEvent};
  16use crate::agent_diff::AgentDiffThread;
  17use crate::ui::AcpOnboardingModal;
  18use crate::{
  19    AddContextServer, AgentDiffPane, ContinueThread, ContinueWithBurnMode,
  20    DeleteRecentlyOpenThread, ExpandMessageEditor, Follow, InlineAssistant, NewTextThread,
  21    NewThread, OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ResetTrialEndUpsell,
  22    ResetTrialUpsell, ToggleBurnMode, ToggleContextPicker, ToggleNavigationMenu,
  23    ToggleNewThreadMenu, ToggleOptionsMenu,
  24    acp::AcpThreadView,
  25    active_thread::{self, ActiveThread, ActiveThreadEvent},
  26    agent_configuration::{AgentConfiguration, AssistantConfigurationEvent},
  27    agent_diff::AgentDiff,
  28    message_editor::{MessageEditor, MessageEditorEvent},
  29    slash_command::SlashCommandCompletionProvider,
  30    text_thread_editor::{
  31        AgentPanelDelegate, TextThreadEditor, humanize_token_count, make_lsp_adapter_delegate,
  32    },
  33    thread_history::{HistoryEntryElement, ThreadHistory},
  34    ui::{AgentOnboardingModal, EndTrialUpsell},
  35};
  36use crate::{ExternalAgent, NewExternalAgentThread, NewNativeAgentThreadFromSummary};
  37use agent::{
  38    Thread, ThreadError, ThreadEvent, ThreadId, ThreadSummary, TokenUsageRatio,
  39    context_store::ContextStore,
  40    history_store::{HistoryEntryId, HistoryStore},
  41    thread_store::{TextThreadStore, ThreadStore},
  42};
  43use agent_settings::{AgentDockPosition, AgentSettings, CompletionMode, DefaultView};
  44use ai_onboarding::AgentPanelOnboarding;
  45use anyhow::{Result, anyhow};
  46use assistant_context::{AssistantContext, ContextEvent, ContextSummary};
  47use assistant_slash_command::SlashCommandWorkingSet;
  48use assistant_tool::ToolWorkingSet;
  49use client::{UserStore, zed_urls};
  50use cloud_llm_client::{CompletionIntent, Plan, UsageLimit};
  51use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  52use feature_flags::{self, ClaudeCodeFeatureFlag, FeatureFlagAppExt, GeminiAndNativeFeatureFlag};
  53use fs::Fs;
  54use gpui::{
  55    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
  56    Corner, DismissEvent, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable, KeyContext,
  57    Pixels, Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
  58};
  59use language::LanguageRegistry;
  60use language_model::{ConfigurationError, ConfiguredModel, LanguageModelRegistry};
  61use project::{DisableAiSettings, Project, ProjectPath, Worktree};
  62use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
  63use rules_library::{RulesLibrary, open_rules_library};
  64use search::{BufferSearchBar, buffer_search};
  65use settings::{Settings, update_settings_file};
  66use theme::ThemeSettings;
  67use time::UtcOffset;
  68use ui::utils::WithRemSize;
  69use ui::{
  70    Banner, Callout, ContextMenu, ContextMenuEntry, ElevationIndex, KeyBinding, PopoverMenu,
  71    PopoverMenuHandle, ProgressBar, Tab, Tooltip, prelude::*,
  72};
  73use util::ResultExt as _;
  74use workspace::{
  75    CollaboratorId, DraggedSelection, DraggedTab, ToggleZoom, ToolbarItemView, Workspace,
  76    dock::{DockPosition, Panel, PanelEvent},
  77};
  78use zed_actions::{
  79    DecreaseBufferFontSize, IncreaseBufferFontSize, ResetBufferFontSize,
  80    agent::{
  81        OpenAcpOnboardingModal, OpenOnboardingModal, OpenSettings, ResetOnboarding,
  82        ToggleModelSelector,
  83    },
  84    assistant::{OpenRulesLibrary, ToggleFocus},
  85};
  86
  87const AGENT_PANEL_KEY: &str = "agent_panel";
  88
  89#[derive(Serialize, Deserialize, Debug)]
  90struct SerializedAgentPanel {
  91    width: Option<Pixels>,
  92    selected_agent: Option<AgentType>,
  93}
  94
  95pub fn init(cx: &mut App) {
  96    cx.observe_new(
  97        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  98            workspace
  99                .register_action(|workspace, action: &NewThread, window, cx| {
 100                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 101                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
 102                        workspace.focus_panel::<AgentPanel>(window, cx);
 103                    }
 104                })
 105                .register_action(
 106                    |workspace, action: &NewNativeAgentThreadFromSummary, window, cx| {
 107                        if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 108                            panel.update(cx, |panel, cx| {
 109                                panel.new_native_agent_thread_from_summary(action, window, cx)
 110                            });
 111                            workspace.focus_panel::<AgentPanel>(window, cx);
 112                        }
 113                    },
 114                )
 115                .register_action(|workspace, _: &OpenHistory, window, cx| {
 116                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 117                        workspace.focus_panel::<AgentPanel>(window, cx);
 118                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
 119                    }
 120                })
 121                .register_action(|workspace, _: &OpenSettings, window, cx| {
 122                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 123                        workspace.focus_panel::<AgentPanel>(window, cx);
 124                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
 125                    }
 126                })
 127                .register_action(|workspace, _: &NewTextThread, window, cx| {
 128                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 129                        workspace.focus_panel::<AgentPanel>(window, cx);
 130                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
 131                    }
 132                })
 133                .register_action(|workspace, action: &NewExternalAgentThread, window, cx| {
 134                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 135                        workspace.focus_panel::<AgentPanel>(window, cx);
 136                        panel.update(cx, |panel, cx| {
 137                            panel.external_thread(action.agent.clone(), None, None, window, cx)
 138                        });
 139                    }
 140                })
 141                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
 142                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 143                        workspace.focus_panel::<AgentPanel>(window, cx);
 144                        panel.update(cx, |panel, cx| {
 145                            panel.deploy_rules_library(action, window, cx)
 146                        });
 147                    }
 148                })
 149                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
 150                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 151                        workspace.focus_panel::<AgentPanel>(window, cx);
 152                        match &panel.read(cx).active_view {
 153                            ActiveView::Thread { thread, .. } => {
 154                                let thread = thread.read(cx).thread().clone();
 155                                AgentDiffPane::deploy_in_workspace(thread, workspace, window, cx);
 156                            }
 157                            ActiveView::ExternalAgentThread { .. }
 158                            | ActiveView::TextThread { .. }
 159                            | ActiveView::History
 160                            | ActiveView::Configuration => {}
 161                        }
 162                    }
 163                })
 164                .register_action(|workspace, _: &Follow, window, cx| {
 165                    workspace.follow(CollaboratorId::Agent, window, cx);
 166                })
 167                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
 168                    let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
 169                        return;
 170                    };
 171                    workspace.focus_panel::<AgentPanel>(window, cx);
 172                    panel.update(cx, |panel, cx| {
 173                        if let Some(message_editor) = panel.active_message_editor() {
 174                            message_editor.update(cx, |editor, cx| {
 175                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);
 176                            });
 177                        }
 178                    });
 179                })
 180                .register_action(|workspace, _: &ToggleNavigationMenu, window, cx| {
 181                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 182                        workspace.focus_panel::<AgentPanel>(window, cx);
 183                        panel.update(cx, |panel, cx| {
 184                            panel.toggle_navigation_menu(&ToggleNavigationMenu, window, cx);
 185                        });
 186                    }
 187                })
 188                .register_action(|workspace, _: &ToggleOptionsMenu, window, cx| {
 189                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 190                        workspace.focus_panel::<AgentPanel>(window, cx);
 191                        panel.update(cx, |panel, cx| {
 192                            panel.toggle_options_menu(&ToggleOptionsMenu, window, cx);
 193                        });
 194                    }
 195                })
 196                .register_action(|workspace, _: &ToggleNewThreadMenu, window, cx| {
 197                    if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 198                        workspace.focus_panel::<AgentPanel>(window, cx);
 199                        panel.update(cx, |panel, cx| {
 200                            panel.toggle_new_thread_menu(&ToggleNewThreadMenu, window, cx);
 201                        });
 202                    }
 203                })
 204                .register_action(|workspace, _: &OpenOnboardingModal, window, cx| {
 205                    AgentOnboardingModal::toggle(workspace, window, cx)
 206                })
 207                .register_action(|workspace, _: &OpenAcpOnboardingModal, window, cx| {
 208                    AcpOnboardingModal::toggle(workspace, window, cx)
 209                })
 210                .register_action(|_workspace, _: &ResetOnboarding, window, cx| {
 211                    window.dispatch_action(workspace::RestoreBanner.boxed_clone(), cx);
 212                    window.refresh();
 213                })
 214                .register_action(|_workspace, _: &ResetTrialUpsell, _window, cx| {
 215                    OnboardingUpsell::set_dismissed(false, cx);
 216                })
 217                .register_action(|_workspace, _: &ResetTrialEndUpsell, _window, cx| {
 218                    TrialEndUpsell::set_dismissed(false, cx);
 219                });
 220        },
 221    )
 222    .detach();
 223}
 224
 225enum ActiveView {
 226    Thread {
 227        thread: Entity<ActiveThread>,
 228        change_title_editor: Entity<Editor>,
 229        message_editor: Entity<MessageEditor>,
 230        _subscriptions: Vec<gpui::Subscription>,
 231    },
 232    ExternalAgentThread {
 233        thread_view: Entity<AcpThreadView>,
 234    },
 235    TextThread {
 236        context_editor: Entity<TextThreadEditor>,
 237        title_editor: Entity<Editor>,
 238        buffer_search_bar: Entity<BufferSearchBar>,
 239        _subscriptions: Vec<gpui::Subscription>,
 240    },
 241    History,
 242    Configuration,
 243}
 244
 245enum WhichFontSize {
 246    AgentFont,
 247    BufferFont,
 248    None,
 249}
 250
 251// TODO unify this with ExternalAgent
 252#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)]
 253pub enum AgentType {
 254    #[default]
 255    Zed,
 256    TextThread,
 257    Gemini,
 258    ClaudeCode,
 259    NativeAgent,
 260    Custom {
 261        name: SharedString,
 262        command: AgentServerCommand,
 263    },
 264}
 265
 266impl AgentType {
 267    fn label(&self) -> SharedString {
 268        match self {
 269            Self::Zed | Self::TextThread => "Zed Agent".into(),
 270            Self::NativeAgent => "Agent 2".into(),
 271            Self::Gemini => "Gemini CLI".into(),
 272            Self::ClaudeCode => "Claude Code".into(),
 273            Self::Custom { name, .. } => name.into(),
 274        }
 275    }
 276
 277    fn icon(&self) -> Option<IconName> {
 278        match self {
 279            Self::Zed | Self::NativeAgent | Self::TextThread => None,
 280            Self::Gemini => Some(IconName::AiGemini),
 281            Self::ClaudeCode => Some(IconName::AiClaude),
 282            Self::Custom { .. } => Some(IconName::Terminal),
 283        }
 284    }
 285}
 286
 287impl From<ExternalAgent> for AgentType {
 288    fn from(value: ExternalAgent) -> Self {
 289        match value {
 290            ExternalAgent::Gemini => Self::Gemini,
 291            ExternalAgent::ClaudeCode => Self::ClaudeCode,
 292            ExternalAgent::Custom { name, command } => Self::Custom { name, command },
 293            ExternalAgent::NativeAgent => Self::NativeAgent,
 294        }
 295    }
 296}
 297
 298impl ActiveView {
 299    pub fn which_font_size_used(&self) -> WhichFontSize {
 300        match self {
 301            ActiveView::Thread { .. }
 302            | ActiveView::ExternalAgentThread { .. }
 303            | ActiveView::History => WhichFontSize::AgentFont,
 304            ActiveView::TextThread { .. } => WhichFontSize::BufferFont,
 305            ActiveView::Configuration => WhichFontSize::None,
 306        }
 307    }
 308
 309    pub fn thread(
 310        active_thread: Entity<ActiveThread>,
 311        message_editor: Entity<MessageEditor>,
 312        window: &mut Window,
 313        cx: &mut Context<AgentPanel>,
 314    ) -> Self {
 315        let summary = active_thread.read(cx).summary(cx).or_default();
 316
 317        let editor = cx.new(|cx| {
 318            let mut editor = Editor::single_line(window, cx);
 319            editor.set_text(summary.clone(), window, cx);
 320            editor
 321        });
 322
 323        let subscriptions = vec![
 324            cx.subscribe(&message_editor, |this, _, event, cx| match event {
 325                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 326                    cx.notify();
 327                }
 328                MessageEditorEvent::ScrollThreadToBottom => match &this.active_view {
 329                    ActiveView::Thread { thread, .. } => {
 330                        thread.update(cx, |thread, cx| {
 331                            thread.scroll_to_bottom(cx);
 332                        });
 333                    }
 334                    ActiveView::ExternalAgentThread { .. } => {}
 335                    ActiveView::TextThread { .. }
 336                    | ActiveView::History
 337                    | ActiveView::Configuration => {}
 338                },
 339            }),
 340            window.subscribe(&editor, cx, {
 341                {
 342                    let thread = active_thread.clone();
 343                    move |editor, event, window, cx| match event {
 344                        EditorEvent::BufferEdited => {
 345                            let new_summary = editor.read(cx).text(cx);
 346
 347                            thread.update(cx, |thread, cx| {
 348                                thread.thread().update(cx, |thread, cx| {
 349                                    thread.set_summary(new_summary, cx);
 350                                });
 351                            })
 352                        }
 353                        EditorEvent::Blurred => {
 354                            if editor.read(cx).text(cx).is_empty() {
 355                                let summary = thread.read(cx).summary(cx).or_default();
 356
 357                                editor.update(cx, |editor, cx| {
 358                                    editor.set_text(summary, window, cx);
 359                                });
 360                            }
 361                        }
 362                        _ => {}
 363                    }
 364                }
 365            }),
 366            cx.subscribe(&active_thread, |_, _, event, cx| match &event {
 367                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 368                    cx.notify();
 369                }
 370            }),
 371            cx.subscribe_in(&active_thread.read(cx).thread().clone(), window, {
 372                let editor = editor.clone();
 373                move |_, thread, event, window, cx| match event {
 374                    ThreadEvent::SummaryGenerated => {
 375                        let summary = thread.read(cx).summary().or_default();
 376
 377                        editor.update(cx, |editor, cx| {
 378                            editor.set_text(summary, window, cx);
 379                        })
 380                    }
 381                    ThreadEvent::MessageAdded(_) => {
 382                        cx.notify();
 383                    }
 384                    _ => {}
 385                }
 386            }),
 387        ];
 388
 389        Self::Thread {
 390            change_title_editor: editor,
 391            thread: active_thread,
 392            message_editor,
 393            _subscriptions: subscriptions,
 394        }
 395    }
 396
 397    pub fn prompt_editor(
 398        context_editor: Entity<TextThreadEditor>,
 399        history_store: Entity<HistoryStore>,
 400        acp_history_store: Entity<agent2::HistoryStore>,
 401        language_registry: Arc<LanguageRegistry>,
 402        window: &mut Window,
 403        cx: &mut App,
 404    ) -> Self {
 405        let title = context_editor.read(cx).title(cx).to_string();
 406
 407        let editor = cx.new(|cx| {
 408            let mut editor = Editor::single_line(window, cx);
 409            editor.set_text(title, window, cx);
 410            editor
 411        });
 412
 413        // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
 414        // cause a custom summary to be set. The presence of this custom summary would cause
 415        // summarization to not happen.
 416        let mut suppress_first_edit = true;
 417
 418        let subscriptions = vec![
 419            window.subscribe(&editor, cx, {
 420                {
 421                    let context_editor = context_editor.clone();
 422                    move |editor, event, window, cx| match event {
 423                        EditorEvent::BufferEdited => {
 424                            if suppress_first_edit {
 425                                suppress_first_edit = false;
 426                                return;
 427                            }
 428                            let new_summary = editor.read(cx).text(cx);
 429
 430                            context_editor.update(cx, |context_editor, cx| {
 431                                context_editor
 432                                    .context()
 433                                    .update(cx, |assistant_context, cx| {
 434                                        assistant_context.set_custom_summary(new_summary, cx);
 435                                    })
 436                            })
 437                        }
 438                        EditorEvent::Blurred => {
 439                            if editor.read(cx).text(cx).is_empty() {
 440                                let summary = context_editor
 441                                    .read(cx)
 442                                    .context()
 443                                    .read(cx)
 444                                    .summary()
 445                                    .or_default();
 446
 447                                editor.update(cx, |editor, cx| {
 448                                    editor.set_text(summary, window, cx);
 449                                });
 450                            }
 451                        }
 452                        _ => {}
 453                    }
 454                }
 455            }),
 456            window.subscribe(&context_editor.read(cx).context().clone(), cx, {
 457                let editor = editor.clone();
 458                move |assistant_context, event, window, cx| match event {
 459                    ContextEvent::SummaryGenerated => {
 460                        let summary = assistant_context.read(cx).summary().or_default();
 461
 462                        editor.update(cx, |editor, cx| {
 463                            editor.set_text(summary, window, cx);
 464                        })
 465                    }
 466                    ContextEvent::PathChanged { old_path, new_path } => {
 467                        history_store.update(cx, |history_store, cx| {
 468                            if let Some(old_path) = old_path {
 469                                history_store
 470                                    .replace_recently_opened_text_thread(old_path, new_path, cx);
 471                            } else {
 472                                history_store.push_recently_opened_entry(
 473                                    HistoryEntryId::Context(new_path.clone()),
 474                                    cx,
 475                                );
 476                            }
 477                        });
 478
 479                        acp_history_store.update(cx, |history_store, cx| {
 480                            if let Some(old_path) = old_path {
 481                                history_store
 482                                    .replace_recently_opened_text_thread(old_path, new_path, cx);
 483                            } else {
 484                                history_store.push_recently_opened_entry(
 485                                    agent2::HistoryEntryId::TextThread(new_path.clone()),
 486                                    cx,
 487                                );
 488                            }
 489                        });
 490                    }
 491                    _ => {}
 492                }
 493            }),
 494        ];
 495
 496        let buffer_search_bar =
 497            cx.new(|cx| BufferSearchBar::new(Some(language_registry), window, cx));
 498        buffer_search_bar.update(cx, |buffer_search_bar, cx| {
 499            buffer_search_bar.set_active_pane_item(Some(&context_editor), window, cx)
 500        });
 501
 502        Self::TextThread {
 503            context_editor,
 504            title_editor: editor,
 505            buffer_search_bar,
 506            _subscriptions: subscriptions,
 507        }
 508    }
 509}
 510
 511pub struct AgentPanel {
 512    workspace: WeakEntity<Workspace>,
 513    user_store: Entity<UserStore>,
 514    project: Entity<Project>,
 515    fs: Arc<dyn Fs>,
 516    language_registry: Arc<LanguageRegistry>,
 517    thread_store: Entity<ThreadStore>,
 518    acp_history: Entity<AcpThreadHistory>,
 519    acp_history_store: Entity<agent2::HistoryStore>,
 520    _default_model_subscription: Subscription,
 521    context_store: Entity<TextThreadStore>,
 522    prompt_store: Option<Entity<PromptStore>>,
 523    inline_assist_context_store: Entity<ContextStore>,
 524    configuration: Option<Entity<AgentConfiguration>>,
 525    configuration_subscription: Option<Subscription>,
 526    local_timezone: UtcOffset,
 527    active_view: ActiveView,
 528    previous_view: Option<ActiveView>,
 529    history_store: Entity<HistoryStore>,
 530    history: Entity<ThreadHistory>,
 531    hovered_recent_history_item: Option<usize>,
 532    new_thread_menu_handle: PopoverMenuHandle<ContextMenu>,
 533    agent_panel_menu_handle: PopoverMenuHandle<ContextMenu>,
 534    assistant_navigation_menu_handle: PopoverMenuHandle<ContextMenu>,
 535    assistant_navigation_menu: Option<Entity<ContextMenu>>,
 536    width: Option<Pixels>,
 537    height: Option<Pixels>,
 538    zoomed: bool,
 539    pending_serialization: Option<Task<Result<()>>>,
 540    onboarding: Entity<AgentPanelOnboarding>,
 541    selected_agent: AgentType,
 542}
 543
 544impl AgentPanel {
 545    fn serialize(&mut self, cx: &mut Context<Self>) {
 546        let width = self.width;
 547        let selected_agent = self.selected_agent.clone();
 548        self.pending_serialization = Some(cx.background_spawn(async move {
 549            KEY_VALUE_STORE
 550                .write_kvp(
 551                    AGENT_PANEL_KEY.into(),
 552                    serde_json::to_string(&SerializedAgentPanel {
 553                        width,
 554                        selected_agent: Some(selected_agent),
 555                    })?,
 556                )
 557                .await?;
 558            anyhow::Ok(())
 559        }));
 560    }
 561
 562    pub fn load(
 563        workspace: WeakEntity<Workspace>,
 564        prompt_builder: Arc<PromptBuilder>,
 565        mut cx: AsyncWindowContext,
 566    ) -> Task<Result<Entity<Self>>> {
 567        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
 568        cx.spawn(async move |cx| {
 569            let prompt_store = match prompt_store {
 570                Ok(prompt_store) => prompt_store.await.ok(),
 571                Err(_) => None,
 572            };
 573            let tools = cx.new(|_| ToolWorkingSet::default())?;
 574            let thread_store = workspace
 575                .update(cx, |workspace, cx| {
 576                    let project = workspace.project().clone();
 577                    ThreadStore::load(
 578                        project,
 579                        tools.clone(),
 580                        prompt_store.clone(),
 581                        prompt_builder.clone(),
 582                        cx,
 583                    )
 584                })?
 585                .await?;
 586
 587            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 588            let context_store = workspace
 589                .update(cx, |workspace, cx| {
 590                    let project = workspace.project().clone();
 591                    assistant_context::ContextStore::new(
 592                        project,
 593                        prompt_builder.clone(),
 594                        slash_commands,
 595                        cx,
 596                    )
 597                })?
 598                .await?;
 599
 600            let serialized_panel = if let Some(panel) = cx
 601                .background_spawn(async move { KEY_VALUE_STORE.read_kvp(AGENT_PANEL_KEY) })
 602                .await
 603                .log_err()
 604                .flatten()
 605            {
 606                serde_json::from_str::<SerializedAgentPanel>(&panel).log_err()
 607            } else {
 608                None
 609            };
 610
 611            let panel = workspace.update_in(cx, |workspace, window, cx| {
 612                let panel = cx.new(|cx| {
 613                    Self::new(
 614                        workspace,
 615                        thread_store,
 616                        context_store,
 617                        prompt_store,
 618                        window,
 619                        cx,
 620                    )
 621                });
 622                if let Some(serialized_panel) = serialized_panel {
 623                    panel.update(cx, |panel, cx| {
 624                        panel.width = serialized_panel.width.map(|w| w.round());
 625                        if let Some(selected_agent) = serialized_panel.selected_agent {
 626                            panel.selected_agent = selected_agent.clone();
 627                            panel.new_agent_thread(selected_agent, window, cx);
 628                        }
 629                        cx.notify();
 630                    });
 631                } else {
 632                    panel.update(cx, |panel, cx| {
 633                        panel.new_agent_thread(AgentType::NativeAgent, window, cx);
 634                    });
 635                }
 636                panel
 637            })?;
 638
 639            Ok(panel)
 640        })
 641    }
 642
 643    fn new(
 644        workspace: &Workspace,
 645        thread_store: Entity<ThreadStore>,
 646        context_store: Entity<TextThreadStore>,
 647        prompt_store: Option<Entity<PromptStore>>,
 648        window: &mut Window,
 649        cx: &mut Context<Self>,
 650    ) -> Self {
 651        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 652        let fs = workspace.app_state().fs.clone();
 653        let user_store = workspace.app_state().user_store.clone();
 654        let project = workspace.project();
 655        let language_registry = project.read(cx).languages().clone();
 656        let client = workspace.client().clone();
 657        let workspace = workspace.weak_handle();
 658        let weak_self = cx.entity().downgrade();
 659
 660        let message_editor_context_store =
 661            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 662        let inline_assist_context_store =
 663            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
 664
 665        let thread_id = thread.read(cx).id().clone();
 666
 667        let history_store = cx.new(|cx| {
 668            HistoryStore::new(
 669                thread_store.clone(),
 670                context_store.clone(),
 671                [HistoryEntryId::Thread(thread_id)],
 672                cx,
 673            )
 674        });
 675
 676        let message_editor = cx.new(|cx| {
 677            MessageEditor::new(
 678                fs.clone(),
 679                workspace.clone(),
 680                message_editor_context_store.clone(),
 681                prompt_store.clone(),
 682                thread_store.downgrade(),
 683                context_store.downgrade(),
 684                Some(history_store.downgrade()),
 685                thread.clone(),
 686                window,
 687                cx,
 688            )
 689        });
 690
 691        let acp_history_store = cx.new(|cx| agent2::HistoryStore::new(context_store.clone(), cx));
 692        let acp_history = cx.new(|cx| AcpThreadHistory::new(acp_history_store.clone(), window, cx));
 693        cx.subscribe_in(
 694            &acp_history,
 695            window,
 696            |this, _, event, window, cx| match event {
 697                ThreadHistoryEvent::Open(HistoryEntry::AcpThread(thread)) => {
 698                    this.external_thread(
 699                        Some(crate::ExternalAgent::NativeAgent),
 700                        Some(thread.clone()),
 701                        None,
 702                        window,
 703                        cx,
 704                    );
 705                }
 706                ThreadHistoryEvent::Open(HistoryEntry::TextThread(thread)) => {
 707                    this.open_saved_prompt_editor(thread.path.clone(), window, cx)
 708                        .detach_and_log_err(cx);
 709                }
 710            },
 711        )
 712        .detach();
 713
 714        cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 715
 716        let active_thread = cx.new(|cx| {
 717            ActiveThread::new(
 718                thread.clone(),
 719                thread_store.clone(),
 720                context_store.clone(),
 721                message_editor_context_store.clone(),
 722                language_registry.clone(),
 723                workspace.clone(),
 724                window,
 725                cx,
 726            )
 727        });
 728
 729        let panel_type = AgentSettings::get_global(cx).default_view;
 730        let active_view = match panel_type {
 731            DefaultView::Thread => ActiveView::thread(active_thread, message_editor, window, cx),
 732            DefaultView::TextThread => {
 733                let context =
 734                    context_store.update(cx, |context_store, cx| context_store.create(cx));
 735                let lsp_adapter_delegate = make_lsp_adapter_delegate(&project.clone(), cx).unwrap();
 736                let context_editor = cx.new(|cx| {
 737                    let mut editor = TextThreadEditor::for_context(
 738                        context,
 739                        fs.clone(),
 740                        workspace.clone(),
 741                        project.clone(),
 742                        lsp_adapter_delegate,
 743                        window,
 744                        cx,
 745                    );
 746                    editor.insert_default_prompt(window, cx);
 747                    editor
 748                });
 749                ActiveView::prompt_editor(
 750                    context_editor,
 751                    history_store.clone(),
 752                    acp_history_store.clone(),
 753                    language_registry.clone(),
 754                    window,
 755                    cx,
 756                )
 757            }
 758        };
 759
 760        AgentDiff::set_active_thread(&workspace, thread.clone(), window, cx);
 761
 762        let weak_panel = weak_self.clone();
 763
 764        window.defer(cx, move |window, cx| {
 765            let panel = weak_panel.clone();
 766            let assistant_navigation_menu =
 767                ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
 768                    if let Some(panel) = panel.upgrade() {
 769                        if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
 770                            menu = Self::populate_recently_opened_menu_section_new(menu, panel, cx);
 771                        } else {
 772                            menu = Self::populate_recently_opened_menu_section_old(menu, panel, cx);
 773                        }
 774                    }
 775                    menu.action("View All", Box::new(OpenHistory))
 776                        .end_slot_action(DeleteRecentlyOpenThread.boxed_clone())
 777                        .fixed_width(px(320.).into())
 778                        .keep_open_on_confirm(false)
 779                        .key_context("NavigationMenu")
 780                });
 781            weak_panel
 782                .update(cx, |panel, cx| {
 783                    cx.subscribe_in(
 784                        &assistant_navigation_menu,
 785                        window,
 786                        |_, menu, _: &DismissEvent, window, cx| {
 787                            menu.update(cx, |menu, _| {
 788                                menu.clear_selected();
 789                            });
 790                            cx.focus_self(window);
 791                        },
 792                    )
 793                    .detach();
 794                    panel.assistant_navigation_menu = Some(assistant_navigation_menu);
 795                })
 796                .ok();
 797        });
 798
 799        let _default_model_subscription =
 800            cx.subscribe(
 801                &LanguageModelRegistry::global(cx),
 802                |this, _, event: &language_model::Event, cx| {
 803                    if let language_model::Event::DefaultModelChanged = event {
 804                        match &this.active_view {
 805                            ActiveView::Thread { thread, .. } => {
 806                                thread.read(cx).thread().clone().update(cx, |thread, cx| {
 807                                    thread.get_or_init_configured_model(cx)
 808                                });
 809                            }
 810                            ActiveView::ExternalAgentThread { .. }
 811                            | ActiveView::TextThread { .. }
 812                            | ActiveView::History
 813                            | ActiveView::Configuration => {}
 814                        }
 815                    }
 816                },
 817            );
 818
 819        let onboarding = cx.new(|cx| {
 820            AgentPanelOnboarding::new(
 821                user_store.clone(),
 822                client,
 823                |_window, cx| {
 824                    OnboardingUpsell::set_dismissed(true, cx);
 825                },
 826                cx,
 827            )
 828        });
 829
 830        Self {
 831            active_view,
 832            workspace,
 833            user_store,
 834            project: project.clone(),
 835            fs: fs.clone(),
 836            language_registry,
 837            thread_store: thread_store.clone(),
 838            _default_model_subscription,
 839            context_store,
 840            prompt_store,
 841            configuration: None,
 842            configuration_subscription: None,
 843            local_timezone: UtcOffset::from_whole_seconds(
 844                chrono::Local::now().offset().local_minus_utc(),
 845            )
 846            .unwrap(),
 847            inline_assist_context_store,
 848            previous_view: None,
 849            history_store: history_store.clone(),
 850            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
 851            hovered_recent_history_item: None,
 852            new_thread_menu_handle: PopoverMenuHandle::default(),
 853            agent_panel_menu_handle: PopoverMenuHandle::default(),
 854            assistant_navigation_menu_handle: PopoverMenuHandle::default(),
 855            assistant_navigation_menu: None,
 856            width: None,
 857            height: None,
 858            zoomed: false,
 859            pending_serialization: None,
 860            onboarding,
 861            acp_history,
 862            acp_history_store,
 863            selected_agent: AgentType::default(),
 864        }
 865    }
 866
 867    pub fn toggle_focus(
 868        workspace: &mut Workspace,
 869        _: &ToggleFocus,
 870        window: &mut Window,
 871        cx: &mut Context<Workspace>,
 872    ) {
 873        if workspace
 874            .panel::<Self>(cx)
 875            .is_some_and(|panel| panel.read(cx).enabled(cx))
 876            && !DisableAiSettings::get_global(cx).disable_ai
 877        {
 878            workspace.toggle_panel_focus::<Self>(window, cx);
 879        }
 880    }
 881
 882    pub(crate) fn local_timezone(&self) -> UtcOffset {
 883        self.local_timezone
 884    }
 885
 886    pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
 887        &self.prompt_store
 888    }
 889
 890    pub(crate) fn inline_assist_context_store(&self) -> &Entity<ContextStore> {
 891        &self.inline_assist_context_store
 892    }
 893
 894    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 895        &self.thread_store
 896    }
 897
 898    pub(crate) fn text_thread_store(&self) -> &Entity<TextThreadStore> {
 899        &self.context_store
 900    }
 901
 902    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 903        match &self.active_view {
 904            ActiveView::Thread { thread, .. } => {
 905                thread.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
 906            }
 907            ActiveView::ExternalAgentThread { .. }
 908            | ActiveView::TextThread { .. }
 909            | ActiveView::History
 910            | ActiveView::Configuration => {}
 911        }
 912    }
 913
 914    fn active_message_editor(&self) -> Option<&Entity<MessageEditor>> {
 915        match &self.active_view {
 916            ActiveView::Thread { message_editor, .. } => Some(message_editor),
 917            ActiveView::ExternalAgentThread { .. }
 918            | ActiveView::TextThread { .. }
 919            | ActiveView::History
 920            | ActiveView::Configuration => None,
 921        }
 922    }
 923
 924    fn active_thread_view(&self) -> Option<&Entity<AcpThreadView>> {
 925        match &self.active_view {
 926            ActiveView::ExternalAgentThread { thread_view, .. } => Some(thread_view),
 927            ActiveView::Thread { .. }
 928            | ActiveView::TextThread { .. }
 929            | ActiveView::History
 930            | ActiveView::Configuration => None,
 931        }
 932    }
 933
 934    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 935        if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
 936            return self.new_agent_thread(AgentType::NativeAgent, window, cx);
 937        }
 938        // Preserve chat box text when using creating new thread
 939        let preserved_text = self
 940            .active_message_editor()
 941            .map(|editor| editor.read(cx).get_text(cx).trim().to_string());
 942
 943        let thread = self
 944            .thread_store
 945            .update(cx, |this, cx| this.create_thread(cx));
 946
 947        let context_store = cx.new(|_cx| {
 948            ContextStore::new(
 949                self.project.downgrade(),
 950                Some(self.thread_store.downgrade()),
 951            )
 952        });
 953
 954        if let Some(other_thread_id) = action.from_thread_id.clone() {
 955            let other_thread_task = self.thread_store.update(cx, |this, cx| {
 956                this.open_thread(&other_thread_id, window, cx)
 957            });
 958
 959            cx.spawn({
 960                let context_store = context_store.clone();
 961
 962                async move |_panel, cx| {
 963                    let other_thread = other_thread_task.await?;
 964
 965                    context_store.update(cx, |this, cx| {
 966                        this.add_thread(other_thread, false, cx);
 967                    })?;
 968                    anyhow::Ok(())
 969                }
 970            })
 971            .detach_and_log_err(cx);
 972        }
 973
 974        let active_thread = cx.new(|cx| {
 975            ActiveThread::new(
 976                thread.clone(),
 977                self.thread_store.clone(),
 978                self.context_store.clone(),
 979                context_store.clone(),
 980                self.language_registry.clone(),
 981                self.workspace.clone(),
 982                window,
 983                cx,
 984            )
 985        });
 986
 987        let message_editor = cx.new(|cx| {
 988            MessageEditor::new(
 989                self.fs.clone(),
 990                self.workspace.clone(),
 991                context_store.clone(),
 992                self.prompt_store.clone(),
 993                self.thread_store.downgrade(),
 994                self.context_store.downgrade(),
 995                Some(self.history_store.downgrade()),
 996                thread.clone(),
 997                window,
 998                cx,
 999            )
1000        });
1001
1002        if let Some(text) = preserved_text {
1003            message_editor.update(cx, |editor, cx| {
1004                editor.set_text(text, window, cx);
1005            });
1006        }
1007
1008        message_editor.focus_handle(cx).focus(window);
1009
1010        let thread_view = ActiveView::thread(active_thread, message_editor, window, cx);
1011        self.set_active_view(thread_view, window, cx);
1012
1013        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
1014    }
1015
1016    fn new_native_agent_thread_from_summary(
1017        &mut self,
1018        action: &NewNativeAgentThreadFromSummary,
1019        window: &mut Window,
1020        cx: &mut Context<Self>,
1021    ) {
1022        let Some(thread) = self
1023            .acp_history_store
1024            .read(cx)
1025            .thread_from_session_id(&action.from_session_id)
1026        else {
1027            return;
1028        };
1029
1030        self.external_thread(
1031            Some(ExternalAgent::NativeAgent),
1032            None,
1033            Some(thread.clone()),
1034            window,
1035            cx,
1036        );
1037    }
1038
1039    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1040        telemetry::event!("Agent Thread Started", agent = "zed-text");
1041
1042        let context = self
1043            .context_store
1044            .update(cx, |context_store, cx| context_store.create(cx));
1045        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1046            .log_err()
1047            .flatten();
1048
1049        let context_editor = cx.new(|cx| {
1050            let mut editor = TextThreadEditor::for_context(
1051                context,
1052                self.fs.clone(),
1053                self.workspace.clone(),
1054                self.project.clone(),
1055                lsp_adapter_delegate,
1056                window,
1057                cx,
1058            );
1059            editor.insert_default_prompt(window, cx);
1060            editor
1061        });
1062
1063        if self.selected_agent != AgentType::TextThread {
1064            self.selected_agent = AgentType::TextThread;
1065            self.serialize(cx);
1066        }
1067
1068        self.set_active_view(
1069            ActiveView::prompt_editor(
1070                context_editor.clone(),
1071                self.history_store.clone(),
1072                self.acp_history_store.clone(),
1073                self.language_registry.clone(),
1074                window,
1075                cx,
1076            ),
1077            window,
1078            cx,
1079        );
1080        context_editor.focus_handle(cx).focus(window);
1081    }
1082
1083    fn external_thread(
1084        &mut self,
1085        agent_choice: Option<crate::ExternalAgent>,
1086        resume_thread: Option<DbThreadMetadata>,
1087        summarize_thread: Option<DbThreadMetadata>,
1088        window: &mut Window,
1089        cx: &mut Context<Self>,
1090    ) {
1091        let workspace = self.workspace.clone();
1092        let project = self.project.clone();
1093        let fs = self.fs.clone();
1094
1095        const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
1096
1097        #[derive(Default, Serialize, Deserialize)]
1098        struct LastUsedExternalAgent {
1099            agent: crate::ExternalAgent,
1100        }
1101
1102        let history = self.acp_history_store.clone();
1103
1104        cx.spawn_in(window, async move |this, cx| {
1105            let ext_agent = match agent_choice {
1106                Some(agent) => {
1107                    cx.background_spawn({
1108                        let agent = agent.clone();
1109                        async move {
1110                            if let Some(serialized) =
1111                                serde_json::to_string(&LastUsedExternalAgent { agent }).log_err()
1112                            {
1113                                KEY_VALUE_STORE
1114                                    .write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized)
1115                                    .await
1116                                    .log_err();
1117                            }
1118                        }
1119                    })
1120                    .detach();
1121
1122                    agent
1123                }
1124                None => {
1125                    cx.background_spawn(async move {
1126                        KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
1127                    })
1128                    .await
1129                    .log_err()
1130                    .flatten()
1131                    .and_then(|value| {
1132                        serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
1133                    })
1134                    .unwrap_or_default()
1135                    .agent
1136                }
1137            };
1138
1139            telemetry::event!("Agent Thread Started", agent = ext_agent.name());
1140
1141            let server = ext_agent.server(fs, history);
1142
1143            this.update_in(cx, |this, window, cx| {
1144                match ext_agent {
1145                    crate::ExternalAgent::Gemini
1146                    | crate::ExternalAgent::NativeAgent
1147                    | crate::ExternalAgent::Custom { .. } => {
1148                        if !cx.has_flag::<GeminiAndNativeFeatureFlag>() {
1149                            return;
1150                        }
1151                    }
1152                    crate::ExternalAgent::ClaudeCode => {
1153                        if !cx.has_flag::<ClaudeCodeFeatureFlag>() {
1154                            return;
1155                        }
1156                    }
1157                }
1158
1159                let selected_agent = ext_agent.into();
1160                if this.selected_agent != selected_agent {
1161                    this.selected_agent = selected_agent;
1162                    this.serialize(cx);
1163                }
1164
1165                let thread_view = cx.new(|cx| {
1166                    crate::acp::AcpThreadView::new(
1167                        server,
1168                        resume_thread,
1169                        summarize_thread,
1170                        workspace.clone(),
1171                        project,
1172                        this.acp_history_store.clone(),
1173                        this.prompt_store.clone(),
1174                        window,
1175                        cx,
1176                    )
1177                });
1178
1179                this.set_active_view(ActiveView::ExternalAgentThread { thread_view }, window, cx);
1180            })
1181        })
1182        .detach_and_log_err(cx);
1183    }
1184
1185    fn deploy_rules_library(
1186        &mut self,
1187        action: &OpenRulesLibrary,
1188        _window: &mut Window,
1189        cx: &mut Context<Self>,
1190    ) {
1191        open_rules_library(
1192            self.language_registry.clone(),
1193            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
1194            Rc::new(|| {
1195                Rc::new(SlashCommandCompletionProvider::new(
1196                    Arc::new(SlashCommandWorkingSet::default()),
1197                    None,
1198                    None,
1199                ))
1200            }),
1201            action
1202                .prompt_to_select
1203                .map(|uuid| UserPromptId(uuid).into()),
1204            cx,
1205        )
1206        .detach_and_log_err(cx);
1207    }
1208
1209    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1210        if matches!(self.active_view, ActiveView::History) {
1211            if let Some(previous_view) = self.previous_view.take() {
1212                self.set_active_view(previous_view, window, cx);
1213            }
1214        } else {
1215            self.thread_store
1216                .update(cx, |thread_store, cx| thread_store.reload(cx))
1217                .detach_and_log_err(cx);
1218            self.set_active_view(ActiveView::History, window, cx);
1219        }
1220        cx.notify();
1221    }
1222
1223    pub(crate) fn open_saved_prompt_editor(
1224        &mut self,
1225        path: Arc<Path>,
1226        window: &mut Window,
1227        cx: &mut Context<Self>,
1228    ) -> Task<Result<()>> {
1229        let context = self
1230            .context_store
1231            .update(cx, |store, cx| store.open_local_context(path, cx));
1232        cx.spawn_in(window, async move |this, cx| {
1233            let context = context.await?;
1234            this.update_in(cx, |this, window, cx| {
1235                this.open_prompt_editor(context, window, cx);
1236            })
1237        })
1238    }
1239
1240    pub(crate) fn open_prompt_editor(
1241        &mut self,
1242        context: Entity<AssistantContext>,
1243        window: &mut Window,
1244        cx: &mut Context<Self>,
1245    ) {
1246        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
1247            .log_err()
1248            .flatten();
1249        let editor = cx.new(|cx| {
1250            TextThreadEditor::for_context(
1251                context,
1252                self.fs.clone(),
1253                self.workspace.clone(),
1254                self.project.clone(),
1255                lsp_adapter_delegate,
1256                window,
1257                cx,
1258            )
1259        });
1260
1261        if self.selected_agent != AgentType::TextThread {
1262            self.selected_agent = AgentType::TextThread;
1263            self.serialize(cx);
1264        }
1265
1266        self.set_active_view(
1267            ActiveView::prompt_editor(
1268                editor,
1269                self.history_store.clone(),
1270                self.acp_history_store.clone(),
1271                self.language_registry.clone(),
1272                window,
1273                cx,
1274            ),
1275            window,
1276            cx,
1277        );
1278    }
1279
1280    pub(crate) fn open_thread_by_id(
1281        &mut self,
1282        thread_id: &ThreadId,
1283        window: &mut Window,
1284        cx: &mut Context<Self>,
1285    ) -> Task<Result<()>> {
1286        let open_thread_task = self
1287            .thread_store
1288            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
1289        cx.spawn_in(window, async move |this, cx| {
1290            let thread = open_thread_task.await?;
1291            this.update_in(cx, |this, window, cx| {
1292                this.open_thread(thread, window, cx);
1293                anyhow::Ok(())
1294            })??;
1295            Ok(())
1296        })
1297    }
1298
1299    pub(crate) fn open_thread(
1300        &mut self,
1301        thread: Entity<Thread>,
1302        window: &mut Window,
1303        cx: &mut Context<Self>,
1304    ) {
1305        let context_store = cx.new(|_cx| {
1306            ContextStore::new(
1307                self.project.downgrade(),
1308                Some(self.thread_store.downgrade()),
1309            )
1310        });
1311
1312        let active_thread = cx.new(|cx| {
1313            ActiveThread::new(
1314                thread.clone(),
1315                self.thread_store.clone(),
1316                self.context_store.clone(),
1317                context_store.clone(),
1318                self.language_registry.clone(),
1319                self.workspace.clone(),
1320                window,
1321                cx,
1322            )
1323        });
1324
1325        let message_editor = cx.new(|cx| {
1326            MessageEditor::new(
1327                self.fs.clone(),
1328                self.workspace.clone(),
1329                context_store,
1330                self.prompt_store.clone(),
1331                self.thread_store.downgrade(),
1332                self.context_store.downgrade(),
1333                Some(self.history_store.downgrade()),
1334                thread.clone(),
1335                window,
1336                cx,
1337            )
1338        });
1339        message_editor.focus_handle(cx).focus(window);
1340
1341        let thread_view = ActiveView::thread(active_thread, message_editor, window, cx);
1342        self.set_active_view(thread_view, window, cx);
1343        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
1344    }
1345
1346    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
1347        match self.active_view {
1348            ActiveView::Configuration | ActiveView::History => {
1349                if let Some(previous_view) = self.previous_view.take() {
1350                    self.active_view = previous_view;
1351
1352                    match &self.active_view {
1353                        ActiveView::Thread { message_editor, .. } => {
1354                            message_editor.focus_handle(cx).focus(window);
1355                        }
1356                        ActiveView::ExternalAgentThread { thread_view } => {
1357                            thread_view.focus_handle(cx).focus(window);
1358                        }
1359                        ActiveView::TextThread { context_editor, .. } => {
1360                            context_editor.focus_handle(cx).focus(window);
1361                        }
1362                        ActiveView::History | ActiveView::Configuration => {}
1363                    }
1364                }
1365                cx.notify();
1366            }
1367            _ => {}
1368        }
1369    }
1370
1371    pub fn toggle_navigation_menu(
1372        &mut self,
1373        _: &ToggleNavigationMenu,
1374        window: &mut Window,
1375        cx: &mut Context<Self>,
1376    ) {
1377        self.assistant_navigation_menu_handle.toggle(window, cx);
1378    }
1379
1380    pub fn toggle_options_menu(
1381        &mut self,
1382        _: &ToggleOptionsMenu,
1383        window: &mut Window,
1384        cx: &mut Context<Self>,
1385    ) {
1386        self.agent_panel_menu_handle.toggle(window, cx);
1387    }
1388
1389    pub fn toggle_new_thread_menu(
1390        &mut self,
1391        _: &ToggleNewThreadMenu,
1392        window: &mut Window,
1393        cx: &mut Context<Self>,
1394    ) {
1395        self.new_thread_menu_handle.toggle(window, cx);
1396    }
1397
1398    pub fn increase_font_size(
1399        &mut self,
1400        action: &IncreaseBufferFontSize,
1401        _: &mut Window,
1402        cx: &mut Context<Self>,
1403    ) {
1404        self.handle_font_size_action(action.persist, px(1.0), cx);
1405    }
1406
1407    pub fn decrease_font_size(
1408        &mut self,
1409        action: &DecreaseBufferFontSize,
1410        _: &mut Window,
1411        cx: &mut Context<Self>,
1412    ) {
1413        self.handle_font_size_action(action.persist, px(-1.0), cx);
1414    }
1415
1416    fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
1417        match self.active_view.which_font_size_used() {
1418            WhichFontSize::AgentFont => {
1419                if persist {
1420                    update_settings_file::<ThemeSettings>(
1421                        self.fs.clone(),
1422                        cx,
1423                        move |settings, cx| {
1424                            let agent_font_size =
1425                                ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
1426                            let _ = settings
1427                                .agent_font_size
1428                                .insert(Some(theme::clamp_font_size(agent_font_size).into()));
1429                        },
1430                    );
1431                } else {
1432                    theme::adjust_agent_font_size(cx, |size| size + delta);
1433                }
1434            }
1435            WhichFontSize::BufferFont => {
1436                // Prompt editor uses the buffer font size, so allow the action to propagate to the
1437                // default handler that changes that font size.
1438                cx.propagate();
1439            }
1440            WhichFontSize::None => {}
1441        }
1442    }
1443
1444    pub fn reset_font_size(
1445        &mut self,
1446        action: &ResetBufferFontSize,
1447        _: &mut Window,
1448        cx: &mut Context<Self>,
1449    ) {
1450        if action.persist {
1451            update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
1452                settings.agent_font_size = None;
1453            });
1454        } else {
1455            theme::reset_agent_font_size(cx);
1456        }
1457    }
1458
1459    pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
1460        if self.zoomed {
1461            cx.emit(PanelEvent::ZoomOut);
1462        } else {
1463            if !self.focus_handle(cx).contains_focused(window, cx) {
1464                cx.focus_self(window);
1465            }
1466            cx.emit(PanelEvent::ZoomIn);
1467        }
1468    }
1469
1470    pub fn open_agent_diff(
1471        &mut self,
1472        _: &OpenAgentDiff,
1473        window: &mut Window,
1474        cx: &mut Context<Self>,
1475    ) {
1476        match &self.active_view {
1477            ActiveView::Thread { thread, .. } => {
1478                let thread = thread.read(cx).thread().clone();
1479                self.workspace
1480                    .update(cx, |workspace, cx| {
1481                        AgentDiffPane::deploy_in_workspace(
1482                            AgentDiffThread::Native(thread),
1483                            workspace,
1484                            window,
1485                            cx,
1486                        )
1487                    })
1488                    .log_err();
1489            }
1490            ActiveView::ExternalAgentThread { .. }
1491            | ActiveView::TextThread { .. }
1492            | ActiveView::History
1493            | ActiveView::Configuration => {}
1494        }
1495    }
1496
1497    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1498        let context_server_store = self.project.read(cx).context_server_store();
1499        let tools = self.thread_store.read(cx).tools();
1500        let fs = self.fs.clone();
1501
1502        self.set_active_view(ActiveView::Configuration, window, cx);
1503        self.configuration = Some(cx.new(|cx| {
1504            AgentConfiguration::new(
1505                fs,
1506                context_server_store,
1507                tools,
1508                self.language_registry.clone(),
1509                self.workspace.clone(),
1510                window,
1511                cx,
1512            )
1513        }));
1514
1515        if let Some(configuration) = self.configuration.as_ref() {
1516            self.configuration_subscription = Some(cx.subscribe_in(
1517                configuration,
1518                window,
1519                Self::handle_agent_configuration_event,
1520            ));
1521
1522            configuration.focus_handle(cx).focus(window);
1523        }
1524    }
1525
1526    pub(crate) fn open_active_thread_as_markdown(
1527        &mut self,
1528        _: &OpenActiveThreadAsMarkdown,
1529        window: &mut Window,
1530        cx: &mut Context<Self>,
1531    ) {
1532        let Some(workspace) = self.workspace.upgrade() else {
1533            return;
1534        };
1535
1536        match &self.active_view {
1537            ActiveView::Thread { thread, .. } => {
1538                active_thread::open_active_thread_as_markdown(
1539                    thread.read(cx).thread().clone(),
1540                    workspace,
1541                    window,
1542                    cx,
1543                )
1544                .detach_and_log_err(cx);
1545            }
1546            ActiveView::ExternalAgentThread { thread_view } => {
1547                thread_view
1548                    .update(cx, |thread_view, cx| {
1549                        thread_view.open_thread_as_markdown(workspace, window, cx)
1550                    })
1551                    .detach_and_log_err(cx);
1552            }
1553            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
1554        }
1555    }
1556
1557    fn handle_agent_configuration_event(
1558        &mut self,
1559        _entity: &Entity<AgentConfiguration>,
1560        event: &AssistantConfigurationEvent,
1561        window: &mut Window,
1562        cx: &mut Context<Self>,
1563    ) {
1564        match event {
1565            AssistantConfigurationEvent::NewThread(provider) => {
1566                if LanguageModelRegistry::read_global(cx)
1567                    .default_model()
1568                    .is_none_or(|model| model.provider.id() != provider.id())
1569                    && let Some(model) = provider.default_model(cx)
1570                {
1571                    update_settings_file::<AgentSettings>(
1572                        self.fs.clone(),
1573                        cx,
1574                        move |settings, _| settings.set_model(model),
1575                    );
1576                }
1577
1578                self.new_thread(&NewThread::default(), window, cx);
1579                if let Some((thread, model)) =
1580                    self.active_thread(cx).zip(provider.default_model(cx))
1581                {
1582                    thread.update(cx, |thread, cx| {
1583                        thread.set_configured_model(
1584                            Some(ConfiguredModel {
1585                                provider: provider.clone(),
1586                                model,
1587                            }),
1588                            cx,
1589                        );
1590                    });
1591                }
1592            }
1593        }
1594    }
1595
1596    pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
1597        match &self.active_view {
1598            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
1599            _ => None,
1600        }
1601    }
1602    pub(crate) fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
1603        match &self.active_view {
1604            ActiveView::ExternalAgentThread { thread_view, .. } => {
1605                thread_view.read(cx).thread().cloned()
1606            }
1607            _ => None,
1608        }
1609    }
1610
1611    pub(crate) fn delete_thread(
1612        &mut self,
1613        thread_id: &ThreadId,
1614        cx: &mut Context<Self>,
1615    ) -> Task<Result<()>> {
1616        self.thread_store
1617            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1618    }
1619
1620    fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1621        let ActiveView::Thread { thread, .. } = &self.active_view else {
1622            return;
1623        };
1624
1625        let thread_state = thread.read(cx).thread().read(cx);
1626        if !thread_state.tool_use_limit_reached() {
1627            return;
1628        }
1629
1630        let model = thread_state.configured_model().map(|cm| cm.model);
1631        if let Some(model) = model {
1632            thread.update(cx, |active_thread, cx| {
1633                active_thread.thread().update(cx, |thread, cx| {
1634                    thread.insert_invisible_continue_message(cx);
1635                    thread.advance_prompt_id();
1636                    thread.send_to_model(
1637                        model,
1638                        CompletionIntent::UserPrompt,
1639                        Some(window.window_handle()),
1640                        cx,
1641                    );
1642                });
1643            });
1644        } else {
1645            log::warn!("No configured model available for continuation");
1646        }
1647    }
1648
1649    fn toggle_burn_mode(
1650        &mut self,
1651        _: &ToggleBurnMode,
1652        _window: &mut Window,
1653        cx: &mut Context<Self>,
1654    ) {
1655        let ActiveView::Thread { thread, .. } = &self.active_view else {
1656            return;
1657        };
1658
1659        thread.update(cx, |active_thread, cx| {
1660            active_thread.thread().update(cx, |thread, _cx| {
1661                let current_mode = thread.completion_mode();
1662
1663                thread.set_completion_mode(match current_mode {
1664                    CompletionMode::Burn => CompletionMode::Normal,
1665                    CompletionMode::Normal => CompletionMode::Burn,
1666                });
1667            });
1668        });
1669    }
1670
1671    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
1672        match &self.active_view {
1673            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
1674            _ => None,
1675        }
1676    }
1677
1678    pub(crate) fn delete_context(
1679        &mut self,
1680        path: Arc<Path>,
1681        cx: &mut Context<Self>,
1682    ) -> Task<Result<()>> {
1683        self.context_store
1684            .update(cx, |this, cx| this.delete_local_context(path, cx))
1685    }
1686
1687    fn set_active_view(
1688        &mut self,
1689        new_view: ActiveView,
1690        window: &mut Window,
1691        cx: &mut Context<Self>,
1692    ) {
1693        let current_is_history = matches!(self.active_view, ActiveView::History);
1694        let new_is_history = matches!(new_view, ActiveView::History);
1695
1696        let current_is_config = matches!(self.active_view, ActiveView::Configuration);
1697        let new_is_config = matches!(new_view, ActiveView::Configuration);
1698
1699        let current_is_special = current_is_history || current_is_config;
1700        let new_is_special = new_is_history || new_is_config;
1701
1702        if let ActiveView::Thread { thread, .. } = &self.active_view {
1703            let thread = thread.read(cx);
1704            if thread.is_empty() {
1705                let id = thread.thread().read(cx).id().clone();
1706                self.history_store.update(cx, |store, cx| {
1707                    store.remove_recently_opened_thread(id, cx);
1708                });
1709            }
1710        }
1711
1712        match &new_view {
1713            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1714                let id = thread.read(cx).thread().read(cx).id().clone();
1715                store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
1716            }),
1717            ActiveView::TextThread { context_editor, .. } => {
1718                self.history_store.update(cx, |store, cx| {
1719                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1720                        store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
1721                    }
1722                });
1723                self.acp_history_store.update(cx, |store, cx| {
1724                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1725                        store.push_recently_opened_entry(
1726                            agent2::HistoryEntryId::TextThread(path.clone()),
1727                            cx,
1728                        )
1729                    }
1730                })
1731            }
1732            ActiveView::ExternalAgentThread { .. } => {}
1733            ActiveView::History | ActiveView::Configuration => {}
1734        }
1735
1736        if current_is_special && !new_is_special {
1737            self.active_view = new_view;
1738        } else if !current_is_special && new_is_special {
1739            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1740        } else {
1741            if !new_is_special {
1742                self.previous_view = None;
1743            }
1744            self.active_view = new_view;
1745        }
1746
1747        self.focus_handle(cx).focus(window);
1748    }
1749
1750    fn populate_recently_opened_menu_section_old(
1751        mut menu: ContextMenu,
1752        panel: Entity<Self>,
1753        cx: &mut Context<ContextMenu>,
1754    ) -> ContextMenu {
1755        let entries = panel
1756            .read(cx)
1757            .history_store
1758            .read(cx)
1759            .recently_opened_entries(cx);
1760
1761        if entries.is_empty() {
1762            return menu;
1763        }
1764
1765        menu = menu.header("Recently Opened");
1766
1767        for entry in entries {
1768            let title = entry.title().clone();
1769            let id = entry.id();
1770
1771            menu = menu.entry_with_end_slot_on_hover(
1772                title,
1773                None,
1774                {
1775                    let panel = panel.downgrade();
1776                    let id = id.clone();
1777                    move |window, cx| {
1778                        let id = id.clone();
1779                        panel
1780                            .update(cx, move |this, cx| match id {
1781                                HistoryEntryId::Thread(id) => this
1782                                    .open_thread_by_id(&id, window, cx)
1783                                    .detach_and_log_err(cx),
1784                                HistoryEntryId::Context(path) => this
1785                                    .open_saved_prompt_editor(path, window, cx)
1786                                    .detach_and_log_err(cx),
1787                            })
1788                            .ok();
1789                    }
1790                },
1791                IconName::Close,
1792                "Close Entry".into(),
1793                {
1794                    let panel = panel.downgrade();
1795                    let id = id.clone();
1796                    move |_window, cx| {
1797                        panel
1798                            .update(cx, |this, cx| {
1799                                this.history_store.update(cx, |history_store, cx| {
1800                                    history_store.remove_recently_opened_entry(&id, cx);
1801                                });
1802                            })
1803                            .ok();
1804                    }
1805                },
1806            );
1807        }
1808
1809        menu = menu.separator();
1810
1811        menu
1812    }
1813
1814    fn populate_recently_opened_menu_section_new(
1815        mut menu: ContextMenu,
1816        panel: Entity<Self>,
1817        cx: &mut Context<ContextMenu>,
1818    ) -> ContextMenu {
1819        let entries = panel
1820            .read(cx)
1821            .acp_history_store
1822            .read(cx)
1823            .recently_opened_entries(cx);
1824
1825        if entries.is_empty() {
1826            return menu;
1827        }
1828
1829        menu = menu.header("Recently Opened");
1830
1831        for entry in entries {
1832            let title = entry.title().clone();
1833
1834            menu = menu.entry_with_end_slot_on_hover(
1835                title,
1836                None,
1837                {
1838                    let panel = panel.downgrade();
1839                    let entry = entry.clone();
1840                    move |window, cx| {
1841                        let entry = entry.clone();
1842                        panel
1843                            .update(cx, move |this, cx| match &entry {
1844                                agent2::HistoryEntry::AcpThread(entry) => this.external_thread(
1845                                    Some(ExternalAgent::NativeAgent),
1846                                    Some(entry.clone()),
1847                                    None,
1848                                    window,
1849                                    cx,
1850                                ),
1851                                agent2::HistoryEntry::TextThread(entry) => this
1852                                    .open_saved_prompt_editor(entry.path.clone(), window, cx)
1853                                    .detach_and_log_err(cx),
1854                            })
1855                            .ok();
1856                    }
1857                },
1858                IconName::Close,
1859                "Close Entry".into(),
1860                {
1861                    let panel = panel.downgrade();
1862                    let id = entry.id();
1863                    move |_window, cx| {
1864                        panel
1865                            .update(cx, |this, cx| {
1866                                this.acp_history_store.update(cx, |history_store, cx| {
1867                                    history_store.remove_recently_opened_entry(&id, cx);
1868                                });
1869                            })
1870                            .ok();
1871                    }
1872                },
1873            );
1874        }
1875
1876        menu = menu.separator();
1877
1878        menu
1879    }
1880
1881    pub fn selected_agent(&self) -> AgentType {
1882        self.selected_agent.clone()
1883    }
1884
1885    pub fn new_agent_thread(
1886        &mut self,
1887        agent: AgentType,
1888        window: &mut Window,
1889        cx: &mut Context<Self>,
1890    ) {
1891        match agent {
1892            AgentType::Zed => {
1893                window.dispatch_action(
1894                    NewThread {
1895                        from_thread_id: None,
1896                    }
1897                    .boxed_clone(),
1898                    cx,
1899                );
1900            }
1901            AgentType::TextThread => {
1902                window.dispatch_action(NewTextThread.boxed_clone(), cx);
1903            }
1904            AgentType::NativeAgent => self.external_thread(
1905                Some(crate::ExternalAgent::NativeAgent),
1906                None,
1907                None,
1908                window,
1909                cx,
1910            ),
1911            AgentType::Gemini => {
1912                self.external_thread(Some(crate::ExternalAgent::Gemini), None, None, window, cx)
1913            }
1914            AgentType::ClaudeCode => self.external_thread(
1915                Some(crate::ExternalAgent::ClaudeCode),
1916                None,
1917                None,
1918                window,
1919                cx,
1920            ),
1921            AgentType::Custom { name, command } => self.external_thread(
1922                Some(crate::ExternalAgent::Custom { name, command }),
1923                None,
1924                None,
1925                window,
1926                cx,
1927            ),
1928        }
1929    }
1930
1931    pub fn load_agent_thread(
1932        &mut self,
1933        thread: DbThreadMetadata,
1934        window: &mut Window,
1935        cx: &mut Context<Self>,
1936    ) {
1937        self.external_thread(
1938            Some(ExternalAgent::NativeAgent),
1939            Some(thread),
1940            None,
1941            window,
1942            cx,
1943        );
1944    }
1945}
1946
1947impl Focusable for AgentPanel {
1948    fn focus_handle(&self, cx: &App) -> FocusHandle {
1949        match &self.active_view {
1950            ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
1951            ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
1952            ActiveView::History => {
1953                if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>() {
1954                    self.acp_history.focus_handle(cx)
1955                } else {
1956                    self.history.focus_handle(cx)
1957                }
1958            }
1959            ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
1960            ActiveView::Configuration => {
1961                if let Some(configuration) = self.configuration.as_ref() {
1962                    configuration.focus_handle(cx)
1963                } else {
1964                    cx.focus_handle()
1965                }
1966            }
1967        }
1968    }
1969}
1970
1971fn agent_panel_dock_position(cx: &App) -> DockPosition {
1972    match AgentSettings::get_global(cx).dock {
1973        AgentDockPosition::Left => DockPosition::Left,
1974        AgentDockPosition::Bottom => DockPosition::Bottom,
1975        AgentDockPosition::Right => DockPosition::Right,
1976    }
1977}
1978
1979impl EventEmitter<PanelEvent> for AgentPanel {}
1980
1981impl Panel for AgentPanel {
1982    fn persistent_name() -> &'static str {
1983        "AgentPanel"
1984    }
1985
1986    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1987        agent_panel_dock_position(cx)
1988    }
1989
1990    fn position_is_valid(&self, position: DockPosition) -> bool {
1991        position != DockPosition::Bottom
1992    }
1993
1994    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1995        settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
1996            let dock = match position {
1997                DockPosition::Left => AgentDockPosition::Left,
1998                DockPosition::Bottom => AgentDockPosition::Bottom,
1999                DockPosition::Right => AgentDockPosition::Right,
2000            };
2001            settings.set_dock(dock);
2002        });
2003    }
2004
2005    fn size(&self, window: &Window, cx: &App) -> Pixels {
2006        let settings = AgentSettings::get_global(cx);
2007        match self.position(window, cx) {
2008            DockPosition::Left | DockPosition::Right => {
2009                self.width.unwrap_or(settings.default_width)
2010            }
2011            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
2012        }
2013    }
2014
2015    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
2016        match self.position(window, cx) {
2017            DockPosition::Left | DockPosition::Right => self.width = size,
2018            DockPosition::Bottom => self.height = size,
2019        }
2020        self.serialize(cx);
2021        cx.notify();
2022    }
2023
2024    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
2025
2026    fn remote_id() -> Option<proto::PanelId> {
2027        Some(proto::PanelId::AssistantPanel)
2028    }
2029
2030    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
2031        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
2032    }
2033
2034    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
2035        Some("Agent Panel")
2036    }
2037
2038    fn toggle_action(&self) -> Box<dyn Action> {
2039        Box::new(ToggleFocus)
2040    }
2041
2042    fn activation_priority(&self) -> u32 {
2043        3
2044    }
2045
2046    fn enabled(&self, cx: &App) -> bool {
2047        DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
2048    }
2049
2050    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
2051        self.zoomed
2052    }
2053
2054    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
2055        self.zoomed = zoomed;
2056        cx.notify();
2057    }
2058}
2059
2060impl AgentPanel {
2061    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
2062        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
2063
2064        let content = match &self.active_view {
2065            ActiveView::Thread {
2066                thread: active_thread,
2067                change_title_editor,
2068                ..
2069            } => {
2070                let state = {
2071                    let active_thread = active_thread.read(cx);
2072                    if active_thread.is_empty() {
2073                        &ThreadSummary::Pending
2074                    } else {
2075                        active_thread.summary(cx)
2076                    }
2077                };
2078
2079                match state {
2080                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT)
2081                        .truncate()
2082                        .color(Color::Muted)
2083                        .into_any_element(),
2084                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
2085                        .truncate()
2086                        .color(Color::Muted)
2087                        .into_any_element(),
2088                    ThreadSummary::Ready(_) => div()
2089                        .w_full()
2090                        .child(change_title_editor.clone())
2091                        .into_any_element(),
2092                    ThreadSummary::Error => h_flex()
2093                        .w_full()
2094                        .child(change_title_editor.clone())
2095                        .child(
2096                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
2097                                .icon_size(IconSize::Small)
2098                                .on_click({
2099                                    let active_thread = active_thread.clone();
2100                                    move |_, _window, cx| {
2101                                        active_thread.update(cx, |thread, cx| {
2102                                            thread.regenerate_summary(cx);
2103                                        });
2104                                    }
2105                                })
2106                                .tooltip(move |_window, cx| {
2107                                    cx.new(|_| {
2108                                        Tooltip::new("Failed to generate title")
2109                                            .meta("Click to try again")
2110                                    })
2111                                    .into()
2112                                }),
2113                        )
2114                        .into_any_element(),
2115                }
2116            }
2117            ActiveView::ExternalAgentThread { thread_view } => {
2118                if let Some(title_editor) = thread_view.read(cx).title_editor() {
2119                    div()
2120                        .w_full()
2121                        .on_action({
2122                            let thread_view = thread_view.downgrade();
2123                            move |_: &menu::Confirm, window, cx| {
2124                                if let Some(thread_view) = thread_view.upgrade() {
2125                                    thread_view.focus_handle(cx).focus(window);
2126                                }
2127                            }
2128                        })
2129                        .on_action({
2130                            let thread_view = thread_view.downgrade();
2131                            move |_: &editor::actions::Cancel, window, cx| {
2132                                if let Some(thread_view) = thread_view.upgrade() {
2133                                    thread_view.focus_handle(cx).focus(window);
2134                                }
2135                            }
2136                        })
2137                        .child(title_editor)
2138                        .into_any_element()
2139                } else {
2140                    Label::new(thread_view.read(cx).title(cx))
2141                        .color(Color::Muted)
2142                        .truncate()
2143                        .into_any_element()
2144                }
2145            }
2146            ActiveView::TextThread {
2147                title_editor,
2148                context_editor,
2149                ..
2150            } => {
2151                let summary = context_editor.read(cx).context().read(cx).summary();
2152
2153                match summary {
2154                    ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
2155                        .color(Color::Muted)
2156                        .truncate()
2157                        .into_any_element(),
2158                    ContextSummary::Content(summary) => {
2159                        if summary.done {
2160                            div()
2161                                .w_full()
2162                                .child(title_editor.clone())
2163                                .into_any_element()
2164                        } else {
2165                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
2166                                .truncate()
2167                                .color(Color::Muted)
2168                                .into_any_element()
2169                        }
2170                    }
2171                    ContextSummary::Error => h_flex()
2172                        .w_full()
2173                        .child(title_editor.clone())
2174                        .child(
2175                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
2176                                .icon_size(IconSize::Small)
2177                                .on_click({
2178                                    let context_editor = context_editor.clone();
2179                                    move |_, _window, cx| {
2180                                        context_editor.update(cx, |context_editor, cx| {
2181                                            context_editor.regenerate_summary(cx);
2182                                        });
2183                                    }
2184                                })
2185                                .tooltip(move |_window, cx| {
2186                                    cx.new(|_| {
2187                                        Tooltip::new("Failed to generate title")
2188                                            .meta("Click to try again")
2189                                    })
2190                                    .into()
2191                                }),
2192                        )
2193                        .into_any_element(),
2194                }
2195            }
2196            ActiveView::History => Label::new("History").truncate().into_any_element(),
2197            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
2198        };
2199
2200        h_flex()
2201            .key_context("TitleEditor")
2202            .id("TitleEditor")
2203            .flex_grow()
2204            .w_full()
2205            .max_w_full()
2206            .overflow_x_scroll()
2207            .child(content)
2208            .into_any()
2209    }
2210
2211    fn render_panel_options_menu(
2212        &self,
2213        window: &mut Window,
2214        cx: &mut Context<Self>,
2215    ) -> impl IntoElement {
2216        let user_store = self.user_store.read(cx);
2217        let usage = user_store.model_request_usage();
2218        let account_url = zed_urls::account_url(cx);
2219
2220        let focus_handle = self.focus_handle(cx);
2221
2222        let full_screen_label = if self.is_zoomed(window, cx) {
2223            "Disable Full Screen"
2224        } else {
2225            "Enable Full Screen"
2226        };
2227
2228        let selected_agent = self.selected_agent.clone();
2229
2230        PopoverMenu::new("agent-options-menu")
2231            .trigger_with_tooltip(
2232                IconButton::new("agent-options-menu", IconName::Ellipsis)
2233                    .icon_size(IconSize::Small),
2234                {
2235                    let focus_handle = focus_handle.clone();
2236                    move |window, cx| {
2237                        Tooltip::for_action_in(
2238                            "Toggle Agent Menu",
2239                            &ToggleOptionsMenu,
2240                            &focus_handle,
2241                            window,
2242                            cx,
2243                        )
2244                    }
2245                },
2246            )
2247            .anchor(Corner::TopRight)
2248            .with_handle(self.agent_panel_menu_handle.clone())
2249            .menu({
2250                move |window, cx| {
2251                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
2252                        menu = menu.context(focus_handle.clone());
2253                        if let Some(usage) = usage {
2254                            menu = menu
2255                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
2256                                .custom_entry(
2257                                    move |_window, cx| {
2258                                        let used_percentage = match usage.limit {
2259                                            UsageLimit::Limited(limit) => {
2260                                                Some((usage.amount as f32 / limit as f32) * 100.)
2261                                            }
2262                                            UsageLimit::Unlimited => None,
2263                                        };
2264
2265                                        h_flex()
2266                                            .flex_1()
2267                                            .gap_1p5()
2268                                            .children(used_percentage.map(|percent| {
2269                                                ProgressBar::new("usage", percent, 100., cx)
2270                                            }))
2271                                            .child(
2272                                                Label::new(match usage.limit {
2273                                                    UsageLimit::Limited(limit) => {
2274                                                        format!("{} / {limit}", usage.amount)
2275                                                    }
2276                                                    UsageLimit::Unlimited => {
2277                                                        format!("{} / ∞", usage.amount)
2278                                                    }
2279                                                })
2280                                                .size(LabelSize::Small)
2281                                                .color(Color::Muted),
2282                                            )
2283                                            .into_any_element()
2284                                    },
2285                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
2286                                )
2287                                .separator()
2288                        }
2289
2290                        menu = menu
2291                            .header("MCP Servers")
2292                            .action(
2293                                "View Server Extensions",
2294                                Box::new(zed_actions::Extensions {
2295                                    category_filter: Some(
2296                                        zed_actions::ExtensionCategoryFilter::ContextServers,
2297                                    ),
2298                                    id: None,
2299                                }),
2300                            )
2301                            .action("Add Custom Server…", Box::new(AddContextServer))
2302                            .separator();
2303
2304                        menu = menu
2305                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
2306                            .action("Settings", Box::new(OpenSettings))
2307                            .separator()
2308                            .action(full_screen_label, Box::new(ToggleZoom));
2309
2310                        if selected_agent == AgentType::Gemini {
2311                            menu = menu.action("Reauthenticate", Box::new(ReauthenticateAgent))
2312                        }
2313
2314                        menu
2315                    }))
2316                }
2317            })
2318    }
2319
2320    fn render_recent_entries_menu(
2321        &self,
2322        icon: IconName,
2323        corner: Corner,
2324        cx: &mut Context<Self>,
2325    ) -> impl IntoElement {
2326        let focus_handle = self.focus_handle(cx);
2327
2328        PopoverMenu::new("agent-nav-menu")
2329            .trigger_with_tooltip(
2330                IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
2331                {
2332                    move |window, cx| {
2333                        Tooltip::for_action_in(
2334                            "Toggle Recent Threads",
2335                            &ToggleNavigationMenu,
2336                            &focus_handle,
2337                            window,
2338                            cx,
2339                        )
2340                    }
2341                },
2342            )
2343            .anchor(corner)
2344            .with_handle(self.assistant_navigation_menu_handle.clone())
2345            .menu({
2346                let menu = self.assistant_navigation_menu.clone();
2347                move |window, cx| {
2348                    telemetry::event!("View Thread History Clicked");
2349
2350                    if let Some(menu) = menu.as_ref() {
2351                        menu.update(cx, |_, cx| {
2352                            cx.defer_in(window, |menu, window, cx| {
2353                                menu.rebuild(window, cx);
2354                            });
2355                        })
2356                    }
2357                    menu.clone()
2358                }
2359            })
2360    }
2361
2362    fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
2363        let focus_handle = self.focus_handle(cx);
2364
2365        IconButton::new("go-back", IconName::ArrowLeft)
2366            .icon_size(IconSize::Small)
2367            .on_click(cx.listener(|this, _, window, cx| {
2368                this.go_back(&workspace::GoBack, window, cx);
2369            }))
2370            .tooltip({
2371                move |window, cx| {
2372                    Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
2373                }
2374            })
2375    }
2376
2377    fn render_toolbar_old(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2378        let focus_handle = self.focus_handle(cx);
2379
2380        let active_thread = match &self.active_view {
2381            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
2382            ActiveView::ExternalAgentThread { .. }
2383            | ActiveView::TextThread { .. }
2384            | ActiveView::History
2385            | ActiveView::Configuration => None,
2386        };
2387
2388        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2389            .trigger_with_tooltip(
2390                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2391                Tooltip::text("New Thread…"),
2392            )
2393            .anchor(Corner::TopRight)
2394            .with_handle(self.new_thread_menu_handle.clone())
2395            .menu({
2396                move |window, cx| {
2397                    let active_thread = active_thread.clone();
2398                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2399                        menu = menu
2400                            .context(focus_handle.clone())
2401                            .when_some(active_thread, |this, active_thread| {
2402                                let thread = active_thread.read(cx);
2403
2404                                if !thread.is_empty() {
2405                                    let thread_id = thread.id().clone();
2406                                    this.item(
2407                                        ContextMenuEntry::new("New From Summary")
2408                                            .icon(IconName::ThreadFromSummary)
2409                                            .icon_color(Color::Muted)
2410                                            .handler(move |window, cx| {
2411                                                window.dispatch_action(
2412                                                    Box::new(NewThread {
2413                                                        from_thread_id: Some(thread_id.clone()),
2414                                                    }),
2415                                                    cx,
2416                                                );
2417                                            }),
2418                                    )
2419                                } else {
2420                                    this
2421                                }
2422                            })
2423                            .item(
2424                                ContextMenuEntry::new("New Thread")
2425                                    .icon(IconName::Thread)
2426                                    .icon_color(Color::Muted)
2427                                    .action(NewThread::default().boxed_clone())
2428                                    .handler(move |window, cx| {
2429                                        window.dispatch_action(
2430                                            NewThread::default().boxed_clone(),
2431                                            cx,
2432                                        );
2433                                    }),
2434                            )
2435                            .item(
2436                                ContextMenuEntry::new("New Text Thread")
2437                                    .icon(IconName::TextThread)
2438                                    .icon_color(Color::Muted)
2439                                    .action(NewTextThread.boxed_clone())
2440                                    .handler(move |window, cx| {
2441                                        window.dispatch_action(NewTextThread.boxed_clone(), cx);
2442                                    }),
2443                            );
2444                        menu
2445                    }))
2446                }
2447            });
2448
2449        h_flex()
2450            .id("assistant-toolbar")
2451            .h(Tab::container_height(cx))
2452            .max_w_full()
2453            .flex_none()
2454            .justify_between()
2455            .gap_2()
2456            .bg(cx.theme().colors().tab_bar_background)
2457            .border_b_1()
2458            .border_color(cx.theme().colors().border)
2459            .child(
2460                h_flex()
2461                    .size_full()
2462                    .pl_1()
2463                    .gap_1()
2464                    .child(match &self.active_view {
2465                        ActiveView::History | ActiveView::Configuration => div()
2466                            .pl(DynamicSpacing::Base04.rems(cx))
2467                            .child(self.render_toolbar_back_button(cx))
2468                            .into_any_element(),
2469                        _ => self
2470                            .render_recent_entries_menu(IconName::MenuAlt, Corner::TopLeft, cx)
2471                            .into_any_element(),
2472                    })
2473                    .child(self.render_title_view(window, cx)),
2474            )
2475            .child(
2476                h_flex()
2477                    .h_full()
2478                    .gap_2()
2479                    .children(self.render_token_count(cx))
2480                    .child(
2481                        h_flex()
2482                            .h_full()
2483                            .gap(DynamicSpacing::Base02.rems(cx))
2484                            .px(DynamicSpacing::Base08.rems(cx))
2485                            .border_l_1()
2486                            .border_color(cx.theme().colors().border)
2487                            .child(new_thread_menu)
2488                            .child(self.render_panel_options_menu(window, cx)),
2489                    ),
2490            )
2491    }
2492
2493    fn render_toolbar_new(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2494        let focus_handle = self.focus_handle(cx);
2495
2496        let active_thread = match &self.active_view {
2497            ActiveView::ExternalAgentThread { thread_view } => {
2498                thread_view.read(cx).as_native_thread(cx)
2499            }
2500            ActiveView::Thread { .. }
2501            | ActiveView::TextThread { .. }
2502            | ActiveView::History
2503            | ActiveView::Configuration => None,
2504        };
2505
2506        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2507            .trigger_with_tooltip(
2508                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2509                {
2510                    let focus_handle = focus_handle.clone();
2511                    move |window, cx| {
2512                        Tooltip::for_action_in(
2513                            "New…",
2514                            &ToggleNewThreadMenu,
2515                            &focus_handle,
2516                            window,
2517                            cx,
2518                        )
2519                    }
2520                },
2521            )
2522            .anchor(Corner::TopLeft)
2523            .with_handle(self.new_thread_menu_handle.clone())
2524            .menu({
2525                let workspace = self.workspace.clone();
2526
2527                move |window, cx| {
2528                    telemetry::event!("New Thread Clicked");
2529
2530                    let active_thread = active_thread.clone();
2531                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2532                        menu = menu
2533                            .context(focus_handle.clone())
2534                            .header("Zed Agent")
2535                            .when_some(active_thread, |this, active_thread| {
2536                                let thread = active_thread.read(cx);
2537
2538                                if !thread.is_empty() {
2539                                    let session_id = thread.id().clone();
2540                                    this.item(
2541                                        ContextMenuEntry::new("New From Summary")
2542                                            .icon(IconName::ThreadFromSummary)
2543                                            .icon_color(Color::Muted)
2544                                            .handler(move |window, cx| {
2545                                                window.dispatch_action(
2546                                                    Box::new(NewNativeAgentThreadFromSummary {
2547                                                        from_session_id: session_id.clone(),
2548                                                    }),
2549                                                    cx,
2550                                                );
2551                                            }),
2552                                    )
2553                                } else {
2554                                    this
2555                                }
2556                            })
2557                            .item(
2558                                ContextMenuEntry::new("New Thread")
2559                                    .action(NewThread::default().boxed_clone())
2560                                    .icon(IconName::Thread)
2561                                    .icon_color(Color::Muted)
2562                                    .handler({
2563                                        let workspace = workspace.clone();
2564                                        move |window, cx| {
2565                                            if let Some(workspace) = workspace.upgrade() {
2566                                                workspace.update(cx, |workspace, cx| {
2567                                                    if let Some(panel) =
2568                                                        workspace.panel::<AgentPanel>(cx)
2569                                                    {
2570                                                        panel.update(cx, |panel, cx| {
2571                                                            panel.new_agent_thread(
2572                                                                AgentType::NativeAgent,
2573                                                                window,
2574                                                                cx,
2575                                                            );
2576                                                        });
2577                                                    }
2578                                                });
2579                                            }
2580                                        }
2581                                    }),
2582                            )
2583                            .item(
2584                                ContextMenuEntry::new("New Text Thread")
2585                                    .icon(IconName::TextThread)
2586                                    .icon_color(Color::Muted)
2587                                    .action(NewTextThread.boxed_clone())
2588                                    .handler({
2589                                        let workspace = workspace.clone();
2590                                        move |window, cx| {
2591                                            if let Some(workspace) = workspace.upgrade() {
2592                                                workspace.update(cx, |workspace, cx| {
2593                                                    if let Some(panel) =
2594                                                        workspace.panel::<AgentPanel>(cx)
2595                                                    {
2596                                                        panel.update(cx, |panel, cx| {
2597                                                            panel.new_agent_thread(
2598                                                                AgentType::TextThread,
2599                                                                window,
2600                                                                cx,
2601                                                            );
2602                                                        });
2603                                                    }
2604                                                });
2605                                            }
2606                                        }
2607                                    }),
2608                            )
2609                            .separator()
2610                            .header("External Agents")
2611                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |menu| {
2612                                menu.item(
2613                                    ContextMenuEntry::new("New Gemini CLI Thread")
2614                                        .icon(IconName::AiGemini)
2615                                        .icon_color(Color::Muted)
2616                                        .handler({
2617                                            let workspace = workspace.clone();
2618                                            move |window, cx| {
2619                                                if let Some(workspace) = workspace.upgrade() {
2620                                                    workspace.update(cx, |workspace, cx| {
2621                                                        if let Some(panel) =
2622                                                            workspace.panel::<AgentPanel>(cx)
2623                                                        {
2624                                                            panel.update(cx, |panel, cx| {
2625                                                                panel.new_agent_thread(
2626                                                                    AgentType::Gemini,
2627                                                                    window,
2628                                                                    cx,
2629                                                                );
2630                                                            });
2631                                                        }
2632                                                    });
2633                                                }
2634                                            }
2635                                        }),
2636                                )
2637                            })
2638                            .when(cx.has_flag::<ClaudeCodeFeatureFlag>(), |menu| {
2639                                menu.item(
2640                                    ContextMenuEntry::new("New Claude Code Thread")
2641                                        .icon(IconName::AiClaude)
2642                                        .icon_color(Color::Muted)
2643                                        .handler({
2644                                            let workspace = workspace.clone();
2645                                            move |window, cx| {
2646                                                if let Some(workspace) = workspace.upgrade() {
2647                                                    workspace.update(cx, |workspace, cx| {
2648                                                        if let Some(panel) =
2649                                                            workspace.panel::<AgentPanel>(cx)
2650                                                        {
2651                                                            panel.update(cx, |panel, cx| {
2652                                                                panel.new_agent_thread(
2653                                                                    AgentType::ClaudeCode,
2654                                                                    window,
2655                                                                    cx,
2656                                                                );
2657                                                            });
2658                                                        }
2659                                                    });
2660                                                }
2661                                            }
2662                                        }),
2663                                )
2664                            })
2665                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |mut menu| {
2666                                // Add custom agents from settings
2667                                let settings =
2668                                    agent_servers::AllAgentServersSettings::get_global(cx);
2669                                for (agent_name, agent_settings) in &settings.custom {
2670                                    menu = menu.item(
2671                                        ContextMenuEntry::new(format!("New {} Thread", agent_name))
2672                                            .icon(IconName::Terminal)
2673                                            .icon_color(Color::Muted)
2674                                            .handler({
2675                                                let workspace = workspace.clone();
2676                                                let agent_name = agent_name.clone();
2677                                                let agent_settings = agent_settings.clone();
2678                                                move |window, cx| {
2679                                                    if let Some(workspace) = workspace.upgrade() {
2680                                                        workspace.update(cx, |workspace, cx| {
2681                                                            if let Some(panel) =
2682                                                                workspace.panel::<AgentPanel>(cx)
2683                                                            {
2684                                                                panel.update(cx, |panel, cx| {
2685                                                                    panel.new_agent_thread(
2686                                                                        AgentType::Custom {
2687                                                                            name: agent_name
2688                                                                                .clone(),
2689                                                                            command: agent_settings
2690                                                                                .command
2691                                                                                .clone(),
2692                                                                        },
2693                                                                        window,
2694                                                                        cx,
2695                                                                    );
2696                                                                });
2697                                                            }
2698                                                        });
2699                                                    }
2700                                                }
2701                                            }),
2702                                    );
2703                                }
2704
2705                                menu
2706                            })
2707                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |menu| {
2708                                menu.separator().link(
2709                                    "Add Other Agents",
2710                                    OpenBrowser {
2711                                        url: zed_urls::external_agents_docs(cx),
2712                                    }
2713                                    .boxed_clone(),
2714                                )
2715                            });
2716                        menu
2717                    }))
2718                }
2719            });
2720
2721        let selected_agent_label = self.selected_agent.label();
2722        let selected_agent = div()
2723            .id("selected_agent_icon")
2724            .when_some(self.selected_agent.icon(), |this, icon| {
2725                this.px(DynamicSpacing::Base02.rems(cx))
2726                    .child(Icon::new(icon).color(Color::Muted))
2727                    .tooltip(move |window, cx| {
2728                        Tooltip::with_meta(
2729                            selected_agent_label.clone(),
2730                            None,
2731                            "Selected Agent",
2732                            window,
2733                            cx,
2734                        )
2735                    })
2736            })
2737            .into_any_element();
2738
2739        h_flex()
2740            .id("agent-panel-toolbar")
2741            .h(Tab::container_height(cx))
2742            .max_w_full()
2743            .flex_none()
2744            .justify_between()
2745            .gap_2()
2746            .bg(cx.theme().colors().tab_bar_background)
2747            .border_b_1()
2748            .border_color(cx.theme().colors().border)
2749            .child(
2750                h_flex()
2751                    .size_full()
2752                    .gap(DynamicSpacing::Base04.rems(cx))
2753                    .pl(DynamicSpacing::Base04.rems(cx))
2754                    .child(match &self.active_view {
2755                        ActiveView::History | ActiveView::Configuration => {
2756                            self.render_toolbar_back_button(cx).into_any_element()
2757                        }
2758                        _ => selected_agent.into_any_element(),
2759                    })
2760                    .child(self.render_title_view(window, cx)),
2761            )
2762            .child(
2763                h_flex()
2764                    .flex_none()
2765                    .gap(DynamicSpacing::Base02.rems(cx))
2766                    .pl(DynamicSpacing::Base04.rems(cx))
2767                    .pr(DynamicSpacing::Base06.rems(cx))
2768                    .child(new_thread_menu)
2769                    .child(self.render_recent_entries_menu(
2770                        IconName::MenuAltTemp,
2771                        Corner::TopRight,
2772                        cx,
2773                    ))
2774                    .child(self.render_panel_options_menu(window, cx)),
2775            )
2776    }
2777
2778    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2779        if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>()
2780            || cx.has_flag::<feature_flags::ClaudeCodeFeatureFlag>()
2781        {
2782            self.render_toolbar_new(window, cx).into_any_element()
2783        } else {
2784            self.render_toolbar_old(window, cx).into_any_element()
2785        }
2786    }
2787
2788    fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
2789        match &self.active_view {
2790            ActiveView::Thread {
2791                thread,
2792                message_editor,
2793                ..
2794            } => {
2795                let active_thread = thread.read(cx);
2796                let message_editor = message_editor.read(cx);
2797
2798                let editor_empty = message_editor.is_editor_fully_empty(cx);
2799
2800                if active_thread.is_empty() && editor_empty {
2801                    return None;
2802                }
2803
2804                let thread = active_thread.thread().read(cx);
2805                let is_generating = thread.is_generating();
2806                let conversation_token_usage = thread.total_token_usage()?;
2807
2808                let (total_token_usage, is_estimating) =
2809                    if let Some((editing_message_id, unsent_tokens)) =
2810                        active_thread.editing_message_id()
2811                    {
2812                        let combined = thread
2813                            .token_usage_up_to_message(editing_message_id)
2814                            .add(unsent_tokens);
2815
2816                        (combined, unsent_tokens > 0)
2817                    } else {
2818                        let unsent_tokens =
2819                            message_editor.last_estimated_token_count().unwrap_or(0);
2820                        let combined = conversation_token_usage.add(unsent_tokens);
2821
2822                        (combined, unsent_tokens > 0)
2823                    };
2824
2825                let is_waiting_to_update_token_count =
2826                    message_editor.is_waiting_to_update_token_count();
2827
2828                if total_token_usage.total == 0 {
2829                    return None;
2830                }
2831
2832                let token_color = match total_token_usage.ratio() {
2833                    TokenUsageRatio::Normal if is_estimating => Color::Default,
2834                    TokenUsageRatio::Normal => Color::Muted,
2835                    TokenUsageRatio::Warning => Color::Warning,
2836                    TokenUsageRatio::Exceeded => Color::Error,
2837                };
2838
2839                let token_count = h_flex()
2840                    .id("token-count")
2841                    .flex_shrink_0()
2842                    .gap_0p5()
2843                    .when(!is_generating && is_estimating, |parent| {
2844                        parent
2845                            .child(
2846                                h_flex()
2847                                    .mr_1()
2848                                    .size_2p5()
2849                                    .justify_center()
2850                                    .rounded_full()
2851                                    .bg(cx.theme().colors().text.opacity(0.1))
2852                                    .child(
2853                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
2854                                    ),
2855                            )
2856                            .tooltip(move |window, cx| {
2857                                Tooltip::with_meta(
2858                                    "Estimated New Token Count",
2859                                    None,
2860                                    format!(
2861                                        "Current Conversation Tokens: {}",
2862                                        humanize_token_count(conversation_token_usage.total)
2863                                    ),
2864                                    window,
2865                                    cx,
2866                                )
2867                            })
2868                    })
2869                    .child(
2870                        Label::new(humanize_token_count(total_token_usage.total))
2871                            .size(LabelSize::Small)
2872                            .color(token_color)
2873                            .map(|label| {
2874                                if is_generating || is_waiting_to_update_token_count {
2875                                    label
2876                                        .with_animation(
2877                                            "used-tokens-label",
2878                                            Animation::new(Duration::from_secs(2))
2879                                                .repeat()
2880                                                .with_easing(pulsating_between(0.6, 1.)),
2881                                            |label, delta| label.alpha(delta),
2882                                        )
2883                                        .into_any()
2884                                } else {
2885                                    label.into_any_element()
2886                                }
2887                            }),
2888                    )
2889                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2890                    .child(
2891                        Label::new(humanize_token_count(total_token_usage.max))
2892                            .size(LabelSize::Small)
2893                            .color(Color::Muted),
2894                    )
2895                    .into_any();
2896
2897                Some(token_count)
2898            }
2899            ActiveView::ExternalAgentThread { .. }
2900            | ActiveView::TextThread { .. }
2901            | ActiveView::History
2902            | ActiveView::Configuration => None,
2903        }
2904    }
2905
2906    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
2907        if TrialEndUpsell::dismissed() {
2908            return false;
2909        }
2910
2911        match &self.active_view {
2912            ActiveView::Thread { thread, .. } => {
2913                if thread
2914                    .read(cx)
2915                    .thread()
2916                    .read(cx)
2917                    .configured_model()
2918                    .is_some_and(|model| {
2919                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2920                    })
2921                {
2922                    return false;
2923                }
2924            }
2925            ActiveView::TextThread { .. } => {
2926                if LanguageModelRegistry::global(cx)
2927                    .read(cx)
2928                    .default_model()
2929                    .is_some_and(|model| {
2930                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2931                    })
2932                {
2933                    return false;
2934                }
2935            }
2936            ActiveView::ExternalAgentThread { .. }
2937            | ActiveView::History
2938            | ActiveView::Configuration => return false,
2939        }
2940
2941        let plan = self.user_store.read(cx).plan();
2942        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
2943
2944        matches!(plan, Some(Plan::ZedFree)) && has_previous_trial
2945    }
2946
2947    fn should_render_onboarding(&self, cx: &mut Context<Self>) -> bool {
2948        if OnboardingUpsell::dismissed() {
2949            return false;
2950        }
2951
2952        match &self.active_view {
2953            ActiveView::History | ActiveView::Configuration => false,
2954            ActiveView::ExternalAgentThread { thread_view, .. }
2955                if thread_view.read(cx).as_native_thread(cx).is_none() =>
2956            {
2957                false
2958            }
2959            _ => {
2960                let history_is_empty = if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
2961                    self.acp_history_store.read(cx).is_empty(cx)
2962                } else {
2963                    self.history_store
2964                        .update(cx, |store, cx| store.recent_entries(1, cx).is_empty())
2965                };
2966
2967                let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
2968                    .providers()
2969                    .iter()
2970                    .any(|provider| {
2971                        provider.is_authenticated(cx)
2972                            && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2973                    });
2974
2975                history_is_empty || !has_configured_non_zed_providers
2976            }
2977        }
2978    }
2979
2980    fn render_onboarding(
2981        &self,
2982        _window: &mut Window,
2983        cx: &mut Context<Self>,
2984    ) -> Option<impl IntoElement> {
2985        if !self.should_render_onboarding(cx) {
2986            return None;
2987        }
2988
2989        let thread_view = matches!(&self.active_view, ActiveView::Thread { .. });
2990        let text_thread_view = matches!(&self.active_view, ActiveView::TextThread { .. });
2991
2992        Some(
2993            div()
2994                .when(thread_view, |this| {
2995                    this.size_full().bg(cx.theme().colors().panel_background)
2996                })
2997                .when(text_thread_view, |this| {
2998                    this.bg(cx.theme().colors().editor_background)
2999                })
3000                .child(self.onboarding.clone()),
3001        )
3002    }
3003
3004    fn render_backdrop(&self, cx: &mut Context<Self>) -> impl IntoElement {
3005        div()
3006            .size_full()
3007            .absolute()
3008            .inset_0()
3009            .bg(cx.theme().colors().panel_background)
3010            .opacity(0.8)
3011            .block_mouse_except_scroll()
3012    }
3013
3014    fn render_trial_end_upsell(
3015        &self,
3016        _window: &mut Window,
3017        cx: &mut Context<Self>,
3018    ) -> Option<impl IntoElement> {
3019        if !self.should_render_trial_end_upsell(cx) {
3020            return None;
3021        }
3022
3023        Some(
3024            v_flex()
3025                .absolute()
3026                .inset_0()
3027                .size_full()
3028                .bg(cx.theme().colors().panel_background)
3029                .opacity(0.85)
3030                .block_mouse_except_scroll()
3031                .child(EndTrialUpsell::new(Arc::new({
3032                    let this = cx.entity();
3033                    move |_, cx| {
3034                        this.update(cx, |_this, cx| {
3035                            TrialEndUpsell::set_dismissed(true, cx);
3036                            cx.notify();
3037                        });
3038                    }
3039                }))),
3040        )
3041    }
3042
3043    fn render_empty_state_section_header(
3044        &self,
3045        label: impl Into<SharedString>,
3046        action_slot: Option<AnyElement>,
3047        cx: &mut Context<Self>,
3048    ) -> impl IntoElement {
3049        div().pl_1().pr_1p5().child(
3050            h_flex()
3051                .mt_2()
3052                .pl_1p5()
3053                .pb_1()
3054                .w_full()
3055                .justify_between()
3056                .border_b_1()
3057                .border_color(cx.theme().colors().border_variant)
3058                .child(
3059                    Label::new(label.into())
3060                        .size(LabelSize::Small)
3061                        .color(Color::Muted),
3062                )
3063                .children(action_slot),
3064        )
3065    }
3066
3067    fn render_thread_empty_state(
3068        &self,
3069        window: &mut Window,
3070        cx: &mut Context<Self>,
3071    ) -> impl IntoElement {
3072        let recent_history = self
3073            .history_store
3074            .update(cx, |this, cx| this.recent_entries(6, cx));
3075
3076        let model_registry = LanguageModelRegistry::read_global(cx);
3077
3078        let configuration_error =
3079            model_registry.configuration_error(model_registry.default_model(), cx);
3080
3081        let no_error = configuration_error.is_none();
3082        let focus_handle = self.focus_handle(cx);
3083
3084        v_flex()
3085            .size_full()
3086            .bg(cx.theme().colors().panel_background)
3087            .when(recent_history.is_empty(), |this| {
3088                this.child(
3089                    v_flex()
3090                        .size_full()
3091                        .mx_auto()
3092                        .justify_center()
3093                        .items_center()
3094                        .gap_1()
3095                        .child(h_flex().child(Headline::new("Welcome to the Agent Panel")))
3096                        .when(no_error, |parent| {
3097                            parent
3098                                .child(h_flex().child(
3099                                    Label::new("Ask and build anything.").color(Color::Muted),
3100                                ))
3101                                .child(
3102                                    v_flex()
3103                                        .mt_2()
3104                                        .gap_1()
3105                                        .max_w_48()
3106                                        .child(
3107                                            Button::new("context", "Add Context")
3108                                                .label_size(LabelSize::Small)
3109                                                .icon(IconName::FileCode)
3110                                                .icon_position(IconPosition::Start)
3111                                                .icon_size(IconSize::Small)
3112                                                .icon_color(Color::Muted)
3113                                                .full_width()
3114                                                .key_binding(KeyBinding::for_action_in(
3115                                                    &ToggleContextPicker,
3116                                                    &focus_handle,
3117                                                    window,
3118                                                    cx,
3119                                                ))
3120                                                .on_click(|_event, window, cx| {
3121                                                    window.dispatch_action(
3122                                                        ToggleContextPicker.boxed_clone(),
3123                                                        cx,
3124                                                    )
3125                                                }),
3126                                        )
3127                                        .child(
3128                                            Button::new("mode", "Switch Model")
3129                                                .label_size(LabelSize::Small)
3130                                                .icon(IconName::DatabaseZap)
3131                                                .icon_position(IconPosition::Start)
3132                                                .icon_size(IconSize::Small)
3133                                                .icon_color(Color::Muted)
3134                                                .full_width()
3135                                                .key_binding(KeyBinding::for_action_in(
3136                                                    &ToggleModelSelector,
3137                                                    &focus_handle,
3138                                                    window,
3139                                                    cx,
3140                                                ))
3141                                                .on_click(|_event, window, cx| {
3142                                                    window.dispatch_action(
3143                                                        ToggleModelSelector.boxed_clone(),
3144                                                        cx,
3145                                                    )
3146                                                }),
3147                                        )
3148                                        .child(
3149                                            Button::new("settings", "View Settings")
3150                                                .label_size(LabelSize::Small)
3151                                                .icon(IconName::Settings)
3152                                                .icon_position(IconPosition::Start)
3153                                                .icon_size(IconSize::Small)
3154                                                .icon_color(Color::Muted)
3155                                                .full_width()
3156                                                .key_binding(KeyBinding::for_action_in(
3157                                                    &OpenSettings,
3158                                                    &focus_handle,
3159                                                    window,
3160                                                    cx,
3161                                                ))
3162                                                .on_click(|_event, window, cx| {
3163                                                    window.dispatch_action(
3164                                                        OpenSettings.boxed_clone(),
3165                                                        cx,
3166                                                    )
3167                                                }),
3168                                        ),
3169                                )
3170                        }),
3171                )
3172            })
3173            .when(!recent_history.is_empty(), |parent| {
3174                parent
3175                    .overflow_hidden()
3176                    .justify_end()
3177                    .gap_1()
3178                    .child(
3179                        self.render_empty_state_section_header(
3180                            "Recent",
3181                            Some(
3182                                Button::new("view-history", "View All")
3183                                    .style(ButtonStyle::Subtle)
3184                                    .label_size(LabelSize::Small)
3185                                    .key_binding(
3186                                        KeyBinding::for_action_in(
3187                                            &OpenHistory,
3188                                            &self.focus_handle(cx),
3189                                            window,
3190                                            cx,
3191                                        )
3192                                        .map(|kb| kb.size(rems_from_px(12.))),
3193                                    )
3194                                    .on_click(move |_event, window, cx| {
3195                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
3196                                    })
3197                                    .into_any_element(),
3198                            ),
3199                            cx,
3200                        ),
3201                    )
3202                    .child(
3203                        v_flex().p_1().pr_1p5().gap_1().children(
3204                            recent_history
3205                                .into_iter()
3206                                .enumerate()
3207                                .map(|(index, entry)| {
3208                                    // TODO: Add keyboard navigation.
3209                                    let is_hovered =
3210                                        self.hovered_recent_history_item == Some(index);
3211                                    HistoryEntryElement::new(entry, cx.entity().downgrade())
3212                                        .hovered(is_hovered)
3213                                        .on_hover(cx.listener(
3214                                            move |this, is_hovered, _window, cx| {
3215                                                if *is_hovered {
3216                                                    this.hovered_recent_history_item = Some(index);
3217                                                } else if this.hovered_recent_history_item
3218                                                    == Some(index)
3219                                                {
3220                                                    this.hovered_recent_history_item = None;
3221                                                }
3222                                                cx.notify();
3223                                            },
3224                                        ))
3225                                        .into_any_element()
3226                                }),
3227                        ),
3228                    )
3229            })
3230            .when_some(configuration_error.as_ref(), |this, err| {
3231                this.child(self.render_configuration_error(false, err, &focus_handle, window, cx))
3232            })
3233    }
3234
3235    fn render_configuration_error(
3236        &self,
3237        border_bottom: bool,
3238        configuration_error: &ConfigurationError,
3239        focus_handle: &FocusHandle,
3240        window: &mut Window,
3241        cx: &mut App,
3242    ) -> impl IntoElement {
3243        let zed_provider_configured = AgentSettings::get_global(cx)
3244            .default_model
3245            .as_ref()
3246            .is_some_and(|selection| selection.provider.0.as_str() == "zed.dev");
3247
3248        let callout = if zed_provider_configured {
3249            Callout::new()
3250                .icon(IconName::Warning)
3251                .severity(Severity::Warning)
3252                .when(border_bottom, |this| {
3253                    this.border_position(ui::BorderPosition::Bottom)
3254                })
3255                .title("Sign in to continue using Zed as your LLM provider.")
3256                .actions_slot(
3257                    Button::new("sign_in", "Sign In")
3258                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
3259                        .label_size(LabelSize::Small)
3260                        .on_click({
3261                            let workspace = self.workspace.clone();
3262                            move |_, _, cx| {
3263                                let Ok(client) =
3264                                    workspace.update(cx, |workspace, _| workspace.client().clone())
3265                                else {
3266                                    return;
3267                                };
3268
3269                                cx.spawn(async move |cx| {
3270                                    client.sign_in_with_optional_connect(true, cx).await
3271                                })
3272                                .detach_and_log_err(cx);
3273                            }
3274                        }),
3275                )
3276        } else {
3277            Callout::new()
3278                .icon(IconName::Warning)
3279                .severity(Severity::Warning)
3280                .when(border_bottom, |this| {
3281                    this.border_position(ui::BorderPosition::Bottom)
3282                })
3283                .title(configuration_error.to_string())
3284                .actions_slot(
3285                    Button::new("settings", "Configure")
3286                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
3287                        .label_size(LabelSize::Small)
3288                        .key_binding(
3289                            KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
3290                                .map(|kb| kb.size(rems_from_px(12.))),
3291                        )
3292                        .on_click(|_event, window, cx| {
3293                            window.dispatch_action(OpenSettings.boxed_clone(), cx)
3294                        }),
3295                )
3296        };
3297
3298        match configuration_error {
3299            ConfigurationError::ModelNotFound
3300            | ConfigurationError::ProviderNotAuthenticated(_)
3301            | ConfigurationError::NoProvider => callout.into_any_element(),
3302        }
3303    }
3304
3305    fn render_tool_use_limit_reached(
3306        &self,
3307        window: &mut Window,
3308        cx: &mut Context<Self>,
3309    ) -> Option<AnyElement> {
3310        let active_thread = match &self.active_view {
3311            ActiveView::Thread { thread, .. } => thread,
3312            ActiveView::ExternalAgentThread { .. } => {
3313                return None;
3314            }
3315            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
3316                return None;
3317            }
3318        };
3319
3320        let thread = active_thread.read(cx).thread().read(cx);
3321
3322        let tool_use_limit_reached = thread.tool_use_limit_reached();
3323        if !tool_use_limit_reached {
3324            return None;
3325        }
3326
3327        let model = thread.configured_model()?.model;
3328
3329        let focus_handle = self.focus_handle(cx);
3330
3331        let banner = Banner::new()
3332            .severity(Severity::Info)
3333            .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
3334            .action_slot(
3335                h_flex()
3336                    .gap_1()
3337                    .child(
3338                        Button::new("continue-conversation", "Continue")
3339                            .layer(ElevationIndex::ModalSurface)
3340                            .label_size(LabelSize::Small)
3341                            .key_binding(
3342                                KeyBinding::for_action_in(
3343                                    &ContinueThread,
3344                                    &focus_handle,
3345                                    window,
3346                                    cx,
3347                                )
3348                                .map(|kb| kb.size(rems_from_px(10.))),
3349                            )
3350                            .on_click(cx.listener(|this, _, window, cx| {
3351                                this.continue_conversation(window, cx);
3352                            })),
3353                    )
3354                    .when(model.supports_burn_mode(), |this| {
3355                        this.child(
3356                            Button::new("continue-burn-mode", "Continue with Burn Mode")
3357                                .style(ButtonStyle::Filled)
3358                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
3359                                .layer(ElevationIndex::ModalSurface)
3360                                .label_size(LabelSize::Small)
3361                                .key_binding(
3362                                    KeyBinding::for_action_in(
3363                                        &ContinueWithBurnMode,
3364                                        &focus_handle,
3365                                        window,
3366                                        cx,
3367                                    )
3368                                    .map(|kb| kb.size(rems_from_px(10.))),
3369                                )
3370                                .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
3371                                .on_click({
3372                                    let active_thread = active_thread.clone();
3373                                    cx.listener(move |this, _, window, cx| {
3374                                        active_thread.update(cx, |active_thread, cx| {
3375                                            active_thread.thread().update(cx, |thread, _cx| {
3376                                                thread.set_completion_mode(CompletionMode::Burn);
3377                                            });
3378                                        });
3379                                        this.continue_conversation(window, cx);
3380                                    })
3381                                }),
3382                        )
3383                    }),
3384            );
3385
3386        Some(div().px_2().pb_2().child(banner).into_any_element())
3387    }
3388
3389    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
3390        let message = message.into();
3391
3392        IconButton::new("copy", IconName::Copy)
3393            .icon_size(IconSize::Small)
3394            .icon_color(Color::Muted)
3395            .tooltip(Tooltip::text("Copy Error Message"))
3396            .on_click(move |_, _, cx| {
3397                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
3398            })
3399    }
3400
3401    fn dismiss_error_button(
3402        &self,
3403        thread: &Entity<ActiveThread>,
3404        cx: &mut Context<Self>,
3405    ) -> impl IntoElement {
3406        IconButton::new("dismiss", IconName::Close)
3407            .icon_size(IconSize::Small)
3408            .icon_color(Color::Muted)
3409            .tooltip(Tooltip::text("Dismiss Error"))
3410            .on_click(cx.listener({
3411                let thread = thread.clone();
3412                move |_, _, _, cx| {
3413                    thread.update(cx, |this, _cx| {
3414                        this.clear_last_error();
3415                    });
3416
3417                    cx.notify();
3418                }
3419            }))
3420    }
3421
3422    fn upgrade_button(
3423        &self,
3424        thread: &Entity<ActiveThread>,
3425        cx: &mut Context<Self>,
3426    ) -> impl IntoElement {
3427        Button::new("upgrade", "Upgrade")
3428            .label_size(LabelSize::Small)
3429            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
3430            .on_click(cx.listener({
3431                let thread = thread.clone();
3432                move |_, _, _, cx| {
3433                    thread.update(cx, |this, _cx| {
3434                        this.clear_last_error();
3435                    });
3436
3437                    cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx));
3438                    cx.notify();
3439                }
3440            }))
3441    }
3442
3443    fn render_payment_required_error(
3444        &self,
3445        thread: &Entity<ActiveThread>,
3446        cx: &mut Context<Self>,
3447    ) -> AnyElement {
3448        const ERROR_MESSAGE: &str =
3449            "You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
3450
3451        Callout::new()
3452            .severity(Severity::Error)
3453            .icon(IconName::XCircle)
3454            .title("Free Usage Exceeded")
3455            .description(ERROR_MESSAGE)
3456            .actions_slot(
3457                h_flex()
3458                    .gap_0p5()
3459                    .child(self.upgrade_button(thread, cx))
3460                    .child(self.create_copy_button(ERROR_MESSAGE)),
3461            )
3462            .dismiss_action(self.dismiss_error_button(thread, cx))
3463            .into_any_element()
3464    }
3465
3466    fn render_model_request_limit_reached_error(
3467        &self,
3468        plan: Plan,
3469        thread: &Entity<ActiveThread>,
3470        cx: &mut Context<Self>,
3471    ) -> AnyElement {
3472        let error_message = match plan {
3473            Plan::ZedPro => "Upgrade to usage-based billing for more prompts.",
3474            Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.",
3475        };
3476
3477        Callout::new()
3478            .severity(Severity::Error)
3479            .title("Model Prompt Limit Reached")
3480            .description(error_message)
3481            .actions_slot(
3482                h_flex()
3483                    .gap_0p5()
3484                    .child(self.upgrade_button(thread, cx))
3485                    .child(self.create_copy_button(error_message)),
3486            )
3487            .dismiss_action(self.dismiss_error_button(thread, cx))
3488            .into_any_element()
3489    }
3490
3491    fn render_retry_button(&self, thread: &Entity<ActiveThread>) -> AnyElement {
3492        Button::new("retry", "Retry")
3493            .icon(IconName::RotateCw)
3494            .icon_position(IconPosition::Start)
3495            .icon_size(IconSize::Small)
3496            .label_size(LabelSize::Small)
3497            .on_click({
3498                let thread = thread.clone();
3499                move |_, window, cx| {
3500                    thread.update(cx, |thread, cx| {
3501                        thread.clear_last_error();
3502                        thread.thread().update(cx, |thread, cx| {
3503                            thread.retry_last_completion(Some(window.window_handle()), cx);
3504                        });
3505                    });
3506                }
3507            })
3508            .into_any_element()
3509    }
3510
3511    fn render_error_message(
3512        &self,
3513        header: SharedString,
3514        message: SharedString,
3515        thread: &Entity<ActiveThread>,
3516        cx: &mut Context<Self>,
3517    ) -> AnyElement {
3518        let message_with_header = format!("{}\n{}", header, message);
3519
3520        Callout::new()
3521            .severity(Severity::Error)
3522            .icon(IconName::XCircle)
3523            .title(header)
3524            .description(message)
3525            .actions_slot(
3526                h_flex()
3527                    .gap_0p5()
3528                    .child(self.render_retry_button(thread))
3529                    .child(self.create_copy_button(message_with_header)),
3530            )
3531            .dismiss_action(self.dismiss_error_button(thread, cx))
3532            .into_any_element()
3533    }
3534
3535    fn render_retryable_error(
3536        &self,
3537        message: SharedString,
3538        can_enable_burn_mode: bool,
3539        thread: &Entity<ActiveThread>,
3540    ) -> AnyElement {
3541        Callout::new()
3542            .severity(Severity::Error)
3543            .title("Error")
3544            .description(message)
3545            .actions_slot(
3546                h_flex()
3547                    .gap_0p5()
3548                    .when(can_enable_burn_mode, |this| {
3549                        this.child(
3550                            Button::new("enable_burn_retry", "Enable Burn Mode and Retry")
3551                                .icon(IconName::ZedBurnMode)
3552                                .icon_position(IconPosition::Start)
3553                                .icon_size(IconSize::Small)
3554                                .label_size(LabelSize::Small)
3555                                .on_click({
3556                                    let thread = thread.clone();
3557                                    move |_, window, cx| {
3558                                        thread.update(cx, |thread, cx| {
3559                                            thread.clear_last_error();
3560                                            thread.thread().update(cx, |thread, cx| {
3561                                                thread.enable_burn_mode_and_retry(
3562                                                    Some(window.window_handle()),
3563                                                    cx,
3564                                                );
3565                                            });
3566                                        });
3567                                    }
3568                                }),
3569                        )
3570                    })
3571                    .child(self.render_retry_button(thread)),
3572            )
3573            .into_any_element()
3574    }
3575
3576    fn render_prompt_editor(
3577        &self,
3578        context_editor: &Entity<TextThreadEditor>,
3579        buffer_search_bar: &Entity<BufferSearchBar>,
3580        window: &mut Window,
3581        cx: &mut Context<Self>,
3582    ) -> Div {
3583        let mut registrar = buffer_search::DivRegistrar::new(
3584            |this, _, _cx| match &this.active_view {
3585                ActiveView::TextThread {
3586                    buffer_search_bar, ..
3587                } => Some(buffer_search_bar.clone()),
3588                _ => None,
3589            },
3590            cx,
3591        );
3592        BufferSearchBar::register(&mut registrar);
3593        registrar
3594            .into_div()
3595            .size_full()
3596            .relative()
3597            .map(|parent| {
3598                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
3599                    if buffer_search_bar.is_dismissed() {
3600                        return parent;
3601                    }
3602                    parent.child(
3603                        div()
3604                            .p(DynamicSpacing::Base08.rems(cx))
3605                            .border_b_1()
3606                            .border_color(cx.theme().colors().border_variant)
3607                            .bg(cx.theme().colors().editor_background)
3608                            .child(buffer_search_bar.render(window, cx)),
3609                    )
3610                })
3611            })
3612            .child(context_editor.clone())
3613            .child(self.render_drag_target(cx))
3614    }
3615
3616    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
3617        let is_local = self.project.read(cx).is_local();
3618        div()
3619            .invisible()
3620            .absolute()
3621            .top_0()
3622            .right_0()
3623            .bottom_0()
3624            .left_0()
3625            .bg(cx.theme().colors().drop_target_background)
3626            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
3627            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
3628            .when(is_local, |this| {
3629                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
3630            })
3631            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
3632                let item = tab.pane.read(cx).item_for_index(tab.ix);
3633                let project_paths = item
3634                    .and_then(|item| item.project_path(cx))
3635                    .into_iter()
3636                    .collect::<Vec<_>>();
3637                this.handle_drop(project_paths, vec![], window, cx);
3638            }))
3639            .on_drop(
3640                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
3641                    let project_paths = selection
3642                        .items()
3643                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
3644                        .collect::<Vec<_>>();
3645                    this.handle_drop(project_paths, vec![], window, cx);
3646                }),
3647            )
3648            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
3649                let tasks = paths
3650                    .paths()
3651                    .iter()
3652                    .map(|path| {
3653                        Workspace::project_path_for_path(this.project.clone(), path, false, cx)
3654                    })
3655                    .collect::<Vec<_>>();
3656                cx.spawn_in(window, async move |this, cx| {
3657                    let mut paths = vec![];
3658                    let mut added_worktrees = vec![];
3659                    let opened_paths = futures::future::join_all(tasks).await;
3660                    for entry in opened_paths {
3661                        if let Some((worktree, project_path)) = entry.log_err() {
3662                            added_worktrees.push(worktree);
3663                            paths.push(project_path);
3664                        }
3665                    }
3666                    this.update_in(cx, |this, window, cx| {
3667                        this.handle_drop(paths, added_worktrees, window, cx);
3668                    })
3669                    .ok();
3670                })
3671                .detach();
3672            }))
3673    }
3674
3675    fn handle_drop(
3676        &mut self,
3677        paths: Vec<ProjectPath>,
3678        added_worktrees: Vec<Entity<Worktree>>,
3679        window: &mut Window,
3680        cx: &mut Context<Self>,
3681    ) {
3682        match &self.active_view {
3683            ActiveView::Thread { thread, .. } => {
3684                let context_store = thread.read(cx).context_store().clone();
3685                context_store.update(cx, move |context_store, cx| {
3686                    let mut tasks = Vec::new();
3687                    for project_path in &paths {
3688                        tasks.push(context_store.add_file_from_path(
3689                            project_path.clone(),
3690                            false,
3691                            cx,
3692                        ));
3693                    }
3694                    cx.background_spawn(async move {
3695                        futures::future::join_all(tasks).await;
3696                        // Need to hold onto the worktrees until they have already been used when
3697                        // opening the buffers.
3698                        drop(added_worktrees);
3699                    })
3700                    .detach();
3701                });
3702            }
3703            ActiveView::ExternalAgentThread { thread_view } => {
3704                thread_view.update(cx, |thread_view, cx| {
3705                    thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
3706                });
3707            }
3708            ActiveView::TextThread { context_editor, .. } => {
3709                context_editor.update(cx, |context_editor, cx| {
3710                    TextThreadEditor::insert_dragged_files(
3711                        context_editor,
3712                        paths,
3713                        added_worktrees,
3714                        window,
3715                        cx,
3716                    );
3717                });
3718            }
3719            ActiveView::History | ActiveView::Configuration => {}
3720        }
3721    }
3722
3723    fn key_context(&self) -> KeyContext {
3724        let mut key_context = KeyContext::new_with_defaults();
3725        key_context.add("AgentPanel");
3726        match &self.active_view {
3727            ActiveView::ExternalAgentThread { .. } => key_context.add("external_agent_thread"),
3728            ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
3729            ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
3730        }
3731        key_context
3732    }
3733}
3734
3735impl Render for AgentPanel {
3736    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3737        // WARNING: Changes to this element hierarchy can have
3738        // non-obvious implications to the layout of children.
3739        //
3740        // If you need to change it, please confirm:
3741        // - The message editor expands (cmd-option-esc) correctly
3742        // - When expanded, the buttons at the bottom of the panel are displayed correctly
3743        // - Font size works as expected and can be changed with cmd-+/cmd-
3744        // - Scrolling in all views works as expected
3745        // - Files can be dropped into the panel
3746        let content = v_flex()
3747            .relative()
3748            .size_full()
3749            .justify_between()
3750            .key_context(self.key_context())
3751            .on_action(cx.listener(Self::cancel))
3752            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
3753                this.new_thread(action, window, cx);
3754            }))
3755            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
3756                this.open_history(window, cx);
3757            }))
3758            .on_action(cx.listener(|this, _: &OpenSettings, window, cx| {
3759                this.open_configuration(window, cx);
3760            }))
3761            .on_action(cx.listener(Self::open_active_thread_as_markdown))
3762            .on_action(cx.listener(Self::deploy_rules_library))
3763            .on_action(cx.listener(Self::open_agent_diff))
3764            .on_action(cx.listener(Self::go_back))
3765            .on_action(cx.listener(Self::toggle_navigation_menu))
3766            .on_action(cx.listener(Self::toggle_options_menu))
3767            .on_action(cx.listener(Self::increase_font_size))
3768            .on_action(cx.listener(Self::decrease_font_size))
3769            .on_action(cx.listener(Self::reset_font_size))
3770            .on_action(cx.listener(Self::toggle_zoom))
3771            .on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
3772                this.continue_conversation(window, cx);
3773            }))
3774            .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
3775                match &this.active_view {
3776                    ActiveView::Thread { thread, .. } => {
3777                        thread.update(cx, |active_thread, cx| {
3778                            active_thread.thread().update(cx, |thread, _cx| {
3779                                thread.set_completion_mode(CompletionMode::Burn);
3780                            });
3781                        });
3782                        this.continue_conversation(window, cx);
3783                    }
3784                    ActiveView::ExternalAgentThread { .. } => {}
3785                    ActiveView::TextThread { .. }
3786                    | ActiveView::History
3787                    | ActiveView::Configuration => {}
3788                }
3789            }))
3790            .on_action(cx.listener(Self::toggle_burn_mode))
3791            .on_action(cx.listener(|this, _: &ReauthenticateAgent, window, cx| {
3792                if let Some(thread_view) = this.active_thread_view() {
3793                    thread_view.update(cx, |thread_view, cx| thread_view.reauthenticate(window, cx))
3794                }
3795            }))
3796            .child(self.render_toolbar(window, cx))
3797            .children(self.render_onboarding(window, cx))
3798            .map(|parent| match &self.active_view {
3799                ActiveView::Thread {
3800                    thread,
3801                    message_editor,
3802                    ..
3803                } => parent
3804                    .child(
3805                        if thread.read(cx).is_empty() && !self.should_render_onboarding(cx) {
3806                            self.render_thread_empty_state(window, cx)
3807                                .into_any_element()
3808                        } else {
3809                            thread.clone().into_any_element()
3810                        },
3811                    )
3812                    .children(self.render_tool_use_limit_reached(window, cx))
3813                    .when_some(thread.read(cx).last_error(), |this, last_error| {
3814                        this.child(
3815                            div()
3816                                .child(match last_error {
3817                                    ThreadError::PaymentRequired => {
3818                                        self.render_payment_required_error(thread, cx)
3819                                    }
3820                                    ThreadError::ModelRequestLimitReached { plan } => self
3821                                        .render_model_request_limit_reached_error(plan, thread, cx),
3822                                    ThreadError::Message { header, message } => {
3823                                        self.render_error_message(header, message, thread, cx)
3824                                    }
3825                                    ThreadError::RetryableError {
3826                                        message,
3827                                        can_enable_burn_mode,
3828                                    } => self.render_retryable_error(
3829                                        message,
3830                                        can_enable_burn_mode,
3831                                        thread,
3832                                    ),
3833                                })
3834                                .into_any(),
3835                        )
3836                    })
3837                    .child(h_flex().relative().child(message_editor.clone()).when(
3838                        !LanguageModelRegistry::read_global(cx).has_authenticated_provider(cx),
3839                        |this| this.child(self.render_backdrop(cx)),
3840                    ))
3841                    .child(self.render_drag_target(cx)),
3842                ActiveView::ExternalAgentThread { thread_view, .. } => parent
3843                    .child(thread_view.clone())
3844                    .child(self.render_drag_target(cx)),
3845                ActiveView::History => {
3846                    if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>() {
3847                        parent.child(self.acp_history.clone())
3848                    } else {
3849                        parent.child(self.history.clone())
3850                    }
3851                }
3852                ActiveView::TextThread {
3853                    context_editor,
3854                    buffer_search_bar,
3855                    ..
3856                } => {
3857                    let model_registry = LanguageModelRegistry::read_global(cx);
3858                    let configuration_error =
3859                        model_registry.configuration_error(model_registry.default_model(), cx);
3860                    parent
3861                        .map(|this| {
3862                            if !self.should_render_onboarding(cx)
3863                                && let Some(err) = configuration_error.as_ref()
3864                            {
3865                                this.child(self.render_configuration_error(
3866                                    true,
3867                                    err,
3868                                    &self.focus_handle(cx),
3869                                    window,
3870                                    cx,
3871                                ))
3872                            } else {
3873                                this
3874                            }
3875                        })
3876                        .child(self.render_prompt_editor(
3877                            context_editor,
3878                            buffer_search_bar,
3879                            window,
3880                            cx,
3881                        ))
3882                }
3883                ActiveView::Configuration => parent.children(self.configuration.clone()),
3884            })
3885            .children(self.render_trial_end_upsell(window, cx));
3886
3887        match self.active_view.which_font_size_used() {
3888            WhichFontSize::AgentFont => {
3889                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
3890                    .size_full()
3891                    .child(content)
3892                    .into_any()
3893            }
3894            _ => content.into_any(),
3895        }
3896    }
3897}
3898
3899struct PromptLibraryInlineAssist {
3900    workspace: WeakEntity<Workspace>,
3901}
3902
3903impl PromptLibraryInlineAssist {
3904    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
3905        Self { workspace }
3906    }
3907}
3908
3909impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
3910    fn assist(
3911        &self,
3912        prompt_editor: &Entity<Editor>,
3913        initial_prompt: Option<String>,
3914        window: &mut Window,
3915        cx: &mut Context<RulesLibrary>,
3916    ) {
3917        InlineAssistant::update_global(cx, |assistant, cx| {
3918            let Some(project) = self
3919                .workspace
3920                .upgrade()
3921                .map(|workspace| workspace.read(cx).project().downgrade())
3922            else {
3923                return;
3924            };
3925            let prompt_store = None;
3926            let thread_store = None;
3927            let text_thread_store = None;
3928            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
3929            assistant.assist(
3930                prompt_editor,
3931                self.workspace.clone(),
3932                context_store,
3933                project,
3934                prompt_store,
3935                thread_store,
3936                text_thread_store,
3937                initial_prompt,
3938                window,
3939                cx,
3940            )
3941        })
3942    }
3943
3944    fn focus_agent_panel(
3945        &self,
3946        workspace: &mut Workspace,
3947        window: &mut Window,
3948        cx: &mut Context<Workspace>,
3949    ) -> bool {
3950        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
3951    }
3952}
3953
3954pub struct ConcreteAssistantPanelDelegate;
3955
3956impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
3957    fn active_context_editor(
3958        &self,
3959        workspace: &mut Workspace,
3960        _window: &mut Window,
3961        cx: &mut Context<Workspace>,
3962    ) -> Option<Entity<TextThreadEditor>> {
3963        let panel = workspace.panel::<AgentPanel>(cx)?;
3964        panel.read(cx).active_context_editor()
3965    }
3966
3967    fn open_saved_context(
3968        &self,
3969        workspace: &mut Workspace,
3970        path: Arc<Path>,
3971        window: &mut Window,
3972        cx: &mut Context<Workspace>,
3973    ) -> Task<Result<()>> {
3974        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3975            return Task::ready(Err(anyhow!("Agent panel not found")));
3976        };
3977
3978        panel.update(cx, |panel, cx| {
3979            panel.open_saved_prompt_editor(path, window, cx)
3980        })
3981    }
3982
3983    fn open_remote_context(
3984        &self,
3985        _workspace: &mut Workspace,
3986        _context_id: assistant_context::ContextId,
3987        _window: &mut Window,
3988        _cx: &mut Context<Workspace>,
3989    ) -> Task<Result<Entity<TextThreadEditor>>> {
3990        Task::ready(Err(anyhow!("opening remote context not implemented")))
3991    }
3992
3993    fn quote_selection(
3994        &self,
3995        workspace: &mut Workspace,
3996        selection_ranges: Vec<Range<Anchor>>,
3997        buffer: Entity<MultiBuffer>,
3998        window: &mut Window,
3999        cx: &mut Context<Workspace>,
4000    ) {
4001        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
4002            return;
4003        };
4004
4005        if !panel.focus_handle(cx).contains_focused(window, cx) {
4006            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
4007        }
4008
4009        panel.update(cx, |_, cx| {
4010            // Wait to create a new context until the workspace is no longer
4011            // being updated.
4012            cx.defer_in(window, move |panel, window, cx| {
4013                if let Some(thread_view) = panel.active_thread_view() {
4014                    thread_view.update(cx, |thread_view, cx| {
4015                        thread_view.insert_selections(window, cx);
4016                    });
4017                } else if let Some(message_editor) = panel.active_message_editor() {
4018                    message_editor.update(cx, |message_editor, cx| {
4019                        message_editor.context_store().update(cx, |store, cx| {
4020                            let buffer = buffer.read(cx);
4021                            let selection_ranges = selection_ranges
4022                                .into_iter()
4023                                .flat_map(|range| {
4024                                    let (start_buffer, start) =
4025                                        buffer.text_anchor_for_position(range.start, cx)?;
4026                                    let (end_buffer, end) =
4027                                        buffer.text_anchor_for_position(range.end, cx)?;
4028                                    if start_buffer != end_buffer {
4029                                        return None;
4030                                    }
4031                                    Some((start_buffer, start..end))
4032                                })
4033                                .collect::<Vec<_>>();
4034
4035                            for (buffer, range) in selection_ranges {
4036                                store.add_selection(buffer, range, cx);
4037                            }
4038                        })
4039                    })
4040                } else if let Some(context_editor) = panel.active_context_editor() {
4041                    let snapshot = buffer.read(cx).snapshot(cx);
4042                    let selection_ranges = selection_ranges
4043                        .into_iter()
4044                        .map(|range| range.to_point(&snapshot))
4045                        .collect::<Vec<_>>();
4046
4047                    context_editor.update(cx, |context_editor, cx| {
4048                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
4049                    });
4050                }
4051            });
4052        });
4053    }
4054}
4055
4056struct OnboardingUpsell;
4057
4058impl Dismissable for OnboardingUpsell {
4059    const KEY: &'static str = "dismissed-trial-upsell";
4060}
4061
4062struct TrialEndUpsell;
4063
4064impl Dismissable for TrialEndUpsell {
4065    const KEY: &'static str = "dismissed-trial-end-upsell";
4066}