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