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