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