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