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