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