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