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