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