agent_panel.rs

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