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