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