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
1079        const LAST_USED_EXTERNAL_AGENT_KEY: &str = "agent_panel__last_used_external_agent";
1080
1081        #[derive(Default, Serialize, Deserialize)]
1082        struct LastUsedExternalAgent {
1083            agent: crate::ExternalAgent,
1084        }
1085
1086        let history = self.acp_history_store.clone();
1087
1088        cx.spawn_in(window, async move |this, cx| {
1089            let ext_agent = match agent_choice {
1090                Some(agent) => {
1091                    cx.background_spawn({
1092                        let agent = agent.clone();
1093                        async move {
1094                            if let Some(serialized) =
1095                                serde_json::to_string(&LastUsedExternalAgent { agent }).log_err()
1096                            {
1097                                KEY_VALUE_STORE
1098                                    .write_kvp(LAST_USED_EXTERNAL_AGENT_KEY.to_string(), serialized)
1099                                    .await
1100                                    .log_err();
1101                            }
1102                        }
1103                    })
1104                    .detach();
1105
1106                    agent
1107                }
1108                None => {
1109                    cx.background_spawn(async move {
1110                        KEY_VALUE_STORE.read_kvp(LAST_USED_EXTERNAL_AGENT_KEY)
1111                    })
1112                    .await
1113                    .log_err()
1114                    .flatten()
1115                    .and_then(|value| {
1116                        serde_json::from_str::<LastUsedExternalAgent>(&value).log_err()
1117                    })
1118                    .unwrap_or_default()
1119                    .agent
1120                }
1121            };
1122
1123            telemetry::event!("Agent Thread Started", agent = ext_agent.name());
1124
1125            let server = ext_agent.server(fs, history);
1126
1127            this.update_in(cx, |this, window, cx| {
1128                match ext_agent {
1129                    crate::ExternalAgent::Gemini
1130                    | crate::ExternalAgent::NativeAgent
1131                    | crate::ExternalAgent::Custom { .. } => {
1132                        if !cx.has_flag::<GeminiAndNativeFeatureFlag>() {
1133                            return;
1134                        }
1135                    }
1136                    crate::ExternalAgent::ClaudeCode => {
1137                        if !cx.has_flag::<ClaudeCodeFeatureFlag>() {
1138                            return;
1139                        }
1140                    }
1141                }
1142
1143                let thread_view = cx.new(|cx| {
1144                    crate::acp::AcpThreadView::new(
1145                        server,
1146                        resume_thread,
1147                        summarize_thread,
1148                        workspace.clone(),
1149                        project,
1150                        this.acp_history_store.clone(),
1151                        this.prompt_store.clone(),
1152                        window,
1153                        cx,
1154                    )
1155                });
1156
1157                this.set_active_view(ActiveView::ExternalAgentThread { thread_view }, window, cx);
1158            })
1159        })
1160        .detach_and_log_err(cx);
1161    }
1162
1163    fn deploy_rules_library(
1164        &mut self,
1165        action: &OpenRulesLibrary,
1166        _window: &mut Window,
1167        cx: &mut Context<Self>,
1168    ) {
1169        open_rules_library(
1170            self.language_registry.clone(),
1171            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
1172            Rc::new(|| {
1173                Rc::new(SlashCommandCompletionProvider::new(
1174                    Arc::new(SlashCommandWorkingSet::default()),
1175                    None,
1176                    None,
1177                ))
1178            }),
1179            action
1180                .prompt_to_select
1181                .map(|uuid| UserPromptId(uuid).into()),
1182            cx,
1183        )
1184        .detach_and_log_err(cx);
1185    }
1186
1187    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1188        if matches!(self.active_view, ActiveView::History) {
1189            if let Some(previous_view) = self.previous_view.take() {
1190                self.set_active_view(previous_view, window, cx);
1191            }
1192        } else {
1193            self.thread_store
1194                .update(cx, |thread_store, cx| thread_store.reload(cx))
1195                .detach_and_log_err(cx);
1196            self.set_active_view(ActiveView::History, window, cx);
1197        }
1198        cx.notify();
1199    }
1200
1201    pub(crate) fn open_saved_prompt_editor(
1202        &mut self,
1203        path: Arc<Path>,
1204        window: &mut Window,
1205        cx: &mut Context<Self>,
1206    ) -> Task<Result<()>> {
1207        let context = self
1208            .context_store
1209            .update(cx, |store, cx| store.open_local_context(path, cx));
1210        cx.spawn_in(window, async move |this, cx| {
1211            let context = context.await?;
1212            this.update_in(cx, |this, window, cx| {
1213                this.open_prompt_editor(context, window, cx);
1214            })
1215        })
1216    }
1217
1218    pub(crate) fn open_prompt_editor(
1219        &mut self,
1220        context: Entity<AssistantContext>,
1221        window: &mut Window,
1222        cx: &mut Context<Self>,
1223    ) {
1224        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project.clone(), cx)
1225            .log_err()
1226            .flatten();
1227        let editor = cx.new(|cx| {
1228            TextThreadEditor::for_context(
1229                context,
1230                self.fs.clone(),
1231                self.workspace.clone(),
1232                self.project.clone(),
1233                lsp_adapter_delegate,
1234                window,
1235                cx,
1236            )
1237        });
1238        self.set_active_view(
1239            ActiveView::prompt_editor(
1240                editor,
1241                self.history_store.clone(),
1242                self.acp_history_store.clone(),
1243                self.language_registry.clone(),
1244                window,
1245                cx,
1246            ),
1247            window,
1248            cx,
1249        );
1250    }
1251
1252    pub(crate) fn open_thread_by_id(
1253        &mut self,
1254        thread_id: &ThreadId,
1255        window: &mut Window,
1256        cx: &mut Context<Self>,
1257    ) -> Task<Result<()>> {
1258        let open_thread_task = self
1259            .thread_store
1260            .update(cx, |this, cx| this.open_thread(thread_id, window, cx));
1261        cx.spawn_in(window, async move |this, cx| {
1262            let thread = open_thread_task.await?;
1263            this.update_in(cx, |this, window, cx| {
1264                this.open_thread(thread, window, cx);
1265                anyhow::Ok(())
1266            })??;
1267            Ok(())
1268        })
1269    }
1270
1271    pub(crate) fn open_thread(
1272        &mut self,
1273        thread: Entity<Thread>,
1274        window: &mut Window,
1275        cx: &mut Context<Self>,
1276    ) {
1277        let context_store = cx.new(|_cx| {
1278            ContextStore::new(
1279                self.project.downgrade(),
1280                Some(self.thread_store.downgrade()),
1281            )
1282        });
1283
1284        let active_thread = cx.new(|cx| {
1285            ActiveThread::new(
1286                thread.clone(),
1287                self.thread_store.clone(),
1288                self.context_store.clone(),
1289                context_store.clone(),
1290                self.language_registry.clone(),
1291                self.workspace.clone(),
1292                window,
1293                cx,
1294            )
1295        });
1296
1297        let message_editor = cx.new(|cx| {
1298            MessageEditor::new(
1299                self.fs.clone(),
1300                self.workspace.clone(),
1301                context_store,
1302                self.prompt_store.clone(),
1303                self.thread_store.downgrade(),
1304                self.context_store.downgrade(),
1305                Some(self.history_store.downgrade()),
1306                thread.clone(),
1307                window,
1308                cx,
1309            )
1310        });
1311        message_editor.focus_handle(cx).focus(window);
1312
1313        let thread_view = ActiveView::thread(active_thread, message_editor, window, cx);
1314        self.set_active_view(thread_view, window, cx);
1315        AgentDiff::set_active_thread(&self.workspace, thread.clone(), window, cx);
1316    }
1317
1318    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
1319        match self.active_view {
1320            ActiveView::Configuration | ActiveView::History => {
1321                if let Some(previous_view) = self.previous_view.take() {
1322                    self.active_view = previous_view;
1323
1324                    match &self.active_view {
1325                        ActiveView::Thread { message_editor, .. } => {
1326                            message_editor.focus_handle(cx).focus(window);
1327                        }
1328                        ActiveView::ExternalAgentThread { thread_view } => {
1329                            thread_view.focus_handle(cx).focus(window);
1330                        }
1331                        ActiveView::TextThread { context_editor, .. } => {
1332                            context_editor.focus_handle(cx).focus(window);
1333                        }
1334                        ActiveView::History | ActiveView::Configuration => {}
1335                    }
1336                }
1337                cx.notify();
1338            }
1339            _ => {}
1340        }
1341    }
1342
1343    pub fn toggle_navigation_menu(
1344        &mut self,
1345        _: &ToggleNavigationMenu,
1346        window: &mut Window,
1347        cx: &mut Context<Self>,
1348    ) {
1349        self.assistant_navigation_menu_handle.toggle(window, cx);
1350    }
1351
1352    pub fn toggle_options_menu(
1353        &mut self,
1354        _: &ToggleOptionsMenu,
1355        window: &mut Window,
1356        cx: &mut Context<Self>,
1357    ) {
1358        self.agent_panel_menu_handle.toggle(window, cx);
1359    }
1360
1361    pub fn toggle_new_thread_menu(
1362        &mut self,
1363        _: &ToggleNewThreadMenu,
1364        window: &mut Window,
1365        cx: &mut Context<Self>,
1366    ) {
1367        self.new_thread_menu_handle.toggle(window, cx);
1368    }
1369
1370    pub fn increase_font_size(
1371        &mut self,
1372        action: &IncreaseBufferFontSize,
1373        _: &mut Window,
1374        cx: &mut Context<Self>,
1375    ) {
1376        self.handle_font_size_action(action.persist, px(1.0), cx);
1377    }
1378
1379    pub fn decrease_font_size(
1380        &mut self,
1381        action: &DecreaseBufferFontSize,
1382        _: &mut Window,
1383        cx: &mut Context<Self>,
1384    ) {
1385        self.handle_font_size_action(action.persist, px(-1.0), cx);
1386    }
1387
1388    fn handle_font_size_action(&mut self, persist: bool, delta: Pixels, cx: &mut Context<Self>) {
1389        match self.active_view.which_font_size_used() {
1390            WhichFontSize::AgentFont => {
1391                if persist {
1392                    update_settings_file::<ThemeSettings>(
1393                        self.fs.clone(),
1394                        cx,
1395                        move |settings, cx| {
1396                            let agent_font_size =
1397                                ThemeSettings::get_global(cx).agent_font_size(cx) + delta;
1398                            let _ = settings
1399                                .agent_font_size
1400                                .insert(Some(theme::clamp_font_size(agent_font_size).into()));
1401                        },
1402                    );
1403                } else {
1404                    theme::adjust_agent_font_size(cx, |size| size + delta);
1405                }
1406            }
1407            WhichFontSize::BufferFont => {
1408                // Prompt editor uses the buffer font size, so allow the action to propagate to the
1409                // default handler that changes that font size.
1410                cx.propagate();
1411            }
1412            WhichFontSize::None => {}
1413        }
1414    }
1415
1416    pub fn reset_font_size(
1417        &mut self,
1418        action: &ResetBufferFontSize,
1419        _: &mut Window,
1420        cx: &mut Context<Self>,
1421    ) {
1422        if action.persist {
1423            update_settings_file::<ThemeSettings>(self.fs.clone(), cx, move |settings, _| {
1424                settings.agent_font_size = None;
1425            });
1426        } else {
1427            theme::reset_agent_font_size(cx);
1428        }
1429    }
1430
1431    pub fn toggle_zoom(&mut self, _: &ToggleZoom, window: &mut Window, cx: &mut Context<Self>) {
1432        if self.zoomed {
1433            cx.emit(PanelEvent::ZoomOut);
1434        } else {
1435            if !self.focus_handle(cx).contains_focused(window, cx) {
1436                cx.focus_self(window);
1437            }
1438            cx.emit(PanelEvent::ZoomIn);
1439        }
1440    }
1441
1442    pub fn open_agent_diff(
1443        &mut self,
1444        _: &OpenAgentDiff,
1445        window: &mut Window,
1446        cx: &mut Context<Self>,
1447    ) {
1448        match &self.active_view {
1449            ActiveView::Thread { thread, .. } => {
1450                let thread = thread.read(cx).thread().clone();
1451                self.workspace
1452                    .update(cx, |workspace, cx| {
1453                        AgentDiffPane::deploy_in_workspace(
1454                            AgentDiffThread::Native(thread),
1455                            workspace,
1456                            window,
1457                            cx,
1458                        )
1459                    })
1460                    .log_err();
1461            }
1462            ActiveView::ExternalAgentThread { .. }
1463            | ActiveView::TextThread { .. }
1464            | ActiveView::History
1465            | ActiveView::Configuration => {}
1466        }
1467    }
1468
1469    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1470        let context_server_store = self.project.read(cx).context_server_store();
1471        let tools = self.thread_store.read(cx).tools();
1472        let fs = self.fs.clone();
1473
1474        self.set_active_view(ActiveView::Configuration, window, cx);
1475        self.configuration = Some(cx.new(|cx| {
1476            AgentConfiguration::new(
1477                fs,
1478                context_server_store,
1479                tools,
1480                self.language_registry.clone(),
1481                self.workspace.clone(),
1482                window,
1483                cx,
1484            )
1485        }));
1486
1487        if let Some(configuration) = self.configuration.as_ref() {
1488            self.configuration_subscription = Some(cx.subscribe_in(
1489                configuration,
1490                window,
1491                Self::handle_agent_configuration_event,
1492            ));
1493
1494            configuration.focus_handle(cx).focus(window);
1495        }
1496    }
1497
1498    pub(crate) fn open_active_thread_as_markdown(
1499        &mut self,
1500        _: &OpenActiveThreadAsMarkdown,
1501        window: &mut Window,
1502        cx: &mut Context<Self>,
1503    ) {
1504        let Some(workspace) = self.workspace.upgrade() else {
1505            return;
1506        };
1507
1508        match &self.active_view {
1509            ActiveView::Thread { thread, .. } => {
1510                active_thread::open_active_thread_as_markdown(
1511                    thread.read(cx).thread().clone(),
1512                    workspace,
1513                    window,
1514                    cx,
1515                )
1516                .detach_and_log_err(cx);
1517            }
1518            ActiveView::ExternalAgentThread { thread_view } => {
1519                thread_view
1520                    .update(cx, |thread_view, cx| {
1521                        thread_view.open_thread_as_markdown(workspace, window, cx)
1522                    })
1523                    .detach_and_log_err(cx);
1524            }
1525            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {}
1526        }
1527    }
1528
1529    fn handle_agent_configuration_event(
1530        &mut self,
1531        _entity: &Entity<AgentConfiguration>,
1532        event: &AssistantConfigurationEvent,
1533        window: &mut Window,
1534        cx: &mut Context<Self>,
1535    ) {
1536        match event {
1537            AssistantConfigurationEvent::NewThread(provider) => {
1538                if LanguageModelRegistry::read_global(cx)
1539                    .default_model()
1540                    .is_none_or(|model| model.provider.id() != provider.id())
1541                    && let Some(model) = provider.default_model(cx)
1542                {
1543                    update_settings_file::<AgentSettings>(
1544                        self.fs.clone(),
1545                        cx,
1546                        move |settings, _| settings.set_model(model),
1547                    );
1548                }
1549
1550                self.new_thread(&NewThread::default(), window, cx);
1551                if let Some((thread, model)) =
1552                    self.active_thread(cx).zip(provider.default_model(cx))
1553                {
1554                    thread.update(cx, |thread, cx| {
1555                        thread.set_configured_model(
1556                            Some(ConfiguredModel {
1557                                provider: provider.clone(),
1558                                model,
1559                            }),
1560                            cx,
1561                        );
1562                    });
1563                }
1564            }
1565        }
1566    }
1567
1568    pub(crate) fn active_thread(&self, cx: &App) -> Option<Entity<Thread>> {
1569        match &self.active_view {
1570            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
1571            _ => None,
1572        }
1573    }
1574    pub(crate) fn active_agent_thread(&self, cx: &App) -> Option<Entity<AcpThread>> {
1575        match &self.active_view {
1576            ActiveView::ExternalAgentThread { thread_view, .. } => {
1577                thread_view.read(cx).thread().cloned()
1578            }
1579            _ => None,
1580        }
1581    }
1582
1583    pub(crate) fn delete_thread(
1584        &mut self,
1585        thread_id: &ThreadId,
1586        cx: &mut Context<Self>,
1587    ) -> Task<Result<()>> {
1588        self.thread_store
1589            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
1590    }
1591
1592    fn continue_conversation(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1593        let ActiveView::Thread { thread, .. } = &self.active_view else {
1594            return;
1595        };
1596
1597        let thread_state = thread.read(cx).thread().read(cx);
1598        if !thread_state.tool_use_limit_reached() {
1599            return;
1600        }
1601
1602        let model = thread_state.configured_model().map(|cm| cm.model);
1603        if let Some(model) = model {
1604            thread.update(cx, |active_thread, cx| {
1605                active_thread.thread().update(cx, |thread, cx| {
1606                    thread.insert_invisible_continue_message(cx);
1607                    thread.advance_prompt_id();
1608                    thread.send_to_model(
1609                        model,
1610                        CompletionIntent::UserPrompt,
1611                        Some(window.window_handle()),
1612                        cx,
1613                    );
1614                });
1615            });
1616        } else {
1617            log::warn!("No configured model available for continuation");
1618        }
1619    }
1620
1621    fn toggle_burn_mode(
1622        &mut self,
1623        _: &ToggleBurnMode,
1624        _window: &mut Window,
1625        cx: &mut Context<Self>,
1626    ) {
1627        let ActiveView::Thread { thread, .. } = &self.active_view else {
1628            return;
1629        };
1630
1631        thread.update(cx, |active_thread, cx| {
1632            active_thread.thread().update(cx, |thread, _cx| {
1633                let current_mode = thread.completion_mode();
1634
1635                thread.set_completion_mode(match current_mode {
1636                    CompletionMode::Burn => CompletionMode::Normal,
1637                    CompletionMode::Normal => CompletionMode::Burn,
1638                });
1639            });
1640        });
1641    }
1642
1643    pub(crate) fn active_context_editor(&self) -> Option<Entity<TextThreadEditor>> {
1644        match &self.active_view {
1645            ActiveView::TextThread { context_editor, .. } => Some(context_editor.clone()),
1646            _ => None,
1647        }
1648    }
1649
1650    pub(crate) fn delete_context(
1651        &mut self,
1652        path: Arc<Path>,
1653        cx: &mut Context<Self>,
1654    ) -> Task<Result<()>> {
1655        self.context_store
1656            .update(cx, |this, cx| this.delete_local_context(path, cx))
1657    }
1658
1659    fn set_active_view(
1660        &mut self,
1661        new_view: ActiveView,
1662        window: &mut Window,
1663        cx: &mut Context<Self>,
1664    ) {
1665        let current_is_history = matches!(self.active_view, ActiveView::History);
1666        let new_is_history = matches!(new_view, ActiveView::History);
1667
1668        let current_is_config = matches!(self.active_view, ActiveView::Configuration);
1669        let new_is_config = matches!(new_view, ActiveView::Configuration);
1670
1671        let current_is_special = current_is_history || current_is_config;
1672        let new_is_special = new_is_history || new_is_config;
1673
1674        if let ActiveView::Thread { thread, .. } = &self.active_view {
1675            let thread = thread.read(cx);
1676            if thread.is_empty() {
1677                let id = thread.thread().read(cx).id().clone();
1678                self.history_store.update(cx, |store, cx| {
1679                    store.remove_recently_opened_thread(id, cx);
1680                });
1681            }
1682        }
1683
1684        match &new_view {
1685            ActiveView::Thread { thread, .. } => self.history_store.update(cx, |store, cx| {
1686                let id = thread.read(cx).thread().read(cx).id().clone();
1687                store.push_recently_opened_entry(HistoryEntryId::Thread(id), cx);
1688            }),
1689            ActiveView::TextThread { context_editor, .. } => {
1690                self.history_store.update(cx, |store, cx| {
1691                    if let Some(path) = context_editor.read(cx).context().read(cx).path() {
1692                        store.push_recently_opened_entry(HistoryEntryId::Context(path.clone()), cx)
1693                    }
1694                });
1695                self.acp_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(
1698                            agent2::HistoryEntryId::TextThread(path.clone()),
1699                            cx,
1700                        )
1701                    }
1702                })
1703            }
1704            ActiveView::ExternalAgentThread { .. } => {}
1705            ActiveView::History | ActiveView::Configuration => {}
1706        }
1707
1708        if current_is_special && !new_is_special {
1709            self.active_view = new_view;
1710        } else if !current_is_special && new_is_special {
1711            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
1712        } else {
1713            if !new_is_special {
1714                self.previous_view = None;
1715            }
1716            self.active_view = new_view;
1717        }
1718
1719        self.focus_handle(cx).focus(window);
1720    }
1721
1722    fn populate_recently_opened_menu_section_old(
1723        mut menu: ContextMenu,
1724        panel: Entity<Self>,
1725        cx: &mut Context<ContextMenu>,
1726    ) -> ContextMenu {
1727        let entries = panel
1728            .read(cx)
1729            .history_store
1730            .read(cx)
1731            .recently_opened_entries(cx);
1732
1733        if entries.is_empty() {
1734            return menu;
1735        }
1736
1737        menu = menu.header("Recently Opened");
1738
1739        for entry in entries {
1740            let title = entry.title().clone();
1741            let id = entry.id();
1742
1743            menu = menu.entry_with_end_slot_on_hover(
1744                title,
1745                None,
1746                {
1747                    let panel = panel.downgrade();
1748                    let id = id.clone();
1749                    move |window, cx| {
1750                        let id = id.clone();
1751                        panel
1752                            .update(cx, move |this, cx| match id {
1753                                HistoryEntryId::Thread(id) => this
1754                                    .open_thread_by_id(&id, window, cx)
1755                                    .detach_and_log_err(cx),
1756                                HistoryEntryId::Context(path) => this
1757                                    .open_saved_prompt_editor(path, window, cx)
1758                                    .detach_and_log_err(cx),
1759                            })
1760                            .ok();
1761                    }
1762                },
1763                IconName::Close,
1764                "Close Entry".into(),
1765                {
1766                    let panel = panel.downgrade();
1767                    let id = id.clone();
1768                    move |_window, cx| {
1769                        panel
1770                            .update(cx, |this, cx| {
1771                                this.history_store.update(cx, |history_store, cx| {
1772                                    history_store.remove_recently_opened_entry(&id, cx);
1773                                });
1774                            })
1775                            .ok();
1776                    }
1777                },
1778            );
1779        }
1780
1781        menu = menu.separator();
1782
1783        menu
1784    }
1785
1786    fn populate_recently_opened_menu_section_new(
1787        mut menu: ContextMenu,
1788        panel: Entity<Self>,
1789        cx: &mut Context<ContextMenu>,
1790    ) -> ContextMenu {
1791        let entries = panel
1792            .read(cx)
1793            .acp_history_store
1794            .read(cx)
1795            .recently_opened_entries(cx);
1796
1797        if entries.is_empty() {
1798            return menu;
1799        }
1800
1801        menu = menu.header("Recently Opened");
1802
1803        for entry in entries {
1804            let title = entry.title().clone();
1805
1806            menu = menu.entry_with_end_slot_on_hover(
1807                title,
1808                None,
1809                {
1810                    let panel = panel.downgrade();
1811                    let entry = entry.clone();
1812                    move |window, cx| {
1813                        let entry = entry.clone();
1814                        panel
1815                            .update(cx, move |this, cx| match &entry {
1816                                agent2::HistoryEntry::AcpThread(entry) => this.external_thread(
1817                                    Some(ExternalAgent::NativeAgent),
1818                                    Some(entry.clone()),
1819                                    None,
1820                                    window,
1821                                    cx,
1822                                ),
1823                                agent2::HistoryEntry::TextThread(entry) => this
1824                                    .open_saved_prompt_editor(entry.path.clone(), window, cx)
1825                                    .detach_and_log_err(cx),
1826                            })
1827                            .ok();
1828                    }
1829                },
1830                IconName::Close,
1831                "Close Entry".into(),
1832                {
1833                    let panel = panel.downgrade();
1834                    let id = entry.id();
1835                    move |_window, cx| {
1836                        panel
1837                            .update(cx, |this, cx| {
1838                                this.acp_history_store.update(cx, |history_store, cx| {
1839                                    history_store.remove_recently_opened_entry(&id, cx);
1840                                });
1841                            })
1842                            .ok();
1843                    }
1844                },
1845            );
1846        }
1847
1848        menu = menu.separator();
1849
1850        menu
1851    }
1852
1853    pub fn selected_agent(&self) -> AgentType {
1854        self.selected_agent.clone()
1855    }
1856
1857    pub fn new_agent_thread(
1858        &mut self,
1859        agent: AgentType,
1860        window: &mut Window,
1861        cx: &mut Context<Self>,
1862    ) {
1863        if self.selected_agent != agent {
1864            self.selected_agent = agent.clone();
1865            self.serialize(cx);
1866        }
1867
1868        match agent {
1869            AgentType::Zed => {
1870                window.dispatch_action(
1871                    NewThread {
1872                        from_thread_id: None,
1873                    }
1874                    .boxed_clone(),
1875                    cx,
1876                );
1877            }
1878            AgentType::TextThread => {
1879                window.dispatch_action(NewTextThread.boxed_clone(), cx);
1880            }
1881            AgentType::NativeAgent => self.external_thread(
1882                Some(crate::ExternalAgent::NativeAgent),
1883                None,
1884                None,
1885                window,
1886                cx,
1887            ),
1888            AgentType::Gemini => {
1889                self.external_thread(Some(crate::ExternalAgent::Gemini), None, None, window, cx)
1890            }
1891            AgentType::ClaudeCode => {
1892                self.selected_agent = AgentType::ClaudeCode;
1893                self.serialize(cx);
1894                self.external_thread(
1895                    Some(crate::ExternalAgent::ClaudeCode),
1896                    None,
1897                    None,
1898                    window,
1899                    cx,
1900                )
1901            }
1902            AgentType::Custom { name, command } => self.external_thread(
1903                Some(crate::ExternalAgent::Custom { name, command }),
1904                None,
1905                None,
1906                window,
1907                cx,
1908            ),
1909        }
1910    }
1911
1912    pub fn load_agent_thread(
1913        &mut self,
1914        thread: DbThreadMetadata,
1915        window: &mut Window,
1916        cx: &mut Context<Self>,
1917    ) {
1918        self.external_thread(
1919            Some(ExternalAgent::NativeAgent),
1920            Some(thread),
1921            None,
1922            window,
1923            cx,
1924        );
1925    }
1926}
1927
1928impl Focusable for AgentPanel {
1929    fn focus_handle(&self, cx: &App) -> FocusHandle {
1930        match &self.active_view {
1931            ActiveView::Thread { message_editor, .. } => message_editor.focus_handle(cx),
1932            ActiveView::ExternalAgentThread { thread_view, .. } => thread_view.focus_handle(cx),
1933            ActiveView::History => {
1934                if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>() {
1935                    self.acp_history.focus_handle(cx)
1936                } else {
1937                    self.history.focus_handle(cx)
1938                }
1939            }
1940            ActiveView::TextThread { context_editor, .. } => context_editor.focus_handle(cx),
1941            ActiveView::Configuration => {
1942                if let Some(configuration) = self.configuration.as_ref() {
1943                    configuration.focus_handle(cx)
1944                } else {
1945                    cx.focus_handle()
1946                }
1947            }
1948        }
1949    }
1950}
1951
1952fn agent_panel_dock_position(cx: &App) -> DockPosition {
1953    match AgentSettings::get_global(cx).dock {
1954        AgentDockPosition::Left => DockPosition::Left,
1955        AgentDockPosition::Bottom => DockPosition::Bottom,
1956        AgentDockPosition::Right => DockPosition::Right,
1957    }
1958}
1959
1960impl EventEmitter<PanelEvent> for AgentPanel {}
1961
1962impl Panel for AgentPanel {
1963    fn persistent_name() -> &'static str {
1964        "AgentPanel"
1965    }
1966
1967    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1968        agent_panel_dock_position(cx)
1969    }
1970
1971    fn position_is_valid(&self, position: DockPosition) -> bool {
1972        position != DockPosition::Bottom
1973    }
1974
1975    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1976        settings::update_settings_file::<AgentSettings>(self.fs.clone(), cx, move |settings, _| {
1977            let dock = match position {
1978                DockPosition::Left => AgentDockPosition::Left,
1979                DockPosition::Bottom => AgentDockPosition::Bottom,
1980                DockPosition::Right => AgentDockPosition::Right,
1981            };
1982            settings.set_dock(dock);
1983        });
1984    }
1985
1986    fn size(&self, window: &Window, cx: &App) -> Pixels {
1987        let settings = AgentSettings::get_global(cx);
1988        match self.position(window, cx) {
1989            DockPosition::Left | DockPosition::Right => {
1990                self.width.unwrap_or(settings.default_width)
1991            }
1992            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1993        }
1994    }
1995
1996    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1997        match self.position(window, cx) {
1998            DockPosition::Left | DockPosition::Right => self.width = size,
1999            DockPosition::Bottom => self.height = size,
2000        }
2001        self.serialize(cx);
2002        cx.notify();
2003    }
2004
2005    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
2006
2007    fn remote_id() -> Option<proto::PanelId> {
2008        Some(proto::PanelId::AssistantPanel)
2009    }
2010
2011    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
2012        (self.enabled(cx) && AgentSettings::get_global(cx).button).then_some(IconName::ZedAssistant)
2013    }
2014
2015    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
2016        Some("Agent Panel")
2017    }
2018
2019    fn toggle_action(&self) -> Box<dyn Action> {
2020        Box::new(ToggleFocus)
2021    }
2022
2023    fn activation_priority(&self) -> u32 {
2024        3
2025    }
2026
2027    fn enabled(&self, cx: &App) -> bool {
2028        DisableAiSettings::get_global(cx).disable_ai.not() && AgentSettings::get_global(cx).enabled
2029    }
2030
2031    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
2032        self.zoomed
2033    }
2034
2035    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
2036        self.zoomed = zoomed;
2037        cx.notify();
2038    }
2039}
2040
2041impl AgentPanel {
2042    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
2043        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
2044
2045        let content = match &self.active_view {
2046            ActiveView::Thread {
2047                thread: active_thread,
2048                change_title_editor,
2049                ..
2050            } => {
2051                let state = {
2052                    let active_thread = active_thread.read(cx);
2053                    if active_thread.is_empty() {
2054                        &ThreadSummary::Pending
2055                    } else {
2056                        active_thread.summary(cx)
2057                    }
2058                };
2059
2060                match state {
2061                    ThreadSummary::Pending => Label::new(ThreadSummary::DEFAULT)
2062                        .truncate()
2063                        .color(Color::Muted)
2064                        .into_any_element(),
2065                    ThreadSummary::Generating => Label::new(LOADING_SUMMARY_PLACEHOLDER)
2066                        .truncate()
2067                        .color(Color::Muted)
2068                        .into_any_element(),
2069                    ThreadSummary::Ready(_) => div()
2070                        .w_full()
2071                        .child(change_title_editor.clone())
2072                        .into_any_element(),
2073                    ThreadSummary::Error => h_flex()
2074                        .w_full()
2075                        .child(change_title_editor.clone())
2076                        .child(
2077                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
2078                                .icon_size(IconSize::Small)
2079                                .on_click({
2080                                    let active_thread = active_thread.clone();
2081                                    move |_, _window, cx| {
2082                                        active_thread.update(cx, |thread, cx| {
2083                                            thread.regenerate_summary(cx);
2084                                        });
2085                                    }
2086                                })
2087                                .tooltip(move |_window, cx| {
2088                                    cx.new(|_| {
2089                                        Tooltip::new("Failed to generate title")
2090                                            .meta("Click to try again")
2091                                    })
2092                                    .into()
2093                                }),
2094                        )
2095                        .into_any_element(),
2096                }
2097            }
2098            ActiveView::ExternalAgentThread { thread_view } => {
2099                if let Some(title_editor) = thread_view.read(cx).title_editor() {
2100                    div()
2101                        .w_full()
2102                        .on_action({
2103                            let thread_view = thread_view.downgrade();
2104                            move |_: &menu::Confirm, window, cx| {
2105                                if let Some(thread_view) = thread_view.upgrade() {
2106                                    thread_view.focus_handle(cx).focus(window);
2107                                }
2108                            }
2109                        })
2110                        .on_action({
2111                            let thread_view = thread_view.downgrade();
2112                            move |_: &editor::actions::Cancel, window, cx| {
2113                                if let Some(thread_view) = thread_view.upgrade() {
2114                                    thread_view.focus_handle(cx).focus(window);
2115                                }
2116                            }
2117                        })
2118                        .child(title_editor)
2119                        .into_any_element()
2120                } else {
2121                    Label::new(thread_view.read(cx).title(cx))
2122                        .color(Color::Muted)
2123                        .truncate()
2124                        .into_any_element()
2125                }
2126            }
2127            ActiveView::TextThread {
2128                title_editor,
2129                context_editor,
2130                ..
2131            } => {
2132                let summary = context_editor.read(cx).context().read(cx).summary();
2133
2134                match summary {
2135                    ContextSummary::Pending => Label::new(ContextSummary::DEFAULT)
2136                        .color(Color::Muted)
2137                        .truncate()
2138                        .into_any_element(),
2139                    ContextSummary::Content(summary) => {
2140                        if summary.done {
2141                            div()
2142                                .w_full()
2143                                .child(title_editor.clone())
2144                                .into_any_element()
2145                        } else {
2146                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
2147                                .truncate()
2148                                .color(Color::Muted)
2149                                .into_any_element()
2150                        }
2151                    }
2152                    ContextSummary::Error => h_flex()
2153                        .w_full()
2154                        .child(title_editor.clone())
2155                        .child(
2156                            IconButton::new("retry-summary-generation", IconName::RotateCcw)
2157                                .icon_size(IconSize::Small)
2158                                .on_click({
2159                                    let context_editor = context_editor.clone();
2160                                    move |_, _window, cx| {
2161                                        context_editor.update(cx, |context_editor, cx| {
2162                                            context_editor.regenerate_summary(cx);
2163                                        });
2164                                    }
2165                                })
2166                                .tooltip(move |_window, cx| {
2167                                    cx.new(|_| {
2168                                        Tooltip::new("Failed to generate title")
2169                                            .meta("Click to try again")
2170                                    })
2171                                    .into()
2172                                }),
2173                        )
2174                        .into_any_element(),
2175                }
2176            }
2177            ActiveView::History => Label::new("History").truncate().into_any_element(),
2178            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
2179        };
2180
2181        h_flex()
2182            .key_context("TitleEditor")
2183            .id("TitleEditor")
2184            .flex_grow()
2185            .w_full()
2186            .max_w_full()
2187            .overflow_x_scroll()
2188            .child(content)
2189            .into_any()
2190    }
2191
2192    fn render_panel_options_menu(
2193        &self,
2194        window: &mut Window,
2195        cx: &mut Context<Self>,
2196    ) -> impl IntoElement {
2197        let user_store = self.user_store.read(cx);
2198        let usage = user_store.model_request_usage();
2199        let account_url = zed_urls::account_url(cx);
2200
2201        let focus_handle = self.focus_handle(cx);
2202
2203        let full_screen_label = if self.is_zoomed(window, cx) {
2204            "Disable Full Screen"
2205        } else {
2206            "Enable Full Screen"
2207        };
2208
2209        let selected_agent = self.selected_agent.clone();
2210
2211        PopoverMenu::new("agent-options-menu")
2212            .trigger_with_tooltip(
2213                IconButton::new("agent-options-menu", IconName::Ellipsis)
2214                    .icon_size(IconSize::Small),
2215                {
2216                    let focus_handle = focus_handle.clone();
2217                    move |window, cx| {
2218                        Tooltip::for_action_in(
2219                            "Toggle Agent Menu",
2220                            &ToggleOptionsMenu,
2221                            &focus_handle,
2222                            window,
2223                            cx,
2224                        )
2225                    }
2226                },
2227            )
2228            .anchor(Corner::TopRight)
2229            .with_handle(self.agent_panel_menu_handle.clone())
2230            .menu({
2231                move |window, cx| {
2232                    Some(ContextMenu::build(window, cx, |mut menu, _window, _| {
2233                        menu = menu.context(focus_handle.clone());
2234                        if let Some(usage) = usage {
2235                            menu = menu
2236                                .header_with_link("Prompt Usage", "Manage", account_url.clone())
2237                                .custom_entry(
2238                                    move |_window, cx| {
2239                                        let used_percentage = match usage.limit {
2240                                            UsageLimit::Limited(limit) => {
2241                                                Some((usage.amount as f32 / limit as f32) * 100.)
2242                                            }
2243                                            UsageLimit::Unlimited => None,
2244                                        };
2245
2246                                        h_flex()
2247                                            .flex_1()
2248                                            .gap_1p5()
2249                                            .children(used_percentage.map(|percent| {
2250                                                ProgressBar::new("usage", percent, 100., cx)
2251                                            }))
2252                                            .child(
2253                                                Label::new(match usage.limit {
2254                                                    UsageLimit::Limited(limit) => {
2255                                                        format!("{} / {limit}", usage.amount)
2256                                                    }
2257                                                    UsageLimit::Unlimited => {
2258                                                        format!("{} / ∞", usage.amount)
2259                                                    }
2260                                                })
2261                                                .size(LabelSize::Small)
2262                                                .color(Color::Muted),
2263                                            )
2264                                            .into_any_element()
2265                                    },
2266                                    move |_, cx| cx.open_url(&zed_urls::account_url(cx)),
2267                                )
2268                                .separator()
2269                        }
2270
2271                        menu = menu
2272                            .header("MCP Servers")
2273                            .action(
2274                                "View Server Extensions",
2275                                Box::new(zed_actions::Extensions {
2276                                    category_filter: Some(
2277                                        zed_actions::ExtensionCategoryFilter::ContextServers,
2278                                    ),
2279                                    id: None,
2280                                }),
2281                            )
2282                            .action("Add Custom Server…", Box::new(AddContextServer))
2283                            .separator();
2284
2285                        menu = menu
2286                            .action("Rules…", Box::new(OpenRulesLibrary::default()))
2287                            .action("Settings", Box::new(OpenSettings))
2288                            .separator()
2289                            .action(full_screen_label, Box::new(ToggleZoom));
2290
2291                        if selected_agent == AgentType::Gemini {
2292                            menu = menu.action("Reauthenticate", Box::new(ReauthenticateAgent))
2293                        }
2294
2295                        menu
2296                    }))
2297                }
2298            })
2299    }
2300
2301    fn render_recent_entries_menu(
2302        &self,
2303        icon: IconName,
2304        corner: Corner,
2305        cx: &mut Context<Self>,
2306    ) -> impl IntoElement {
2307        let focus_handle = self.focus_handle(cx);
2308
2309        PopoverMenu::new("agent-nav-menu")
2310            .trigger_with_tooltip(
2311                IconButton::new("agent-nav-menu", icon).icon_size(IconSize::Small),
2312                {
2313                    move |window, cx| {
2314                        Tooltip::for_action_in(
2315                            "Toggle Recent Threads",
2316                            &ToggleNavigationMenu,
2317                            &focus_handle,
2318                            window,
2319                            cx,
2320                        )
2321                    }
2322                },
2323            )
2324            .anchor(corner)
2325            .with_handle(self.assistant_navigation_menu_handle.clone())
2326            .menu({
2327                let menu = self.assistant_navigation_menu.clone();
2328                move |window, cx| {
2329                    telemetry::event!("View Thread History Clicked");
2330
2331                    if let Some(menu) = menu.as_ref() {
2332                        menu.update(cx, |_, cx| {
2333                            cx.defer_in(window, |menu, window, cx| {
2334                                menu.rebuild(window, cx);
2335                            });
2336                        })
2337                    }
2338                    menu.clone()
2339                }
2340            })
2341    }
2342
2343    fn render_toolbar_back_button(&self, cx: &mut Context<Self>) -> impl IntoElement {
2344        let focus_handle = self.focus_handle(cx);
2345
2346        IconButton::new("go-back", IconName::ArrowLeft)
2347            .icon_size(IconSize::Small)
2348            .on_click(cx.listener(|this, _, window, cx| {
2349                this.go_back(&workspace::GoBack, window, cx);
2350            }))
2351            .tooltip({
2352                move |window, cx| {
2353                    Tooltip::for_action_in("Go Back", &workspace::GoBack, &focus_handle, window, cx)
2354                }
2355            })
2356    }
2357
2358    fn render_toolbar_old(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2359        let focus_handle = self.focus_handle(cx);
2360
2361        let active_thread = match &self.active_view {
2362            ActiveView::Thread { thread, .. } => Some(thread.read(cx).thread().clone()),
2363            ActiveView::ExternalAgentThread { .. }
2364            | ActiveView::TextThread { .. }
2365            | ActiveView::History
2366            | ActiveView::Configuration => None,
2367        };
2368
2369        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2370            .trigger_with_tooltip(
2371                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2372                Tooltip::text("New Thread…"),
2373            )
2374            .anchor(Corner::TopRight)
2375            .with_handle(self.new_thread_menu_handle.clone())
2376            .menu({
2377                move |window, cx| {
2378                    let active_thread = active_thread.clone();
2379                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2380                        menu = menu
2381                            .context(focus_handle.clone())
2382                            .when_some(active_thread, |this, active_thread| {
2383                                let thread = active_thread.read(cx);
2384
2385                                if !thread.is_empty() {
2386                                    let thread_id = thread.id().clone();
2387                                    this.item(
2388                                        ContextMenuEntry::new("New From Summary")
2389                                            .icon(IconName::ThreadFromSummary)
2390                                            .icon_color(Color::Muted)
2391                                            .handler(move |window, cx| {
2392                                                window.dispatch_action(
2393                                                    Box::new(NewThread {
2394                                                        from_thread_id: Some(thread_id.clone()),
2395                                                    }),
2396                                                    cx,
2397                                                );
2398                                            }),
2399                                    )
2400                                } else {
2401                                    this
2402                                }
2403                            })
2404                            .item(
2405                                ContextMenuEntry::new("New Thread")
2406                                    .icon(IconName::Thread)
2407                                    .icon_color(Color::Muted)
2408                                    .action(NewThread::default().boxed_clone())
2409                                    .handler(move |window, cx| {
2410                                        window.dispatch_action(
2411                                            NewThread::default().boxed_clone(),
2412                                            cx,
2413                                        );
2414                                    }),
2415                            )
2416                            .item(
2417                                ContextMenuEntry::new("New Text Thread")
2418                                    .icon(IconName::TextThread)
2419                                    .icon_color(Color::Muted)
2420                                    .action(NewTextThread.boxed_clone())
2421                                    .handler(move |window, cx| {
2422                                        window.dispatch_action(NewTextThread.boxed_clone(), cx);
2423                                    }),
2424                            );
2425                        menu
2426                    }))
2427                }
2428            });
2429
2430        h_flex()
2431            .id("assistant-toolbar")
2432            .h(Tab::container_height(cx))
2433            .max_w_full()
2434            .flex_none()
2435            .justify_between()
2436            .gap_2()
2437            .bg(cx.theme().colors().tab_bar_background)
2438            .border_b_1()
2439            .border_color(cx.theme().colors().border)
2440            .child(
2441                h_flex()
2442                    .size_full()
2443                    .pl_1()
2444                    .gap_1()
2445                    .child(match &self.active_view {
2446                        ActiveView::History | ActiveView::Configuration => div()
2447                            .pl(DynamicSpacing::Base04.rems(cx))
2448                            .child(self.render_toolbar_back_button(cx))
2449                            .into_any_element(),
2450                        _ => self
2451                            .render_recent_entries_menu(IconName::MenuAlt, Corner::TopLeft, cx)
2452                            .into_any_element(),
2453                    })
2454                    .child(self.render_title_view(window, cx)),
2455            )
2456            .child(
2457                h_flex()
2458                    .h_full()
2459                    .gap_2()
2460                    .children(self.render_token_count(cx))
2461                    .child(
2462                        h_flex()
2463                            .h_full()
2464                            .gap(DynamicSpacing::Base02.rems(cx))
2465                            .px(DynamicSpacing::Base08.rems(cx))
2466                            .border_l_1()
2467                            .border_color(cx.theme().colors().border)
2468                            .child(new_thread_menu)
2469                            .child(self.render_panel_options_menu(window, cx)),
2470                    ),
2471            )
2472    }
2473
2474    fn render_toolbar_new(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2475        let focus_handle = self.focus_handle(cx);
2476
2477        let active_thread = match &self.active_view {
2478            ActiveView::ExternalAgentThread { thread_view } => {
2479                thread_view.read(cx).as_native_thread(cx)
2480            }
2481            ActiveView::Thread { .. }
2482            | ActiveView::TextThread { .. }
2483            | ActiveView::History
2484            | ActiveView::Configuration => None,
2485        };
2486
2487        let new_thread_menu = PopoverMenu::new("new_thread_menu")
2488            .trigger_with_tooltip(
2489                IconButton::new("new_thread_menu_btn", IconName::Plus).icon_size(IconSize::Small),
2490                {
2491                    let focus_handle = focus_handle.clone();
2492                    move |window, cx| {
2493                        Tooltip::for_action_in(
2494                            "New…",
2495                            &ToggleNewThreadMenu,
2496                            &focus_handle,
2497                            window,
2498                            cx,
2499                        )
2500                    }
2501                },
2502            )
2503            .anchor(Corner::TopLeft)
2504            .with_handle(self.new_thread_menu_handle.clone())
2505            .menu({
2506                let workspace = self.workspace.clone();
2507
2508                move |window, cx| {
2509                    telemetry::event!("New Thread Clicked");
2510
2511                    let active_thread = active_thread.clone();
2512                    Some(ContextMenu::build(window, cx, |mut menu, _window, cx| {
2513                        menu = menu
2514                            .context(focus_handle.clone())
2515                            .header("Zed Agent")
2516                            .when_some(active_thread, |this, active_thread| {
2517                                let thread = active_thread.read(cx);
2518
2519                                if !thread.is_empty() {
2520                                    let session_id = thread.id().clone();
2521                                    this.item(
2522                                        ContextMenuEntry::new("New From Summary")
2523                                            .icon(IconName::ThreadFromSummary)
2524                                            .icon_color(Color::Muted)
2525                                            .handler(move |window, cx| {
2526                                                window.dispatch_action(
2527                                                    Box::new(NewNativeAgentThreadFromSummary {
2528                                                        from_session_id: session_id.clone(),
2529                                                    }),
2530                                                    cx,
2531                                                );
2532                                            }),
2533                                    )
2534                                } else {
2535                                    this
2536                                }
2537                            })
2538                            .item(
2539                                ContextMenuEntry::new("New Thread")
2540                                    .action(NewThread::default().boxed_clone())
2541                                    .icon(IconName::Thread)
2542                                    .icon_color(Color::Muted)
2543                                    .handler({
2544                                        let workspace = workspace.clone();
2545                                        move |window, cx| {
2546                                            if let Some(workspace) = workspace.upgrade() {
2547                                                workspace.update(cx, |workspace, cx| {
2548                                                    if let Some(panel) =
2549                                                        workspace.panel::<AgentPanel>(cx)
2550                                                    {
2551                                                        panel.update(cx, |panel, cx| {
2552                                                            panel.new_agent_thread(
2553                                                                AgentType::NativeAgent,
2554                                                                window,
2555                                                                cx,
2556                                                            );
2557                                                        });
2558                                                    }
2559                                                });
2560                                            }
2561                                        }
2562                                    }),
2563                            )
2564                            .item(
2565                                ContextMenuEntry::new("New Text Thread")
2566                                    .icon(IconName::TextThread)
2567                                    .icon_color(Color::Muted)
2568                                    .action(NewTextThread.boxed_clone())
2569                                    .handler({
2570                                        let workspace = workspace.clone();
2571                                        move |window, cx| {
2572                                            if let Some(workspace) = workspace.upgrade() {
2573                                                workspace.update(cx, |workspace, cx| {
2574                                                    if let Some(panel) =
2575                                                        workspace.panel::<AgentPanel>(cx)
2576                                                    {
2577                                                        panel.update(cx, |panel, cx| {
2578                                                            panel.new_agent_thread(
2579                                                                AgentType::TextThread,
2580                                                                window,
2581                                                                cx,
2582                                                            );
2583                                                        });
2584                                                    }
2585                                                });
2586                                            }
2587                                        }
2588                                    }),
2589                            )
2590                            .separator()
2591                            .header("External Agents")
2592                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |menu| {
2593                                menu.item(
2594                                    ContextMenuEntry::new("New Gemini CLI Thread")
2595                                        .icon(IconName::AiGemini)
2596                                        .icon_color(Color::Muted)
2597                                        .handler({
2598                                            let workspace = workspace.clone();
2599                                            move |window, cx| {
2600                                                if let Some(workspace) = workspace.upgrade() {
2601                                                    workspace.update(cx, |workspace, cx| {
2602                                                        if let Some(panel) =
2603                                                            workspace.panel::<AgentPanel>(cx)
2604                                                        {
2605                                                            panel.update(cx, |panel, cx| {
2606                                                                panel.new_agent_thread(
2607                                                                    AgentType::Gemini,
2608                                                                    window,
2609                                                                    cx,
2610                                                                );
2611                                                            });
2612                                                        }
2613                                                    });
2614                                                }
2615                                            }
2616                                        }),
2617                                )
2618                            })
2619                            .when(cx.has_flag::<ClaudeCodeFeatureFlag>(), |menu| {
2620                                menu.item(
2621                                    ContextMenuEntry::new("New Claude Code Thread")
2622                                        .icon(IconName::AiClaude)
2623                                        .icon_color(Color::Muted)
2624                                        .handler({
2625                                            let workspace = workspace.clone();
2626                                            move |window, cx| {
2627                                                if let Some(workspace) = workspace.upgrade() {
2628                                                    workspace.update(cx, |workspace, cx| {
2629                                                        if let Some(panel) =
2630                                                            workspace.panel::<AgentPanel>(cx)
2631                                                        {
2632                                                            panel.update(cx, |panel, cx| {
2633                                                                panel.new_agent_thread(
2634                                                                    AgentType::ClaudeCode,
2635                                                                    window,
2636                                                                    cx,
2637                                                                );
2638                                                            });
2639                                                        }
2640                                                    });
2641                                                }
2642                                            }
2643                                        }),
2644                                )
2645                            })
2646                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |mut menu| {
2647                                // Add custom agents from settings
2648                                let settings =
2649                                    agent_servers::AllAgentServersSettings::get_global(cx);
2650                                for (agent_name, agent_settings) in &settings.custom {
2651                                    menu = menu.item(
2652                                        ContextMenuEntry::new(format!("New {} Thread", agent_name))
2653                                            .icon(IconName::Terminal)
2654                                            .icon_color(Color::Muted)
2655                                            .handler({
2656                                                let workspace = workspace.clone();
2657                                                let agent_name = agent_name.clone();
2658                                                let agent_settings = agent_settings.clone();
2659                                                move |window, cx| {
2660                                                    if let Some(workspace) = workspace.upgrade() {
2661                                                        workspace.update(cx, |workspace, cx| {
2662                                                            if let Some(panel) =
2663                                                                workspace.panel::<AgentPanel>(cx)
2664                                                            {
2665                                                                panel.update(cx, |panel, cx| {
2666                                                                    panel.new_agent_thread(
2667                                                                        AgentType::Custom {
2668                                                                            name: agent_name
2669                                                                                .clone(),
2670                                                                            command: agent_settings
2671                                                                                .command
2672                                                                                .clone(),
2673                                                                        },
2674                                                                        window,
2675                                                                        cx,
2676                                                                    );
2677                                                                });
2678                                                            }
2679                                                        });
2680                                                    }
2681                                                }
2682                                            }),
2683                                    );
2684                                }
2685
2686                                menu
2687                            })
2688                            .when(cx.has_flag::<GeminiAndNativeFeatureFlag>(), |menu| {
2689                                menu.separator().link(
2690                                    "Add Other Agents",
2691                                    OpenBrowser {
2692                                        url: zed_urls::external_agents_docs(cx),
2693                                    }
2694                                    .boxed_clone(),
2695                                )
2696                            });
2697                        menu
2698                    }))
2699                }
2700            });
2701
2702        let selected_agent_label = self.selected_agent.label();
2703        let selected_agent = div()
2704            .id("selected_agent_icon")
2705            .when_some(self.selected_agent.icon(), |this, icon| {
2706                this.px(DynamicSpacing::Base02.rems(cx))
2707                    .child(Icon::new(icon).color(Color::Muted))
2708                    .tooltip(move |window, cx| {
2709                        Tooltip::with_meta(
2710                            selected_agent_label.clone(),
2711                            None,
2712                            "Selected Agent",
2713                            window,
2714                            cx,
2715                        )
2716                    })
2717            })
2718            .into_any_element();
2719
2720        h_flex()
2721            .id("agent-panel-toolbar")
2722            .h(Tab::container_height(cx))
2723            .max_w_full()
2724            .flex_none()
2725            .justify_between()
2726            .gap_2()
2727            .bg(cx.theme().colors().tab_bar_background)
2728            .border_b_1()
2729            .border_color(cx.theme().colors().border)
2730            .child(
2731                h_flex()
2732                    .size_full()
2733                    .gap(DynamicSpacing::Base04.rems(cx))
2734                    .pl(DynamicSpacing::Base04.rems(cx))
2735                    .child(match &self.active_view {
2736                        ActiveView::History | ActiveView::Configuration => {
2737                            self.render_toolbar_back_button(cx).into_any_element()
2738                        }
2739                        _ => selected_agent.into_any_element(),
2740                    })
2741                    .child(self.render_title_view(window, cx)),
2742            )
2743            .child(
2744                h_flex()
2745                    .flex_none()
2746                    .gap(DynamicSpacing::Base02.rems(cx))
2747                    .pl(DynamicSpacing::Base04.rems(cx))
2748                    .pr(DynamicSpacing::Base06.rems(cx))
2749                    .child(new_thread_menu)
2750                    .child(self.render_recent_entries_menu(
2751                        IconName::MenuAltTemp,
2752                        Corner::TopRight,
2753                        cx,
2754                    ))
2755                    .child(self.render_panel_options_menu(window, cx)),
2756            )
2757    }
2758
2759    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2760        if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>()
2761            || cx.has_flag::<feature_flags::ClaudeCodeFeatureFlag>()
2762        {
2763            self.render_toolbar_new(window, cx).into_any_element()
2764        } else {
2765            self.render_toolbar_old(window, cx).into_any_element()
2766        }
2767    }
2768
2769    fn render_token_count(&self, cx: &App) -> Option<AnyElement> {
2770        match &self.active_view {
2771            ActiveView::Thread {
2772                thread,
2773                message_editor,
2774                ..
2775            } => {
2776                let active_thread = thread.read(cx);
2777                let message_editor = message_editor.read(cx);
2778
2779                let editor_empty = message_editor.is_editor_fully_empty(cx);
2780
2781                if active_thread.is_empty() && editor_empty {
2782                    return None;
2783                }
2784
2785                let thread = active_thread.thread().read(cx);
2786                let is_generating = thread.is_generating();
2787                let conversation_token_usage = thread.total_token_usage()?;
2788
2789                let (total_token_usage, is_estimating) =
2790                    if let Some((editing_message_id, unsent_tokens)) =
2791                        active_thread.editing_message_id()
2792                    {
2793                        let combined = thread
2794                            .token_usage_up_to_message(editing_message_id)
2795                            .add(unsent_tokens);
2796
2797                        (combined, unsent_tokens > 0)
2798                    } else {
2799                        let unsent_tokens =
2800                            message_editor.last_estimated_token_count().unwrap_or(0);
2801                        let combined = conversation_token_usage.add(unsent_tokens);
2802
2803                        (combined, unsent_tokens > 0)
2804                    };
2805
2806                let is_waiting_to_update_token_count =
2807                    message_editor.is_waiting_to_update_token_count();
2808
2809                if total_token_usage.total == 0 {
2810                    return None;
2811                }
2812
2813                let token_color = match total_token_usage.ratio() {
2814                    TokenUsageRatio::Normal if is_estimating => Color::Default,
2815                    TokenUsageRatio::Normal => Color::Muted,
2816                    TokenUsageRatio::Warning => Color::Warning,
2817                    TokenUsageRatio::Exceeded => Color::Error,
2818                };
2819
2820                let token_count = h_flex()
2821                    .id("token-count")
2822                    .flex_shrink_0()
2823                    .gap_0p5()
2824                    .when(!is_generating && is_estimating, |parent| {
2825                        parent
2826                            .child(
2827                                h_flex()
2828                                    .mr_1()
2829                                    .size_2p5()
2830                                    .justify_center()
2831                                    .rounded_full()
2832                                    .bg(cx.theme().colors().text.opacity(0.1))
2833                                    .child(
2834                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
2835                                    ),
2836                            )
2837                            .tooltip(move |window, cx| {
2838                                Tooltip::with_meta(
2839                                    "Estimated New Token Count",
2840                                    None,
2841                                    format!(
2842                                        "Current Conversation Tokens: {}",
2843                                        humanize_token_count(conversation_token_usage.total)
2844                                    ),
2845                                    window,
2846                                    cx,
2847                                )
2848                            })
2849                    })
2850                    .child(
2851                        Label::new(humanize_token_count(total_token_usage.total))
2852                            .size(LabelSize::Small)
2853                            .color(token_color)
2854                            .map(|label| {
2855                                if is_generating || is_waiting_to_update_token_count {
2856                                    label
2857                                        .with_animation(
2858                                            "used-tokens-label",
2859                                            Animation::new(Duration::from_secs(2))
2860                                                .repeat()
2861                                                .with_easing(pulsating_between(0.6, 1.)),
2862                                            |label, delta| label.alpha(delta),
2863                                        )
2864                                        .into_any()
2865                                } else {
2866                                    label.into_any_element()
2867                                }
2868                            }),
2869                    )
2870                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2871                    .child(
2872                        Label::new(humanize_token_count(total_token_usage.max))
2873                            .size(LabelSize::Small)
2874                            .color(Color::Muted),
2875                    )
2876                    .into_any();
2877
2878                Some(token_count)
2879            }
2880            ActiveView::ExternalAgentThread { .. }
2881            | ActiveView::TextThread { .. }
2882            | ActiveView::History
2883            | ActiveView::Configuration => None,
2884        }
2885    }
2886
2887    fn should_render_trial_end_upsell(&self, cx: &mut Context<Self>) -> bool {
2888        if TrialEndUpsell::dismissed() {
2889            return false;
2890        }
2891
2892        match &self.active_view {
2893            ActiveView::Thread { thread, .. } => {
2894                if thread
2895                    .read(cx)
2896                    .thread()
2897                    .read(cx)
2898                    .configured_model()
2899                    .is_some_and(|model| {
2900                        model.provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2901                    })
2902                {
2903                    return false;
2904                }
2905            }
2906            ActiveView::TextThread { .. } => {
2907                if LanguageModelRegistry::global(cx)
2908                    .read(cx)
2909                    .default_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::ExternalAgentThread { .. }
2918            | ActiveView::History
2919            | ActiveView::Configuration => return false,
2920        }
2921
2922        let plan = self.user_store.read(cx).plan();
2923        let has_previous_trial = self.user_store.read(cx).trial_started_at().is_some();
2924
2925        matches!(plan, Some(Plan::ZedFree)) && has_previous_trial
2926    }
2927
2928    fn should_render_onboarding(&self, cx: &mut Context<Self>) -> bool {
2929        if OnboardingUpsell::dismissed() {
2930            return false;
2931        }
2932
2933        match &self.active_view {
2934            ActiveView::History | ActiveView::Configuration => false,
2935            ActiveView::ExternalAgentThread { thread_view, .. }
2936                if thread_view.read(cx).as_native_thread(cx).is_none() =>
2937            {
2938                false
2939            }
2940            _ => {
2941                let history_is_empty = if cx.has_flag::<GeminiAndNativeFeatureFlag>() {
2942                    self.acp_history_store.read(cx).is_empty(cx)
2943                } else {
2944                    self.history_store
2945                        .update(cx, |store, cx| store.recent_entries(1, cx).is_empty())
2946                };
2947
2948                let has_configured_non_zed_providers = LanguageModelRegistry::read_global(cx)
2949                    .providers()
2950                    .iter()
2951                    .any(|provider| {
2952                        provider.is_authenticated(cx)
2953                            && provider.id() != language_model::ZED_CLOUD_PROVIDER_ID
2954                    });
2955
2956                history_is_empty || !has_configured_non_zed_providers
2957            }
2958        }
2959    }
2960
2961    fn render_onboarding(
2962        &self,
2963        _window: &mut Window,
2964        cx: &mut Context<Self>,
2965    ) -> Option<impl IntoElement> {
2966        if !self.should_render_onboarding(cx) {
2967            return None;
2968        }
2969
2970        let thread_view = matches!(&self.active_view, ActiveView::Thread { .. });
2971        let text_thread_view = matches!(&self.active_view, ActiveView::TextThread { .. });
2972
2973        Some(
2974            div()
2975                .when(thread_view, |this| {
2976                    this.size_full().bg(cx.theme().colors().panel_background)
2977                })
2978                .when(text_thread_view, |this| {
2979                    this.bg(cx.theme().colors().editor_background)
2980                })
2981                .child(self.onboarding.clone()),
2982        )
2983    }
2984
2985    fn render_backdrop(&self, cx: &mut Context<Self>) -> impl IntoElement {
2986        div()
2987            .size_full()
2988            .absolute()
2989            .inset_0()
2990            .bg(cx.theme().colors().panel_background)
2991            .opacity(0.8)
2992            .block_mouse_except_scroll()
2993    }
2994
2995    fn render_trial_end_upsell(
2996        &self,
2997        _window: &mut Window,
2998        cx: &mut Context<Self>,
2999    ) -> Option<impl IntoElement> {
3000        if !self.should_render_trial_end_upsell(cx) {
3001            return None;
3002        }
3003
3004        Some(
3005            v_flex()
3006                .absolute()
3007                .inset_0()
3008                .size_full()
3009                .bg(cx.theme().colors().panel_background)
3010                .opacity(0.85)
3011                .block_mouse_except_scroll()
3012                .child(EndTrialUpsell::new(Arc::new({
3013                    let this = cx.entity();
3014                    move |_, cx| {
3015                        this.update(cx, |_this, cx| {
3016                            TrialEndUpsell::set_dismissed(true, cx);
3017                            cx.notify();
3018                        });
3019                    }
3020                }))),
3021        )
3022    }
3023
3024    fn render_empty_state_section_header(
3025        &self,
3026        label: impl Into<SharedString>,
3027        action_slot: Option<AnyElement>,
3028        cx: &mut Context<Self>,
3029    ) -> impl IntoElement {
3030        div().pl_1().pr_1p5().child(
3031            h_flex()
3032                .mt_2()
3033                .pl_1p5()
3034                .pb_1()
3035                .w_full()
3036                .justify_between()
3037                .border_b_1()
3038                .border_color(cx.theme().colors().border_variant)
3039                .child(
3040                    Label::new(label.into())
3041                        .size(LabelSize::Small)
3042                        .color(Color::Muted),
3043                )
3044                .children(action_slot),
3045        )
3046    }
3047
3048    fn render_thread_empty_state(
3049        &self,
3050        window: &mut Window,
3051        cx: &mut Context<Self>,
3052    ) -> impl IntoElement {
3053        let recent_history = self
3054            .history_store
3055            .update(cx, |this, cx| this.recent_entries(6, cx));
3056
3057        let model_registry = LanguageModelRegistry::read_global(cx);
3058
3059        let configuration_error =
3060            model_registry.configuration_error(model_registry.default_model(), cx);
3061
3062        let no_error = configuration_error.is_none();
3063        let focus_handle = self.focus_handle(cx);
3064
3065        v_flex()
3066            .size_full()
3067            .bg(cx.theme().colors().panel_background)
3068            .when(recent_history.is_empty(), |this| {
3069                this.child(
3070                    v_flex()
3071                        .size_full()
3072                        .mx_auto()
3073                        .justify_center()
3074                        .items_center()
3075                        .gap_1()
3076                        .child(h_flex().child(Headline::new("Welcome to the Agent Panel")))
3077                        .when(no_error, |parent| {
3078                            parent
3079                                .child(h_flex().child(
3080                                    Label::new("Ask and build anything.").color(Color::Muted),
3081                                ))
3082                                .child(
3083                                    v_flex()
3084                                        .mt_2()
3085                                        .gap_1()
3086                                        .max_w_48()
3087                                        .child(
3088                                            Button::new("context", "Add Context")
3089                                                .label_size(LabelSize::Small)
3090                                                .icon(IconName::FileCode)
3091                                                .icon_position(IconPosition::Start)
3092                                                .icon_size(IconSize::Small)
3093                                                .icon_color(Color::Muted)
3094                                                .full_width()
3095                                                .key_binding(KeyBinding::for_action_in(
3096                                                    &ToggleContextPicker,
3097                                                    &focus_handle,
3098                                                    window,
3099                                                    cx,
3100                                                ))
3101                                                .on_click(|_event, window, cx| {
3102                                                    window.dispatch_action(
3103                                                        ToggleContextPicker.boxed_clone(),
3104                                                        cx,
3105                                                    )
3106                                                }),
3107                                        )
3108                                        .child(
3109                                            Button::new("mode", "Switch Model")
3110                                                .label_size(LabelSize::Small)
3111                                                .icon(IconName::DatabaseZap)
3112                                                .icon_position(IconPosition::Start)
3113                                                .icon_size(IconSize::Small)
3114                                                .icon_color(Color::Muted)
3115                                                .full_width()
3116                                                .key_binding(KeyBinding::for_action_in(
3117                                                    &ToggleModelSelector,
3118                                                    &focus_handle,
3119                                                    window,
3120                                                    cx,
3121                                                ))
3122                                                .on_click(|_event, window, cx| {
3123                                                    window.dispatch_action(
3124                                                        ToggleModelSelector.boxed_clone(),
3125                                                        cx,
3126                                                    )
3127                                                }),
3128                                        )
3129                                        .child(
3130                                            Button::new("settings", "View Settings")
3131                                                .label_size(LabelSize::Small)
3132                                                .icon(IconName::Settings)
3133                                                .icon_position(IconPosition::Start)
3134                                                .icon_size(IconSize::Small)
3135                                                .icon_color(Color::Muted)
3136                                                .full_width()
3137                                                .key_binding(KeyBinding::for_action_in(
3138                                                    &OpenSettings,
3139                                                    &focus_handle,
3140                                                    window,
3141                                                    cx,
3142                                                ))
3143                                                .on_click(|_event, window, cx| {
3144                                                    window.dispatch_action(
3145                                                        OpenSettings.boxed_clone(),
3146                                                        cx,
3147                                                    )
3148                                                }),
3149                                        ),
3150                                )
3151                        }),
3152                )
3153            })
3154            .when(!recent_history.is_empty(), |parent| {
3155                parent
3156                    .overflow_hidden()
3157                    .justify_end()
3158                    .gap_1()
3159                    .child(
3160                        self.render_empty_state_section_header(
3161                            "Recent",
3162                            Some(
3163                                Button::new("view-history", "View All")
3164                                    .style(ButtonStyle::Subtle)
3165                                    .label_size(LabelSize::Small)
3166                                    .key_binding(
3167                                        KeyBinding::for_action_in(
3168                                            &OpenHistory,
3169                                            &self.focus_handle(cx),
3170                                            window,
3171                                            cx,
3172                                        )
3173                                        .map(|kb| kb.size(rems_from_px(12.))),
3174                                    )
3175                                    .on_click(move |_event, window, cx| {
3176                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
3177                                    })
3178                                    .into_any_element(),
3179                            ),
3180                            cx,
3181                        ),
3182                    )
3183                    .child(
3184                        v_flex().p_1().pr_1p5().gap_1().children(
3185                            recent_history
3186                                .into_iter()
3187                                .enumerate()
3188                                .map(|(index, entry)| {
3189                                    // TODO: Add keyboard navigation.
3190                                    let is_hovered =
3191                                        self.hovered_recent_history_item == Some(index);
3192                                    HistoryEntryElement::new(entry, cx.entity().downgrade())
3193                                        .hovered(is_hovered)
3194                                        .on_hover(cx.listener(
3195                                            move |this, is_hovered, _window, cx| {
3196                                                if *is_hovered {
3197                                                    this.hovered_recent_history_item = Some(index);
3198                                                } else if this.hovered_recent_history_item
3199                                                    == Some(index)
3200                                                {
3201                                                    this.hovered_recent_history_item = None;
3202                                                }
3203                                                cx.notify();
3204                                            },
3205                                        ))
3206                                        .into_any_element()
3207                                }),
3208                        ),
3209                    )
3210            })
3211            .when_some(configuration_error.as_ref(), |this, err| {
3212                this.child(self.render_configuration_error(false, err, &focus_handle, window, cx))
3213            })
3214    }
3215
3216    fn render_configuration_error(
3217        &self,
3218        border_bottom: bool,
3219        configuration_error: &ConfigurationError,
3220        focus_handle: &FocusHandle,
3221        window: &mut Window,
3222        cx: &mut App,
3223    ) -> impl IntoElement {
3224        let zed_provider_configured = AgentSettings::get_global(cx)
3225            .default_model
3226            .as_ref()
3227            .is_some_and(|selection| selection.provider.0.as_str() == "zed.dev");
3228
3229        let callout = if zed_provider_configured {
3230            Callout::new()
3231                .icon(IconName::Warning)
3232                .severity(Severity::Warning)
3233                .when(border_bottom, |this| {
3234                    this.border_position(ui::BorderPosition::Bottom)
3235                })
3236                .title("Sign in to continue using Zed as your LLM provider.")
3237                .actions_slot(
3238                    Button::new("sign_in", "Sign In")
3239                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
3240                        .label_size(LabelSize::Small)
3241                        .on_click({
3242                            let workspace = self.workspace.clone();
3243                            move |_, _, cx| {
3244                                let Ok(client) =
3245                                    workspace.update(cx, |workspace, _| workspace.client().clone())
3246                                else {
3247                                    return;
3248                                };
3249
3250                                cx.spawn(async move |cx| {
3251                                    client.sign_in_with_optional_connect(true, cx).await
3252                                })
3253                                .detach_and_log_err(cx);
3254                            }
3255                        }),
3256                )
3257        } else {
3258            Callout::new()
3259                .icon(IconName::Warning)
3260                .severity(Severity::Warning)
3261                .when(border_bottom, |this| {
3262                    this.border_position(ui::BorderPosition::Bottom)
3263                })
3264                .title(configuration_error.to_string())
3265                .actions_slot(
3266                    Button::new("settings", "Configure")
3267                        .style(ButtonStyle::Tinted(ui::TintColor::Warning))
3268                        .label_size(LabelSize::Small)
3269                        .key_binding(
3270                            KeyBinding::for_action_in(&OpenSettings, focus_handle, window, cx)
3271                                .map(|kb| kb.size(rems_from_px(12.))),
3272                        )
3273                        .on_click(|_event, window, cx| {
3274                            window.dispatch_action(OpenSettings.boxed_clone(), cx)
3275                        }),
3276                )
3277        };
3278
3279        match configuration_error {
3280            ConfigurationError::ModelNotFound
3281            | ConfigurationError::ProviderNotAuthenticated(_)
3282            | ConfigurationError::NoProvider => callout.into_any_element(),
3283        }
3284    }
3285
3286    fn render_tool_use_limit_reached(
3287        &self,
3288        window: &mut Window,
3289        cx: &mut Context<Self>,
3290    ) -> Option<AnyElement> {
3291        let active_thread = match &self.active_view {
3292            ActiveView::Thread { thread, .. } => thread,
3293            ActiveView::ExternalAgentThread { .. } => {
3294                return None;
3295            }
3296            ActiveView::TextThread { .. } | ActiveView::History | ActiveView::Configuration => {
3297                return None;
3298            }
3299        };
3300
3301        let thread = active_thread.read(cx).thread().read(cx);
3302
3303        let tool_use_limit_reached = thread.tool_use_limit_reached();
3304        if !tool_use_limit_reached {
3305            return None;
3306        }
3307
3308        let model = thread.configured_model()?.model;
3309
3310        let focus_handle = self.focus_handle(cx);
3311
3312        let banner = Banner::new()
3313            .severity(Severity::Info)
3314            .child(Label::new("Consecutive tool use limit reached.").size(LabelSize::Small))
3315            .action_slot(
3316                h_flex()
3317                    .gap_1()
3318                    .child(
3319                        Button::new("continue-conversation", "Continue")
3320                            .layer(ElevationIndex::ModalSurface)
3321                            .label_size(LabelSize::Small)
3322                            .key_binding(
3323                                KeyBinding::for_action_in(
3324                                    &ContinueThread,
3325                                    &focus_handle,
3326                                    window,
3327                                    cx,
3328                                )
3329                                .map(|kb| kb.size(rems_from_px(10.))),
3330                            )
3331                            .on_click(cx.listener(|this, _, window, cx| {
3332                                this.continue_conversation(window, cx);
3333                            })),
3334                    )
3335                    .when(model.supports_burn_mode(), |this| {
3336                        this.child(
3337                            Button::new("continue-burn-mode", "Continue with Burn Mode")
3338                                .style(ButtonStyle::Filled)
3339                                .style(ButtonStyle::Tinted(ui::TintColor::Accent))
3340                                .layer(ElevationIndex::ModalSurface)
3341                                .label_size(LabelSize::Small)
3342                                .key_binding(
3343                                    KeyBinding::for_action_in(
3344                                        &ContinueWithBurnMode,
3345                                        &focus_handle,
3346                                        window,
3347                                        cx,
3348                                    )
3349                                    .map(|kb| kb.size(rems_from_px(10.))),
3350                                )
3351                                .tooltip(Tooltip::text("Enable Burn Mode for unlimited tool use."))
3352                                .on_click({
3353                                    let active_thread = active_thread.clone();
3354                                    cx.listener(move |this, _, window, cx| {
3355                                        active_thread.update(cx, |active_thread, cx| {
3356                                            active_thread.thread().update(cx, |thread, _cx| {
3357                                                thread.set_completion_mode(CompletionMode::Burn);
3358                                            });
3359                                        });
3360                                        this.continue_conversation(window, cx);
3361                                    })
3362                                }),
3363                        )
3364                    }),
3365            );
3366
3367        Some(div().px_2().pb_2().child(banner).into_any_element())
3368    }
3369
3370    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
3371        let message = message.into();
3372
3373        IconButton::new("copy", IconName::Copy)
3374            .icon_size(IconSize::Small)
3375            .icon_color(Color::Muted)
3376            .tooltip(Tooltip::text("Copy Error Message"))
3377            .on_click(move |_, _, cx| {
3378                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
3379            })
3380    }
3381
3382    fn dismiss_error_button(
3383        &self,
3384        thread: &Entity<ActiveThread>,
3385        cx: &mut Context<Self>,
3386    ) -> impl IntoElement {
3387        IconButton::new("dismiss", IconName::Close)
3388            .icon_size(IconSize::Small)
3389            .icon_color(Color::Muted)
3390            .tooltip(Tooltip::text("Dismiss Error"))
3391            .on_click(cx.listener({
3392                let thread = thread.clone();
3393                move |_, _, _, cx| {
3394                    thread.update(cx, |this, _cx| {
3395                        this.clear_last_error();
3396                    });
3397
3398                    cx.notify();
3399                }
3400            }))
3401    }
3402
3403    fn upgrade_button(
3404        &self,
3405        thread: &Entity<ActiveThread>,
3406        cx: &mut Context<Self>,
3407    ) -> impl IntoElement {
3408        Button::new("upgrade", "Upgrade")
3409            .label_size(LabelSize::Small)
3410            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
3411            .on_click(cx.listener({
3412                let thread = thread.clone();
3413                move |_, _, _, cx| {
3414                    thread.update(cx, |this, _cx| {
3415                        this.clear_last_error();
3416                    });
3417
3418                    cx.open_url(&zed_urls::upgrade_to_zed_pro_url(cx));
3419                    cx.notify();
3420                }
3421            }))
3422    }
3423
3424    fn render_payment_required_error(
3425        &self,
3426        thread: &Entity<ActiveThread>,
3427        cx: &mut Context<Self>,
3428    ) -> AnyElement {
3429        const ERROR_MESSAGE: &str =
3430            "You reached your free usage limit. Upgrade to Zed Pro for more prompts.";
3431
3432        Callout::new()
3433            .severity(Severity::Error)
3434            .icon(IconName::XCircle)
3435            .title("Free Usage Exceeded")
3436            .description(ERROR_MESSAGE)
3437            .actions_slot(
3438                h_flex()
3439                    .gap_0p5()
3440                    .child(self.upgrade_button(thread, cx))
3441                    .child(self.create_copy_button(ERROR_MESSAGE)),
3442            )
3443            .dismiss_action(self.dismiss_error_button(thread, cx))
3444            .into_any_element()
3445    }
3446
3447    fn render_model_request_limit_reached_error(
3448        &self,
3449        plan: Plan,
3450        thread: &Entity<ActiveThread>,
3451        cx: &mut Context<Self>,
3452    ) -> AnyElement {
3453        let error_message = match plan {
3454            Plan::ZedPro => "Upgrade to usage-based billing for more prompts.",
3455            Plan::ZedProTrial | Plan::ZedFree => "Upgrade to Zed Pro for more prompts.",
3456        };
3457
3458        Callout::new()
3459            .severity(Severity::Error)
3460            .title("Model Prompt Limit Reached")
3461            .description(error_message)
3462            .actions_slot(
3463                h_flex()
3464                    .gap_0p5()
3465                    .child(self.upgrade_button(thread, cx))
3466                    .child(self.create_copy_button(error_message)),
3467            )
3468            .dismiss_action(self.dismiss_error_button(thread, cx))
3469            .into_any_element()
3470    }
3471
3472    fn render_retry_button(&self, thread: &Entity<ActiveThread>) -> AnyElement {
3473        Button::new("retry", "Retry")
3474            .icon(IconName::RotateCw)
3475            .icon_position(IconPosition::Start)
3476            .icon_size(IconSize::Small)
3477            .label_size(LabelSize::Small)
3478            .on_click({
3479                let thread = thread.clone();
3480                move |_, window, cx| {
3481                    thread.update(cx, |thread, cx| {
3482                        thread.clear_last_error();
3483                        thread.thread().update(cx, |thread, cx| {
3484                            thread.retry_last_completion(Some(window.window_handle()), cx);
3485                        });
3486                    });
3487                }
3488            })
3489            .into_any_element()
3490    }
3491
3492    fn render_error_message(
3493        &self,
3494        header: SharedString,
3495        message: SharedString,
3496        thread: &Entity<ActiveThread>,
3497        cx: &mut Context<Self>,
3498    ) -> AnyElement {
3499        let message_with_header = format!("{}\n{}", header, message);
3500
3501        Callout::new()
3502            .severity(Severity::Error)
3503            .icon(IconName::XCircle)
3504            .title(header)
3505            .description(message)
3506            .actions_slot(
3507                h_flex()
3508                    .gap_0p5()
3509                    .child(self.render_retry_button(thread))
3510                    .child(self.create_copy_button(message_with_header)),
3511            )
3512            .dismiss_action(self.dismiss_error_button(thread, cx))
3513            .into_any_element()
3514    }
3515
3516    fn render_retryable_error(
3517        &self,
3518        message: SharedString,
3519        can_enable_burn_mode: bool,
3520        thread: &Entity<ActiveThread>,
3521    ) -> AnyElement {
3522        Callout::new()
3523            .severity(Severity::Error)
3524            .title("Error")
3525            .description(message)
3526            .actions_slot(
3527                h_flex()
3528                    .gap_0p5()
3529                    .when(can_enable_burn_mode, |this| {
3530                        this.child(
3531                            Button::new("enable_burn_retry", "Enable Burn Mode and Retry")
3532                                .icon(IconName::ZedBurnMode)
3533                                .icon_position(IconPosition::Start)
3534                                .icon_size(IconSize::Small)
3535                                .label_size(LabelSize::Small)
3536                                .on_click({
3537                                    let thread = thread.clone();
3538                                    move |_, window, cx| {
3539                                        thread.update(cx, |thread, cx| {
3540                                            thread.clear_last_error();
3541                                            thread.thread().update(cx, |thread, cx| {
3542                                                thread.enable_burn_mode_and_retry(
3543                                                    Some(window.window_handle()),
3544                                                    cx,
3545                                                );
3546                                            });
3547                                        });
3548                                    }
3549                                }),
3550                        )
3551                    })
3552                    .child(self.render_retry_button(thread)),
3553            )
3554            .into_any_element()
3555    }
3556
3557    fn render_prompt_editor(
3558        &self,
3559        context_editor: &Entity<TextThreadEditor>,
3560        buffer_search_bar: &Entity<BufferSearchBar>,
3561        window: &mut Window,
3562        cx: &mut Context<Self>,
3563    ) -> Div {
3564        let mut registrar = buffer_search::DivRegistrar::new(
3565            |this, _, _cx| match &this.active_view {
3566                ActiveView::TextThread {
3567                    buffer_search_bar, ..
3568                } => Some(buffer_search_bar.clone()),
3569                _ => None,
3570            },
3571            cx,
3572        );
3573        BufferSearchBar::register(&mut registrar);
3574        registrar
3575            .into_div()
3576            .size_full()
3577            .relative()
3578            .map(|parent| {
3579                buffer_search_bar.update(cx, |buffer_search_bar, cx| {
3580                    if buffer_search_bar.is_dismissed() {
3581                        return parent;
3582                    }
3583                    parent.child(
3584                        div()
3585                            .p(DynamicSpacing::Base08.rems(cx))
3586                            .border_b_1()
3587                            .border_color(cx.theme().colors().border_variant)
3588                            .bg(cx.theme().colors().editor_background)
3589                            .child(buffer_search_bar.render(window, cx)),
3590                    )
3591                })
3592            })
3593            .child(context_editor.clone())
3594            .child(self.render_drag_target(cx))
3595    }
3596
3597    fn render_drag_target(&self, cx: &Context<Self>) -> Div {
3598        let is_local = self.project.read(cx).is_local();
3599        div()
3600            .invisible()
3601            .absolute()
3602            .top_0()
3603            .right_0()
3604            .bottom_0()
3605            .left_0()
3606            .bg(cx.theme().colors().drop_target_background)
3607            .drag_over::<DraggedTab>(|this, _, _, _| this.visible())
3608            .drag_over::<DraggedSelection>(|this, _, _, _| this.visible())
3609            .when(is_local, |this| {
3610                this.drag_over::<ExternalPaths>(|this, _, _, _| this.visible())
3611            })
3612            .on_drop(cx.listener(move |this, tab: &DraggedTab, window, cx| {
3613                let item = tab.pane.read(cx).item_for_index(tab.ix);
3614                let project_paths = item
3615                    .and_then(|item| item.project_path(cx))
3616                    .into_iter()
3617                    .collect::<Vec<_>>();
3618                this.handle_drop(project_paths, vec![], window, cx);
3619            }))
3620            .on_drop(
3621                cx.listener(move |this, selection: &DraggedSelection, window, cx| {
3622                    let project_paths = selection
3623                        .items()
3624                        .filter_map(|item| this.project.read(cx).path_for_entry(item.entry_id, cx))
3625                        .collect::<Vec<_>>();
3626                    this.handle_drop(project_paths, vec![], window, cx);
3627                }),
3628            )
3629            .on_drop(cx.listener(move |this, paths: &ExternalPaths, window, cx| {
3630                let tasks = paths
3631                    .paths()
3632                    .iter()
3633                    .map(|path| {
3634                        Workspace::project_path_for_path(this.project.clone(), path, false, cx)
3635                    })
3636                    .collect::<Vec<_>>();
3637                cx.spawn_in(window, async move |this, cx| {
3638                    let mut paths = vec![];
3639                    let mut added_worktrees = vec![];
3640                    let opened_paths = futures::future::join_all(tasks).await;
3641                    for entry in opened_paths {
3642                        if let Some((worktree, project_path)) = entry.log_err() {
3643                            added_worktrees.push(worktree);
3644                            paths.push(project_path);
3645                        }
3646                    }
3647                    this.update_in(cx, |this, window, cx| {
3648                        this.handle_drop(paths, added_worktrees, window, cx);
3649                    })
3650                    .ok();
3651                })
3652                .detach();
3653            }))
3654    }
3655
3656    fn handle_drop(
3657        &mut self,
3658        paths: Vec<ProjectPath>,
3659        added_worktrees: Vec<Entity<Worktree>>,
3660        window: &mut Window,
3661        cx: &mut Context<Self>,
3662    ) {
3663        match &self.active_view {
3664            ActiveView::Thread { thread, .. } => {
3665                let context_store = thread.read(cx).context_store().clone();
3666                context_store.update(cx, move |context_store, cx| {
3667                    let mut tasks = Vec::new();
3668                    for project_path in &paths {
3669                        tasks.push(context_store.add_file_from_path(
3670                            project_path.clone(),
3671                            false,
3672                            cx,
3673                        ));
3674                    }
3675                    cx.background_spawn(async move {
3676                        futures::future::join_all(tasks).await;
3677                        // Need to hold onto the worktrees until they have already been used when
3678                        // opening the buffers.
3679                        drop(added_worktrees);
3680                    })
3681                    .detach();
3682                });
3683            }
3684            ActiveView::ExternalAgentThread { thread_view } => {
3685                thread_view.update(cx, |thread_view, cx| {
3686                    thread_view.insert_dragged_files(paths, added_worktrees, window, cx);
3687                });
3688            }
3689            ActiveView::TextThread { context_editor, .. } => {
3690                context_editor.update(cx, |context_editor, cx| {
3691                    TextThreadEditor::insert_dragged_files(
3692                        context_editor,
3693                        paths,
3694                        added_worktrees,
3695                        window,
3696                        cx,
3697                    );
3698                });
3699            }
3700            ActiveView::History | ActiveView::Configuration => {}
3701        }
3702    }
3703
3704    fn key_context(&self) -> KeyContext {
3705        let mut key_context = KeyContext::new_with_defaults();
3706        key_context.add("AgentPanel");
3707        match &self.active_view {
3708            ActiveView::ExternalAgentThread { .. } => key_context.add("external_agent_thread"),
3709            ActiveView::TextThread { .. } => key_context.add("prompt_editor"),
3710            ActiveView::Thread { .. } | ActiveView::History | ActiveView::Configuration => {}
3711        }
3712        key_context
3713    }
3714}
3715
3716impl Render for AgentPanel {
3717    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
3718        // WARNING: Changes to this element hierarchy can have
3719        // non-obvious implications to the layout of children.
3720        //
3721        // If you need to change it, please confirm:
3722        // - The message editor expands (cmd-option-esc) correctly
3723        // - When expanded, the buttons at the bottom of the panel are displayed correctly
3724        // - Font size works as expected and can be changed with cmd-+/cmd-
3725        // - Scrolling in all views works as expected
3726        // - Files can be dropped into the panel
3727        let content = v_flex()
3728            .relative()
3729            .size_full()
3730            .justify_between()
3731            .key_context(self.key_context())
3732            .on_action(cx.listener(Self::cancel))
3733            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
3734                this.new_thread(action, window, cx);
3735            }))
3736            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
3737                this.open_history(window, cx);
3738            }))
3739            .on_action(cx.listener(|this, _: &OpenSettings, window, cx| {
3740                this.open_configuration(window, cx);
3741            }))
3742            .on_action(cx.listener(Self::open_active_thread_as_markdown))
3743            .on_action(cx.listener(Self::deploy_rules_library))
3744            .on_action(cx.listener(Self::open_agent_diff))
3745            .on_action(cx.listener(Self::go_back))
3746            .on_action(cx.listener(Self::toggle_navigation_menu))
3747            .on_action(cx.listener(Self::toggle_options_menu))
3748            .on_action(cx.listener(Self::increase_font_size))
3749            .on_action(cx.listener(Self::decrease_font_size))
3750            .on_action(cx.listener(Self::reset_font_size))
3751            .on_action(cx.listener(Self::toggle_zoom))
3752            .on_action(cx.listener(|this, _: &ContinueThread, window, cx| {
3753                this.continue_conversation(window, cx);
3754            }))
3755            .on_action(cx.listener(|this, _: &ContinueWithBurnMode, window, cx| {
3756                match &this.active_view {
3757                    ActiveView::Thread { thread, .. } => {
3758                        thread.update(cx, |active_thread, cx| {
3759                            active_thread.thread().update(cx, |thread, _cx| {
3760                                thread.set_completion_mode(CompletionMode::Burn);
3761                            });
3762                        });
3763                        this.continue_conversation(window, cx);
3764                    }
3765                    ActiveView::ExternalAgentThread { .. } => {}
3766                    ActiveView::TextThread { .. }
3767                    | ActiveView::History
3768                    | ActiveView::Configuration => {}
3769                }
3770            }))
3771            .on_action(cx.listener(Self::toggle_burn_mode))
3772            .on_action(cx.listener(|this, _: &ReauthenticateAgent, window, cx| {
3773                if let Some(thread_view) = this.active_thread_view() {
3774                    thread_view.update(cx, |thread_view, cx| thread_view.reauthenticate(window, cx))
3775                }
3776            }))
3777            .child(self.render_toolbar(window, cx))
3778            .children(self.render_onboarding(window, cx))
3779            .map(|parent| match &self.active_view {
3780                ActiveView::Thread {
3781                    thread,
3782                    message_editor,
3783                    ..
3784                } => parent
3785                    .child(
3786                        if thread.read(cx).is_empty() && !self.should_render_onboarding(cx) {
3787                            self.render_thread_empty_state(window, cx)
3788                                .into_any_element()
3789                        } else {
3790                            thread.clone().into_any_element()
3791                        },
3792                    )
3793                    .children(self.render_tool_use_limit_reached(window, cx))
3794                    .when_some(thread.read(cx).last_error(), |this, last_error| {
3795                        this.child(
3796                            div()
3797                                .child(match last_error {
3798                                    ThreadError::PaymentRequired => {
3799                                        self.render_payment_required_error(thread, cx)
3800                                    }
3801                                    ThreadError::ModelRequestLimitReached { plan } => self
3802                                        .render_model_request_limit_reached_error(plan, thread, cx),
3803                                    ThreadError::Message { header, message } => {
3804                                        self.render_error_message(header, message, thread, cx)
3805                                    }
3806                                    ThreadError::RetryableError {
3807                                        message,
3808                                        can_enable_burn_mode,
3809                                    } => self.render_retryable_error(
3810                                        message,
3811                                        can_enable_burn_mode,
3812                                        thread,
3813                                    ),
3814                                })
3815                                .into_any(),
3816                        )
3817                    })
3818                    .child(h_flex().relative().child(message_editor.clone()).when(
3819                        !LanguageModelRegistry::read_global(cx).has_authenticated_provider(cx),
3820                        |this| this.child(self.render_backdrop(cx)),
3821                    ))
3822                    .child(self.render_drag_target(cx)),
3823                ActiveView::ExternalAgentThread { thread_view, .. } => parent
3824                    .child(thread_view.clone())
3825                    .child(self.render_drag_target(cx)),
3826                ActiveView::History => {
3827                    if cx.has_flag::<feature_flags::GeminiAndNativeFeatureFlag>() {
3828                        parent.child(self.acp_history.clone())
3829                    } else {
3830                        parent.child(self.history.clone())
3831                    }
3832                }
3833                ActiveView::TextThread {
3834                    context_editor,
3835                    buffer_search_bar,
3836                    ..
3837                } => {
3838                    let model_registry = LanguageModelRegistry::read_global(cx);
3839                    let configuration_error =
3840                        model_registry.configuration_error(model_registry.default_model(), cx);
3841                    parent
3842                        .map(|this| {
3843                            if !self.should_render_onboarding(cx)
3844                                && let Some(err) = configuration_error.as_ref()
3845                            {
3846                                this.child(self.render_configuration_error(
3847                                    true,
3848                                    err,
3849                                    &self.focus_handle(cx),
3850                                    window,
3851                                    cx,
3852                                ))
3853                            } else {
3854                                this
3855                            }
3856                        })
3857                        .child(self.render_prompt_editor(
3858                            context_editor,
3859                            buffer_search_bar,
3860                            window,
3861                            cx,
3862                        ))
3863                }
3864                ActiveView::Configuration => parent.children(self.configuration.clone()),
3865            })
3866            .children(self.render_trial_end_upsell(window, cx));
3867
3868        match self.active_view.which_font_size_used() {
3869            WhichFontSize::AgentFont => {
3870                WithRemSize::new(ThemeSettings::get_global(cx).agent_font_size(cx))
3871                    .size_full()
3872                    .child(content)
3873                    .into_any()
3874            }
3875            _ => content.into_any(),
3876        }
3877    }
3878}
3879
3880struct PromptLibraryInlineAssist {
3881    workspace: WeakEntity<Workspace>,
3882}
3883
3884impl PromptLibraryInlineAssist {
3885    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
3886        Self { workspace }
3887    }
3888}
3889
3890impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
3891    fn assist(
3892        &self,
3893        prompt_editor: &Entity<Editor>,
3894        initial_prompt: Option<String>,
3895        window: &mut Window,
3896        cx: &mut Context<RulesLibrary>,
3897    ) {
3898        InlineAssistant::update_global(cx, |assistant, cx| {
3899            let Some(project) = self
3900                .workspace
3901                .upgrade()
3902                .map(|workspace| workspace.read(cx).project().downgrade())
3903            else {
3904                return;
3905            };
3906            let prompt_store = None;
3907            let thread_store = None;
3908            let text_thread_store = None;
3909            let context_store = cx.new(|_| ContextStore::new(project.clone(), None));
3910            assistant.assist(
3911                prompt_editor,
3912                self.workspace.clone(),
3913                context_store,
3914                project,
3915                prompt_store,
3916                thread_store,
3917                text_thread_store,
3918                initial_prompt,
3919                window,
3920                cx,
3921            )
3922        })
3923    }
3924
3925    fn focus_agent_panel(
3926        &self,
3927        workspace: &mut Workspace,
3928        window: &mut Window,
3929        cx: &mut Context<Workspace>,
3930    ) -> bool {
3931        workspace.focus_panel::<AgentPanel>(window, cx).is_some()
3932    }
3933}
3934
3935pub struct ConcreteAssistantPanelDelegate;
3936
3937impl AgentPanelDelegate for ConcreteAssistantPanelDelegate {
3938    fn active_context_editor(
3939        &self,
3940        workspace: &mut Workspace,
3941        _window: &mut Window,
3942        cx: &mut Context<Workspace>,
3943    ) -> Option<Entity<TextThreadEditor>> {
3944        let panel = workspace.panel::<AgentPanel>(cx)?;
3945        panel.read(cx).active_context_editor()
3946    }
3947
3948    fn open_saved_context(
3949        &self,
3950        workspace: &mut Workspace,
3951        path: Arc<Path>,
3952        window: &mut Window,
3953        cx: &mut Context<Workspace>,
3954    ) -> Task<Result<()>> {
3955        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3956            return Task::ready(Err(anyhow!("Agent panel not found")));
3957        };
3958
3959        panel.update(cx, |panel, cx| {
3960            panel.open_saved_prompt_editor(path, window, cx)
3961        })
3962    }
3963
3964    fn open_remote_context(
3965        &self,
3966        _workspace: &mut Workspace,
3967        _context_id: assistant_context::ContextId,
3968        _window: &mut Window,
3969        _cx: &mut Context<Workspace>,
3970    ) -> Task<Result<Entity<TextThreadEditor>>> {
3971        Task::ready(Err(anyhow!("opening remote context not implemented")))
3972    }
3973
3974    fn quote_selection(
3975        &self,
3976        workspace: &mut Workspace,
3977        selection_ranges: Vec<Range<Anchor>>,
3978        buffer: Entity<MultiBuffer>,
3979        window: &mut Window,
3980        cx: &mut Context<Workspace>,
3981    ) {
3982        let Some(panel) = workspace.panel::<AgentPanel>(cx) else {
3983            return;
3984        };
3985
3986        if !panel.focus_handle(cx).contains_focused(window, cx) {
3987            workspace.toggle_panel_focus::<AgentPanel>(window, cx);
3988        }
3989
3990        panel.update(cx, |_, cx| {
3991            // Wait to create a new context until the workspace is no longer
3992            // being updated.
3993            cx.defer_in(window, move |panel, window, cx| {
3994                if let Some(thread_view) = panel.active_thread_view() {
3995                    thread_view.update(cx, |thread_view, cx| {
3996                        thread_view.insert_selections(window, cx);
3997                    });
3998                } else if let Some(message_editor) = panel.active_message_editor() {
3999                    message_editor.update(cx, |message_editor, cx| {
4000                        message_editor.context_store().update(cx, |store, cx| {
4001                            let buffer = buffer.read(cx);
4002                            let selection_ranges = selection_ranges
4003                                .into_iter()
4004                                .flat_map(|range| {
4005                                    let (start_buffer, start) =
4006                                        buffer.text_anchor_for_position(range.start, cx)?;
4007                                    let (end_buffer, end) =
4008                                        buffer.text_anchor_for_position(range.end, cx)?;
4009                                    if start_buffer != end_buffer {
4010                                        return None;
4011                                    }
4012                                    Some((start_buffer, start..end))
4013                                })
4014                                .collect::<Vec<_>>();
4015
4016                            for (buffer, range) in selection_ranges {
4017                                store.add_selection(buffer, range, cx);
4018                            }
4019                        })
4020                    })
4021                } else if let Some(context_editor) = panel.active_context_editor() {
4022                    let snapshot = buffer.read(cx).snapshot(cx);
4023                    let selection_ranges = selection_ranges
4024                        .into_iter()
4025                        .map(|range| range.to_point(&snapshot))
4026                        .collect::<Vec<_>>();
4027
4028                    context_editor.update(cx, |context_editor, cx| {
4029                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
4030                    });
4031                }
4032            });
4033        });
4034    }
4035}
4036
4037struct OnboardingUpsell;
4038
4039impl Dismissable for OnboardingUpsell {
4040    const KEY: &'static str = "dismissed-trial-upsell";
4041}
4042
4043struct TrialEndUpsell;
4044
4045impl Dismissable for TrialEndUpsell {
4046    const KEY: &'static str = "dismissed-trial-end-upsell";
4047}