agent_panel.rs

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