use acp_thread::{ AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk, AuthRequired, LoadError, MentionUri, RetryStatus, ThreadStatus, ToolCall, ToolCallContent, ToolCallStatus, UserMessageId,};use acp_thread::{AgentConnection, Plan};use action_log::{ActionLog, ActionLogTelemetry};use agent::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer};use agent_client_protocol::{self as acp, PromptCapabilities};use agent_servers::{AgentServer, AgentServerDelegate};use agent_settings::{AgentProfileId, AgentSettings, CompletionMode};use anyhow::{Result, anyhow};use arrayvec::ArrayVec;use audio::{Audio, Sound};use buffer_diff::BufferDiff;use client::zed_urls;use cloud_llm_client::PlanV1;use collections::{HashMap, HashSet};use editor::scroll::Autoscroll;use editor::{ Editor, EditorEvent, EditorMode, MultiBuffer, PathKey, SelectionEffects, SizingBehavior,};use file_icons::FileIcons;use fs::Fs;use futures::FutureExt as _;use gpui::{ Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div, ease_in_out, linear_color_stop, linear_gradient, list, point, pulsating_between,};use language::Buffer; use language_model::LanguageModelRegistry;use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle};use project::{AgentServerStore, ExternalAgentServerName, Project, ProjectEntryId};use prompt_store::{PromptId, PromptStore};use rope::Point;use settings::{NotifyWhenAgentWaiting, Settings as _, SettingsStore};use std::cell::RefCell;use std::path::Path;use std::sync::Arc;use std::time::Instant;use std::{collections::BTreeMap, rc::Rc, time::Duration};use terminal_view::terminal_panel::TerminalPanel;use theme::{AgentFontSize, ThemeSettings};use ui::{ Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, prelude::*, right_click_menu,};use util::{ResultExt, size::format_file_size, time::duration_alt_display};use workspace::{CollaboratorId, NewTerminal, Workspace};use zed_actions::agent::{Chat, ToggleModelSelector};use zed_actions::assistant::OpenRulesLibrary; use super::config_options::ConfigOptionsView;use super::entry_view_state::EntryViewState;use crate::acp::AcpModelSelectorPopover;use crate::acp::ModeSelector;use crate::acp::entry_view_state::{EntryViewEvent, ViewEvent};use crate::acp::message_editor::{MessageEditor, MessageEditorEvent};use crate::agent_diff::AgentDiff;use crate::profile_selector::{ProfileProvider, ProfileSelector}; use crate::ui::{AgentNotification, AgentNotificationEvent, BurnModeTooltip, UsageCallout};use crate::{ AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, ClearMessageQueue, ContinueThread, ContinueWithBurnMode, CycleFavoriteModels, CycleModeSelector, ExpandMessageEditor, Follow, KeepAll, NewThread, OpenHistory, QueueMessage, RejectAll, RejectOnce, SendNextQueuedMessage, ToggleBurnMode, ToggleProfileSelector,}; #[derive(Copy, Clone, Debug, PartialEq, Eq)]enum ThreadFeedback { Positive, Negative,} #[derive(Debug)]enum ThreadError { PaymentRequired, ModelRequestLimitReached(cloud_llm_client::Plan), ToolUseLimitReached, Refusal, AuthenticationRequired(SharedString), Other(SharedString),} impl ThreadError { fn from_err(error: anyhow::Error, agent: &Rc<dyn AgentServer>) -> Self { if error.is::<language_model::PaymentRequiredError>() { Self::PaymentRequired } else if error.is::<language_model::ToolUseLimitReachedError>() { Self::ToolUseLimitReached } else if let Some(error) = error.downcast_ref::<language_model::ModelRequestLimitReachedError>() { Self::ModelRequestLimitReached(error.plan) } else if let Some(acp_error) = error.downcast_ref::<acp::Error>() && acp_error.code == acp::ErrorCode::AuthRequired { Self::AuthenticationRequired(acp_error.message.clone().into()) } else { let string = format!("{:#}", error); // TODO: we should have Gemini return better errors here. if agent.clone().downcast::<agent_servers::Gemini>().is_some() && string.contains("Could not load the default credentials") || string.contains("API key not valid") || string.contains("Request had invalid authentication credentials") { Self::AuthenticationRequired(string.into()) } else { Self::Other(string.into()) } } }} impl ProfileProvider for Entity<agent::Thread> { fn profile_id(&self, cx: &App) -> AgentProfileId { self.read(cx).profile().clone() } fn set_profile(&self, profile_id: AgentProfileId, cx: &mut App) { self.update(cx, |thread, cx| { // Apply the profile and let the thread swap to its default model. thread.set_profile(profile_id, cx); }); } fn profiles_supported(&self, cx: &App) -> bool { self.read(cx) .model() .is_some_and(|model| model.supports_tools()) }} #[derive(Default)]struct ThreadFeedbackState { feedback: Option<ThreadFeedback>, comments_editor: Option<Entity<Editor>>,} impl ThreadFeedbackState { pub fn submit( &mut self, thread: Entity<AcpThread>, feedback: ThreadFeedback, window: &mut Window, cx: &mut App, ) { let Some(telemetry) = thread.read(cx).connection().telemetry() else { return; }; if self.feedback == Some(feedback) { return; } self.feedback = Some(feedback); match feedback { ThreadFeedback::Positive => { self.comments_editor = None; } ThreadFeedback::Negative => { self.comments_editor = Some(Self::build_feedback_comments_editor(window, cx)); } } let session_id = thread.read(cx).session_id().clone(); let agent_telemetry_id = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); let rating = match feedback { ThreadFeedback::Positive => "positive", ThreadFeedback::Negative => "negative", }; cx.background_spawn(async move { let thread = task.await?; telemetry::event!( "Agent Thread Rated", agent = agent_telemetry_id, session_id = session_id, rating = rating, thread = thread ); anyhow::Ok(()) }) .detach_and_log_err(cx); } pub fn submit_comments(&mut self, thread: Entity<AcpThread>, cx: &mut App) { let Some(telemetry) = thread.read(cx).connection().telemetry() else { return; }; let Some(comments) = self .comments_editor .as_ref() .map(|editor| editor.read(cx).text(cx)) .filter(|text| !text.trim().is_empty()) else { return; }; self.comments_editor.take(); let session_id = thread.read(cx).session_id().clone(); let agent_telemetry_id = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); cx.background_spawn(async move { let thread = task.await?; telemetry::event!( "Agent Thread Feedback Comments", agent = agent_telemetry_id, session_id = session_id, comments = comments, thread = thread ); anyhow::Ok(()) }) .detach_and_log_err(cx); } pub fn clear(&mut self) { *self = Self::default() } pub fn dismiss_comments(&mut self) { self.comments_editor.take(); } fn build_feedback_comments_editor(window: &mut Window, cx: &mut App) -> Entity<Editor> { let buffer = cx.new(|cx| { let empty_string = String::new(); MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx) }); let editor = cx.new(|cx| { let mut editor = Editor::new( editor::EditorMode::AutoHeight { min_lines: 1, max_lines: Some(4), }, buffer, None, window, cx, ); editor.set_placeholder_text( "What went wrong? Share your feedback so we can improve.", window, cx, ); editor }); editor.read(cx).focus_handle(cx).focus(window, cx); editor }} pub struct AcpThreadView { agent: Rc<dyn AgentServer>, agent_server_store: Entity<AgentServerStore>, workspace: WeakEntity<Workspace>, project: Entity<Project>, thread_state: ThreadState, login: Option<task::SpawnInTerminal>, history_store: Entity<HistoryStore>, hovered_recent_history_item: Option<usize>, entry_view_state: Entity<EntryViewState>, message_editor: Entity<MessageEditor>, focus_handle: FocusHandle, model_selector: Option<Entity<AcpModelSelectorPopover>>, config_options_view: Option<Entity<ConfigOptionsView>>, profile_selector: Option<Entity<ProfileSelector>>, notifications: Vec<WindowHandle<AgentNotification>>, notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>, thread_retry_status: Option<RetryStatus>, thread_error: Option<ThreadError>, thread_error_markdown: Option<Entity<Markdown>>, token_limit_callout_dismissed: bool, thread_feedback: ThreadFeedbackState, list_state: ListState, auth_task: Option<Task<()>>, expanded_tool_calls: HashSet<acp::ToolCallId>, expanded_thinking_blocks: HashSet<(usize, usize)>, edits_expanded: bool, plan_expanded: bool, queue_expanded: bool, editor_expanded: bool, should_be_following: bool, editing_message: Option<usize>, prompt_capabilities: Rc<RefCell<PromptCapabilities>>, available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>, is_loading_contents: bool, new_server_version_available: Option<SharedString>, resume_thread_metadata: Option<DbThreadMetadata>, _cancel_task: Option<Task<()>>, _subscriptions: [Subscription; 5], show_codex_windows_warning: bool, in_flight_prompt: Option<Vec<acp::ContentBlock>>, message_queue: Vec<QueuedMessage>, skip_queue_processing_count: usize, user_interrupted_generation: bool,} struct QueuedMessage { content: Vec<acp::ContentBlock>, tracked_buffers: Vec<Entity<Buffer>>,} enum ThreadState { Loading(Entity<LoadingView>), Ready { thread: Entity<AcpThread>, title_editor: Option<Entity<Editor>>, mode_selector: Option<Entity<ModeSelector>>, _subscriptions: Vec<Subscription>, }, LoadError(LoadError), Unauthenticated { connection: Rc<dyn AgentConnection>, description: Option<Entity<Markdown>>, configuration_view: Option<AnyView>, pending_auth_method: Option<acp::AuthMethodId>, _subscription: Option<Subscription>, },} struct LoadingView { title: SharedString, _load_task: Task<()>, _update_title_task: Task<anyhow::Result<()>>,} impl AcpThreadView { pub fn new( agent: Rc<dyn AgentServer>, resume_thread: Option<DbThreadMetadata>, summarize_thread: Option<DbThreadMetadata>, workspace: WeakEntity<Workspace>, project: Entity<Project>, history_store: Entity<HistoryStore>, prompt_store: Option<Entity<PromptStore>>, track_load_event: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default())); let available_commands = Rc::new(RefCell::new(vec![])); let agent_server_store = project.read(cx).agent_server_store().clone(); let agent_display_name = agent_server_store .read(cx) .agent_display_name(&ExternalAgentServerName(agent.name())) .unwrap_or_else(|| agent.name()); let placeholder = placeholder_text(agent_display_name.as_ref(), false); let message_editor = cx.new(|cx| { let mut editor = MessageEditor::new( workspace.clone(), project.downgrade(), history_store.clone(), prompt_store.clone(), prompt_capabilities.clone(), available_commands.clone(), agent.name(), &placeholder, editor::EditorMode::AutoHeight { min_lines: AgentSettings::get_global(cx).message_editor_min_lines, max_lines: Some(AgentSettings::get_global(cx).set_message_editor_max_lines()), }, window, cx, ); if let Some(entry) = summarize_thread { editor.insert_thread_summary(entry, window, cx); } editor }); let list_state = ListState::new(0, gpui::ListAlignment::Bottom, px(2048.0)); let entry_view_state = cx.new(|_| { EntryViewState::new( workspace.clone(), project.downgrade(), history_store.clone(), prompt_store.clone(), prompt_capabilities.clone(), available_commands.clone(), agent.name(), ) }); let subscriptions = [ cx.observe_global_in::<SettingsStore>(window, Self::agent_ui_font_size_changed), cx.observe_global_in::<AgentFontSize>(window, Self::agent_ui_font_size_changed), cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event), cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event), cx.subscribe_in( &agent_server_store, window, Self::handle_agent_servers_updated, ), ]; cx.on_release(|this, cx| { for window in this.notifications.drain(..) { window .update(cx, |_, window, _| { window.remove_window(); }) .ok(); } }) .detach(); let show_codex_windows_warning = cfg!(windows) && project.read(cx).is_local() && agent.clone().downcast::<agent_servers::Codex>().is_some(); Self { agent: agent.clone(), agent_server_store, workspace: workspace.clone(), project: project.clone(), entry_view_state, thread_state: Self::initial_state( agent.clone(), resume_thread.clone(), workspace.clone(), project.clone(), track_load_event, window, cx, ), login: None, message_editor, model_selector: None, config_options_view: None, profile_selector: None, notifications: Vec::new(), notification_subscriptions: HashMap::default(), list_state: list_state, thread_retry_status: None, thread_error: None, thread_error_markdown: None, token_limit_callout_dismissed: false, thread_feedback: Default::default(), auth_task: None, expanded_tool_calls: HashSet::default(), expanded_thinking_blocks: HashSet::default(), editing_message: None, edits_expanded: false, plan_expanded: false, queue_expanded: true, prompt_capabilities, available_commands, editor_expanded: false, should_be_following: false, history_store, hovered_recent_history_item: None, is_loading_contents: false, _subscriptions: subscriptions, _cancel_task: None, focus_handle: cx.focus_handle(), new_server_version_available: None, resume_thread_metadata: resume_thread, show_codex_windows_warning, in_flight_prompt: None, message_queue: Vec::new(), skip_queue_processing_count: 0, user_interrupted_generation: false, } } fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.thread_state = Self::initial_state( self.agent.clone(), self.resume_thread_metadata.clone(), self.workspace.clone(), self.project.clone(), true, window, cx, ); self.available_commands.replace(vec![]); self.new_server_version_available.take(); self.message_queue.clear(); cx.notify(); } fn initial_state( agent: Rc<dyn AgentServer>, resume_thread: Option<DbThreadMetadata>, workspace: WeakEntity<Workspace>,Lines 1–500 of 7976. Load more
use acp_thread::{ AcpThread, AcpThreadEvent, AgentThreadEntry, AssistantMessage, AssistantMessageChunk, AuthRequired, LoadError, MentionUri, RetryStatus, ThreadStatus, ToolCall, ToolCallContent, ToolCallStatus, UserMessageId, }; use acp_thread::{AgentConnection, Plan}; use action_log::{ActionLog, ActionLogTelemetry}; use agent::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer}; use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, AgentServerDelegate}; use agent_settings::{AgentProfileId, AgentSettings, CompletionMode}; use anyhow::{Result, anyhow}; use arrayvec::ArrayVec; use audio::{Audio, Sound}; use buffer_diff::BufferDiff; use client::zed_urls; use cloud_llm_client::PlanV1; use collections::{HashMap, HashSet}; use editor::scroll::Autoscroll; use editor::{ Editor, EditorEvent, EditorMode, MultiBuffer, PathKey, SelectionEffects, SizingBehavior, }; use file_icons::FileIcons; use fs::Fs; use futures::FutureExt as _; use gpui::{ Action, Animation, AnimationExt, AnyView, App, BorderStyle, ClickEvent, CursorStyle, EdgesRefinement, ElementId, Empty, Entity, FocusHandle, Focusable, Hsla, Length, ListOffset, ListState, PlatformDisplay, SharedString, StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, UnderlineStyle, WeakEntity, Window, WindowHandle, div, ease_in_out, linear_color_stop, linear_gradient, list, point, pulsating_between, }; use language::Buffer; use language_model::LanguageModelRegistry; use markdown::{HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle}; use project::{AgentServerStore, ExternalAgentServerName, Project, ProjectEntryId}; use prompt_store::{PromptId, PromptStore}; use rope::Point; use settings::{NotifyWhenAgentWaiting, Settings as _, SettingsStore}; use std::cell::RefCell; use std::path::Path; use std::sync::Arc; use std::time::Instant; use std::{collections::BTreeMap, rc::Rc, time::Duration}; use terminal_view::terminal_panel::TerminalPanel; use theme::{AgentFontSize, ThemeSettings}; use ui::{ Callout, CommonAnimationExt, ContextMenu, ContextMenuEntry, CopyButton, Disclosure, Divider, DividerColor, ElevationIndex, KeyBinding, PopoverMenuHandle, SpinnerLabel, TintColor, Tooltip, WithScrollbar, prelude::*, right_click_menu, }; use util::{ResultExt, size::format_file_size, time::duration_alt_display}; use workspace::{CollaboratorId, NewTerminal, Workspace}; use zed_actions::agent::{Chat, ToggleModelSelector}; use zed_actions::assistant::OpenRulesLibrary; use super::config_options::ConfigOptionsView; use super::entry_view_state::EntryViewState; use crate::acp::AcpModelSelectorPopover; use crate::acp::ModeSelector; use crate::acp::entry_view_state::{EntryViewEvent, ViewEvent}; use crate::acp::message_editor::{MessageEditor, MessageEditorEvent}; use crate::agent_diff::AgentDiff; use crate::profile_selector::{ProfileProvider, ProfileSelector}; use crate::ui::{AgentNotification, AgentNotificationEvent, BurnModeTooltip, UsageCallout}; use crate::{ AgentDiffPane, AgentPanel, AllowAlways, AllowOnce, ClearMessageQueue, ContinueThread, ContinueWithBurnMode, CycleFavoriteModels, CycleModeSelector, ExpandMessageEditor, Follow, KeepAll, NewThread, OpenHistory, QueueMessage, RejectAll, RejectOnce, SendNextQueuedMessage, ToggleBurnMode, ToggleProfileSelector, }; #[derive(Copy, Clone, Debug, PartialEq, Eq)] enum ThreadFeedback { Positive, Negative, } #[derive(Debug)] enum ThreadError { PaymentRequired, ModelRequestLimitReached(cloud_llm_client::Plan), ToolUseLimitReached, Refusal, AuthenticationRequired(SharedString), Other(SharedString), } impl ThreadError { fn from_err(error: anyhow::Error, agent: &Rc<dyn AgentServer>) -> Self { if error.is::<language_model::PaymentRequiredError>() { Self::PaymentRequired } else if error.is::<language_model::ToolUseLimitReachedError>() { Self::ToolUseLimitReached } else if let Some(error) = error.downcast_ref::<language_model::ModelRequestLimitReachedError>() { Self::ModelRequestLimitReached(error.plan) } else if let Some(acp_error) = error.downcast_ref::<acp::Error>() && acp_error.code == acp::ErrorCode::AuthRequired { Self::AuthenticationRequired(acp_error.message.clone().into()) } else { let string = format!("{:#}", error); // TODO: we should have Gemini return better errors here. if agent.clone().downcast::<agent_servers::Gemini>().is_some() && string.contains("Could not load the default credentials") || string.contains("API key not valid") || string.contains("Request had invalid authentication credentials") { Self::AuthenticationRequired(string.into()) } else { Self::Other(string.into()) } } } } impl ProfileProvider for Entity<agent::Thread> { fn profile_id(&self, cx: &App) -> AgentProfileId { self.read(cx).profile().clone() } fn set_profile(&self, profile_id: AgentProfileId, cx: &mut App) { self.update(cx, |thread, cx| { // Apply the profile and let the thread swap to its default model. thread.set_profile(profile_id, cx); }); } fn profiles_supported(&self, cx: &App) -> bool { self.read(cx) .model() .is_some_and(|model| model.supports_tools()) } } #[derive(Default)] struct ThreadFeedbackState { feedback: Option<ThreadFeedback>, comments_editor: Option<Entity<Editor>>, } impl ThreadFeedbackState { pub fn submit( &mut self, thread: Entity<AcpThread>, feedback: ThreadFeedback, window: &mut Window, cx: &mut App, ) { let Some(telemetry) = thread.read(cx).connection().telemetry() else { return; }; if self.feedback == Some(feedback) { return; } self.feedback = Some(feedback); match feedback { ThreadFeedback::Positive => { self.comments_editor = None; } ThreadFeedback::Negative => { self.comments_editor = Some(Self::build_feedback_comments_editor(window, cx)); } } let session_id = thread.read(cx).session_id().clone(); let agent_telemetry_id = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); let rating = match feedback { ThreadFeedback::Positive => "positive", ThreadFeedback::Negative => "negative", }; cx.background_spawn(async move { let thread = task.await?; telemetry::event!( "Agent Thread Rated", agent = agent_telemetry_id, session_id = session_id, rating = rating, thread = thread ); anyhow::Ok(()) }) .detach_and_log_err(cx); } pub fn submit_comments(&mut self, thread: Entity<AcpThread>, cx: &mut App) { let Some(telemetry) = thread.read(cx).connection().telemetry() else { return; }; let Some(comments) = self .comments_editor .as_ref() .map(|editor| editor.read(cx).text(cx)) .filter(|text| !text.trim().is_empty()) else { return; }; self.comments_editor.take(); let session_id = thread.read(cx).session_id().clone(); let agent_telemetry_id = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); cx.background_spawn(async move { let thread = task.await?; telemetry::event!( "Agent Thread Feedback Comments", agent = agent_telemetry_id, session_id = session_id, comments = comments, thread = thread ); anyhow::Ok(()) }) .detach_and_log_err(cx); } pub fn clear(&mut self) { *self = Self::default() } pub fn dismiss_comments(&mut self) { self.comments_editor.take(); } fn build_feedback_comments_editor(window: &mut Window, cx: &mut App) -> Entity<Editor> { let buffer = cx.new(|cx| { let empty_string = String::new(); MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx) }); let editor = cx.new(|cx| { let mut editor = Editor::new( editor::EditorMode::AutoHeight { min_lines: 1, max_lines: Some(4), }, buffer, None, window, cx, ); editor.set_placeholder_text( "What went wrong? Share your feedback so we can improve.", window, cx, ); editor }); editor.read(cx).focus_handle(cx).focus(window, cx); editor } } pub struct AcpThreadView { agent: Rc<dyn AgentServer>, agent_server_store: Entity<AgentServerStore>, workspace: WeakEntity<Workspace>, project: Entity<Project>, thread_state: ThreadState, login: Option<task::SpawnInTerminal>, history_store: Entity<HistoryStore>, hovered_recent_history_item: Option<usize>, entry_view_state: Entity<EntryViewState>, message_editor: Entity<MessageEditor>, focus_handle: FocusHandle, model_selector: Option<Entity<AcpModelSelectorPopover>>, config_options_view: Option<Entity<ConfigOptionsView>>, profile_selector: Option<Entity<ProfileSelector>>, notifications: Vec<WindowHandle<AgentNotification>>, notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>, thread_retry_status: Option<RetryStatus>, thread_error: Option<ThreadError>, thread_error_markdown: Option<Entity<Markdown>>, token_limit_callout_dismissed: bool, thread_feedback: ThreadFeedbackState, list_state: ListState, auth_task: Option<Task<()>>, expanded_tool_calls: HashSet<acp::ToolCallId>, expanded_thinking_blocks: HashSet<(usize, usize)>, edits_expanded: bool, plan_expanded: bool, queue_expanded: bool, editor_expanded: bool, should_be_following: bool, editing_message: Option<usize>, prompt_capabilities: Rc<RefCell<PromptCapabilities>>, available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>, is_loading_contents: bool, new_server_version_available: Option<SharedString>, resume_thread_metadata: Option<DbThreadMetadata>, _cancel_task: Option<Task<()>>, _subscriptions: [Subscription; 5], show_codex_windows_warning: bool, in_flight_prompt: Option<Vec<acp::ContentBlock>>, message_queue: Vec<QueuedMessage>, skip_queue_processing_count: usize, user_interrupted_generation: bool, } struct QueuedMessage { content: Vec<acp::ContentBlock>, tracked_buffers: Vec<Entity<Buffer>>, } enum ThreadState { Loading(Entity<LoadingView>), Ready { thread: Entity<AcpThread>, title_editor: Option<Entity<Editor>>, mode_selector: Option<Entity<ModeSelector>>, _subscriptions: Vec<Subscription>, }, LoadError(LoadError), Unauthenticated { connection: Rc<dyn AgentConnection>, description: Option<Entity<Markdown>>, configuration_view: Option<AnyView>, pending_auth_method: Option<acp::AuthMethodId>, _subscription: Option<Subscription>, }, } struct LoadingView { title: SharedString, _load_task: Task<()>, _update_title_task: Task<anyhow::Result<()>>, } impl AcpThreadView { pub fn new( agent: Rc<dyn AgentServer>, resume_thread: Option<DbThreadMetadata>, summarize_thread: Option<DbThreadMetadata>, workspace: WeakEntity<Workspace>, project: Entity<Project>, history_store: Entity<HistoryStore>, prompt_store: Option<Entity<PromptStore>>, track_load_event: bool, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default())); let available_commands = Rc::new(RefCell::new(vec![])); let agent_server_store = project.read(cx).agent_server_store().clone(); let agent_display_name = agent_server_store .read(cx) .agent_display_name(&ExternalAgentServerName(agent.name())) .unwrap_or_else(|| agent.name()); let placeholder = placeholder_text(agent_display_name.as_ref(), false); let message_editor = cx.new(|cx| { let mut editor = MessageEditor::new( workspace.clone(), project.downgrade(), history_store.clone(), prompt_store.clone(), prompt_capabilities.clone(), available_commands.clone(), agent.name(), &placeholder, editor::EditorMode::AutoHeight { min_lines: AgentSettings::get_global(cx).message_editor_min_lines, max_lines: Some(AgentSettings::get_global(cx).set_message_editor_max_lines()), }, window, cx, ); if let Some(entry) = summarize_thread { editor.insert_thread_summary(entry, window, cx); } editor }); let list_state = ListState::new(0, gpui::ListAlignment::Bottom, px(2048.0)); let entry_view_state = cx.new(|_| { EntryViewState::new( workspace.clone(), project.downgrade(), history_store.clone(), prompt_store.clone(), prompt_capabilities.clone(), available_commands.clone(), agent.name(), ) }); let subscriptions = [ cx.observe_global_in::<SettingsStore>(window, Self::agent_ui_font_size_changed), cx.observe_global_in::<AgentFontSize>(window, Self::agent_ui_font_size_changed), cx.subscribe_in(&message_editor, window, Self::handle_message_editor_event), cx.subscribe_in(&entry_view_state, window, Self::handle_entry_view_event), cx.subscribe_in( &agent_server_store, window, Self::handle_agent_servers_updated, ), ]; cx.on_release(|this, cx| { for window in this.notifications.drain(..) { window .update(cx, |_, window, _| { window.remove_window(); }) .ok(); } }) .detach(); let show_codex_windows_warning = cfg!(windows) && project.read(cx).is_local() && agent.clone().downcast::<agent_servers::Codex>().is_some(); Self { agent: agent.clone(), agent_server_store, workspace: workspace.clone(), project: project.clone(), entry_view_state, thread_state: Self::initial_state( agent.clone(), resume_thread.clone(), workspace.clone(), project.clone(), track_load_event, window, cx, ), login: None, message_editor, model_selector: None, config_options_view: None, profile_selector: None, notifications: Vec::new(), notification_subscriptions: HashMap::default(), list_state: list_state, thread_retry_status: None, thread_error: None, thread_error_markdown: None, token_limit_callout_dismissed: false, thread_feedback: Default::default(), auth_task: None, expanded_tool_calls: HashSet::default(), expanded_thinking_blocks: HashSet::default(), editing_message: None, edits_expanded: false, plan_expanded: false, queue_expanded: true, prompt_capabilities, available_commands, editor_expanded: false, should_be_following: false, history_store, hovered_recent_history_item: None, is_loading_contents: false, _subscriptions: subscriptions, _cancel_task: None, focus_handle: cx.focus_handle(), new_server_version_available: None, resume_thread_metadata: resume_thread, show_codex_windows_warning, in_flight_prompt: None, message_queue: Vec::new(), skip_queue_processing_count: 0, user_interrupted_generation: false, } } fn reset(&mut self, window: &mut Window, cx: &mut Context<Self>) { self.thread_state = Self::initial_state( self.agent.clone(), self.resume_thread_metadata.clone(), self.workspace.clone(), self.project.clone(), true, window, cx, ); self.available_commands.replace(vec![]); self.new_server_version_available.take(); self.message_queue.clear(); cx.notify(); } fn initial_state( agent: Rc<dyn AgentServer>, resume_thread: Option<DbThreadMetadata>, workspace: WeakEntity<Workspace>,