use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCommentsBody};use gpui::{Corner, List};use language_model::LanguageModelEffortLevel;use settings::update_settings_file;use ui::{ButtonLike, SplitButton, SplitButtonStyle, Tab}; use super::*; #[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; }; let project = thread.read(cx).project().read(cx); let client = project.client(); let user_store = project.user_store(); let organization = user_store.read(cx).current_organization(); 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?; client .cloud_client() .submit_agent_feedback(SubmitAgentThreadFeedbackBody { organization_id: organization.map(|organization| organization.id.clone()), agent: agent_telemetry_id.to_string(), session_id: session_id.to_string(), rating: rating.to_string(), thread, }) .await?; 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 project = thread.read(cx).project().read(cx); let client = project.client(); let user_store = project.user_store(); let organization = user_store.read(cx).current_organization(); 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?; client .cloud_client() .submit_agent_feedback_comments(SubmitAgentThreadFeedbackCommentsBody { organization_id: organization.map(|organization| organization.id.clone()), agent: agent_telemetry_id.to_string(), session_id: session_id.to_string(), comments, thread, }) .await?; 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 }} #[derive(Default, Clone, Copy)]struct DiffStats { lines_added: u32, lines_removed: u32,} impl DiffStats { fn single_file(buffer: &Buffer, diff: &BufferDiff, cx: &App) -> Self { let mut stats = DiffStats::default(); let diff_snapshot = diff.snapshot(cx); let buffer_snapshot = buffer.snapshot(); let base_text = diff_snapshot.base_text(); for hunk in diff_snapshot.hunks(&buffer_snapshot) { let added_rows = hunk.range.end.row.saturating_sub(hunk.range.start.row); stats.lines_added += added_rows; let base_start = hunk.diff_base_byte_range.start.to_point(base_text).row; let base_end = hunk.diff_base_byte_range.end.to_point(base_text).row; let removed_rows = base_end.saturating_sub(base_start); stats.lines_removed += removed_rows; } stats } fn all_files(changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>, cx: &App) -> Self { let mut total = DiffStats::default(); for (buffer, diff) in changed_buffers { let stats = DiffStats::single_file(buffer.read(cx), diff.read(cx), cx); total.lines_added += stats.lines_added; total.lines_removed += stats.lines_removed; } total }} pub struct AcpThreadView { pub id: acp::SessionId, pub parent_id: Option<acp::SessionId>, pub login: Option<task::SpawnInTerminal>, // is some <=> Active | Unauthenticated pub thread: Entity<AcpThread>, pub(crate) conversation: Entity<super::Conversation>, pub server_view: WeakEntity<AcpServerView>, pub agent_icon: IconName, pub agent_name: SharedString, pub focus_handle: FocusHandle, pub workspace: WeakEntity<Workspace>, pub entry_view_state: Entity<EntryViewState>, pub title_editor: Entity<Editor>, pub config_options_view: Option<Entity<ConfigOptionsView>>, pub mode_selector: Option<Entity<ModeSelector>>, pub model_selector: Option<Entity<AcpModelSelectorPopover>>, pub profile_selector: Option<Entity<ProfileSelector>>, pub permission_dropdown_handle: PopoverMenuHandle<ContextMenu>, pub thread_retry_status: Option<RetryStatus>, pub(super) thread_error: Option<ThreadError>, pub thread_error_markdown: Option<Entity<Markdown>>, pub token_limit_callout_dismissed: bool, pub last_token_limit_telemetry: Option<acp_thread::TokenUsageRatio>, thread_feedback: ThreadFeedbackState, pub list_state: ListState, pub prompt_capabilities: Rc<RefCell<PromptCapabilities>>, pub available_commands: Rc<RefCell<Vec<agent_client_protocol::AvailableCommand>>>, /// Tracks which tool calls have their content/output expanded. /// Used for showing/hiding tool call results, terminal output, etc. pub expanded_tool_calls: HashSet<agent_client_protocol::ToolCallId>, pub expanded_tool_call_raw_inputs: HashSet<agent_client_protocol::ToolCallId>, pub expanded_thinking_blocks: HashSet<(usize, usize)>, pub subagent_scroll_handles: RefCell<HashMap<agent_client_protocol::SessionId, ScrollHandle>>, pub edits_expanded: bool, pub plan_expanded: bool, pub queue_expanded: bool, pub editor_expanded: bool, pub should_be_following: bool, pub editing_message: Option<usize>, pub local_queued_messages: Vec<QueuedMessage>, pub queued_message_editors: Vec<Entity<MessageEditor>>, pub queued_message_editor_subscriptions: Vec<Subscription>, pub last_synced_queue_length: usize, pub turn_fields: TurnFields, pub discarded_partial_edits: HashSet<agent_client_protocol::ToolCallId>, pub is_loading_contents: bool, pub new_server_version_available: Option<SharedString>, pub resumed_without_history: bool, /// Tracks the selected granularity index for each tool call's permission dropdown. /// The index corresponds to the position in the allow_options list. /// Default is the last option (index pointing to "Only this time"). pub selected_permission_granularity: HashMap<agent_client_protocol::ToolCallId, usize>, pub resume_thread_metadata: Option<AgentSessionInfo>, pub _cancel_task: Option<Task<()>>, pub skip_queue_processing_count: usize, pub user_interrupted_generation: bool, pub can_fast_track_queue: bool, pub hovered_edited_file_buttons: Option<usize>, pub in_flight_prompt: Option<Vec<acp::ContentBlock>>, pub _subscriptions: Vec<Subscription>, pub message_editor: Entity<MessageEditor>, pub add_context_menu_handle: PopoverMenuHandle<ContextMenu>, pub thinking_effort_menu_handle: PopoverMenuHandle<ContextMenu>, pub project: WeakEntity<Project>, pub recent_history_entries: Vec<AgentSessionInfo>, pub hovered_recent_history_item: Option<usize>, pub show_codex_windows_warning: bool, pub history: Entity<AcpThreadHistory>, pub _history_subscription: Subscription,}impl Focusable for AcpThreadView { fn focus_handle(&self, cx: &App) -> FocusHandle { if self.parent_id.is_some() { self.focus_handle.clone() } else { self.active_editor(cx).focus_handle(cx) } }} #[derive(Default)]pub struct TurnFields { pub _turn_timer_task: Option<Task<()>>, pub last_turn_duration: Option<Duration>, pub last_turn_tokens: Option<u64>, pub turn_generation: usize, pub turn_started_at: Option<Instant>, pub turn_tokens: Option<u64>,} impl AcpThreadView { pub(crate) fn new( parent_id: Option<acp::SessionId>, thread: Entity<AcpThread>, conversation: Entity<super::Conversation>, login: Option<task::SpawnInTerminal>, server_view: WeakEntity<AcpServerView>, agent_icon: IconName, agent_name: SharedString, agent_display_name: SharedString, workspace: WeakEntity<Workspace>, entry_view_state: Entity<EntryViewState>, config_options_view: Option<Entity<ConfigOptionsView>>, mode_selector: Option<Entity<ModeSelector>>, model_selector: Option<Entity<AcpModelSelectorPopover>>, profile_selector: Option<Entity<ProfileSelector>>, list_state: ListState, prompt_capabilities: Rc<RefCell<PromptCapabilities>>, available_commands: Rc<RefCell<Vec<agent_client_protocol::AvailableCommand>>>, resumed_without_history: bool, resume_thread_metadata: Option<AgentSessionInfo>, project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>, history: Entity<AcpThreadHistory>, prompt_store: Option<Entity<PromptStore>>, initial_content: Option<AgentInitialContent>, mut subscriptions: Vec<Subscription>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let id = thread.read(cx).session_id().clone(); let placeholder = placeholder_text(agent_display_name.as_ref(), false); let history_subscription = cx.observe(&history, |this, history, cx| { this.update_recent_history_from_cache(&history, cx); }); let mut should_auto_submit = false; let message_editor = cx.new(|cx| { let mut editor = MessageEditor::new( workspace.clone(), project.clone(), thread_store, history.downgrade(), prompt_store, prompt_capabilities.clone(), available_commands.clone(), agent_name.clone(), &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(content) = initial_content { match content { AgentInitialContent::ThreadSummary(entry) => { editor.insert_thread_summary(entry, window, cx); } AgentInitialContent::ContentBlock { blocks, auto_submit, } => { should_auto_submit = auto_submit; editor.set_message(blocks, window, cx); } } } editor }); let show_codex_windows_warning = cfg!(windows) && project.upgrade().is_some_and(|p| p.read(cx).is_local()) && agent_name == "Codex"; let title_editor = { let can_edit = thread.update(cx, |thread, cx| thread.can_set_title(cx)); let editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); editor.set_text(thread.read(cx).title(), window, cx); editor.set_read_only(!can_edit); editor }); subscriptions.push(cx.subscribe_in(&editor, window, Self::handle_title_editor_event)); editor }; subscriptions.push(cx.subscribe_in( &entry_view_state, window, Self::handle_entry_view_event, )); subscriptions.push(cx.subscribe_in( &message_editor, window, Self::handle_message_editor_event, )); let recent_history_entries = history.read(cx).get_recent_sessions(3); let mut this = Self { id, parent_id, focus_handle: cx.focus_handle(), thread, conversation, login, server_view, agent_icon, agent_name, workspace, entry_view_state, title_editor, config_options_view, mode_selector, model_selector, profile_selector, list_state, prompt_capabilities, available_commands, resumed_without_history, resume_thread_metadata, _subscriptions: subscriptions, permission_dropdown_handle: PopoverMenuHandle::default(), thread_retry_status: None, thread_error: None, thread_error_markdown: None, token_limit_callout_dismissed: false, last_token_limit_telemetry: None, thread_feedback: Default::default(), expanded_tool_calls: HashSet::default(), expanded_tool_call_raw_inputs: HashSet::default(), expanded_thinking_blocks: HashSet::default(), subagent_scroll_handles: RefCell::new(HashMap::default()), edits_expanded: false, plan_expanded: false, queue_expanded: true, editor_expanded: false, should_be_following: false, editing_message: None, local_queued_messages: Vec::new(), queued_message_editors: Vec::new(), queued_message_editor_subscriptions: Vec::new(), last_synced_queue_length: 0, turn_fields: TurnFields::default(), discarded_partial_edits: HashSet::default(), is_loading_contents: false, new_server_version_available: None, selected_permission_granularity: HashMap::default(), _cancel_task: None, skip_queue_processing_count: 0, user_interrupted_generation: false, can_fast_track_queue: false, hovered_edited_file_buttons: None, in_flight_prompt: None, message_editor, add_context_menu_handle: PopoverMenuHandle::default(), thinking_effort_menu_handle: PopoverMenuHandle::default(), project, recent_history_entries, hovered_recent_history_item: None, history, _history_subscription: history_subscription, show_codex_windows_warning, }; if should_auto_submit { this.send(window, cx); } this } pub fn handle_message_editor_event( &mut self, _editor: &Entity<MessageEditor>, event: &MessageEditorEvent, window: &mut Window, cx: &mut Context<Self>, ) { match event { MessageEditorEvent::Send => self.send(window, cx), MessageEditorEvent::SendImmediately => self.interrupt_and_send(window, cx), MessageEditorEvent::Cancel => self.cancel_generation(cx), MessageEditorEvent::Focus => { self.cancel_editing(&Default::default(), window, cx); } MessageEditorEvent::LostFocus => {} } } pub(crate) fn as_native_connection( &self, cx: &App, ) -> Option<Rc<agent::NativeAgentConnection>> { let acp_thread = self.thread.read(cx); acp_thread.connection().clone().downcast() } pub(crate) fn as_native_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> { let acp_thread = self.thread.read(cx); self.as_native_connection(cx)? .thread(acp_thread.session_id(), cx) } pub fn current_model_id(&self, cx: &App) -> Option<String> { let selector = self.model_selector.as_ref()?; let model = selector.read(cx).active_model(cx)?; Some(model.id.to_string()) } pub fn current_mode_id(&self, cx: &App) -> Option<Arc<str>> { if let Some(thread) = self.as_native_thread(cx) { Some(thread.read(cx).profile().0.clone()) } else { let mode_selector = self.mode_selector.as_ref()?; Some(mode_selector.read(cx).mode().0) } }Lines 1–500 of 7376. Load more
use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCommentsBody}; use gpui::{Corner, List}; use language_model::LanguageModelEffortLevel; use settings::update_settings_file; use ui::{ButtonLike, SplitButton, SplitButtonStyle, Tab}; use super::*; #[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; }; let project = thread.read(cx).project().read(cx); let client = project.client(); let user_store = project.user_store(); let organization = user_store.read(cx).current_organization(); 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?; client .cloud_client() .submit_agent_feedback(SubmitAgentThreadFeedbackBody { organization_id: organization.map(|organization| organization.id.clone()), agent: agent_telemetry_id.to_string(), session_id: session_id.to_string(), rating: rating.to_string(), thread, }) .await?; 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 project = thread.read(cx).project().read(cx); let client = project.client(); let user_store = project.user_store(); let organization = user_store.read(cx).current_organization(); 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?; client .cloud_client() .submit_agent_feedback_comments(SubmitAgentThreadFeedbackCommentsBody { organization_id: organization.map(|organization| organization.id.clone()), agent: agent_telemetry_id.to_string(), session_id: session_id.to_string(), comments, thread, }) .await?; 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 } } #[derive(Default, Clone, Copy)] struct DiffStats { lines_added: u32, lines_removed: u32, } impl DiffStats { fn single_file(buffer: &Buffer, diff: &BufferDiff, cx: &App) -> Self { let mut stats = DiffStats::default(); let diff_snapshot = diff.snapshot(cx); let buffer_snapshot = buffer.snapshot(); let base_text = diff_snapshot.base_text(); for hunk in diff_snapshot.hunks(&buffer_snapshot) { let added_rows = hunk.range.end.row.saturating_sub(hunk.range.start.row); stats.lines_added += added_rows; let base_start = hunk.diff_base_byte_range.start.to_point(base_text).row; let base_end = hunk.diff_base_byte_range.end.to_point(base_text).row; let removed_rows = base_end.saturating_sub(base_start); stats.lines_removed += removed_rows; } stats } fn all_files(changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>, cx: &App) -> Self { let mut total = DiffStats::default(); for (buffer, diff) in changed_buffers { let stats = DiffStats::single_file(buffer.read(cx), diff.read(cx), cx); total.lines_added += stats.lines_added; total.lines_removed += stats.lines_removed; } total } } pub struct AcpThreadView { pub id: acp::SessionId, pub parent_id: Option<acp::SessionId>, pub login: Option<task::SpawnInTerminal>, // is some <=> Active | Unauthenticated pub thread: Entity<AcpThread>, pub(crate) conversation: Entity<super::Conversation>, pub server_view: WeakEntity<AcpServerView>, pub agent_icon: IconName, pub agent_name: SharedString, pub focus_handle: FocusHandle, pub workspace: WeakEntity<Workspace>, pub entry_view_state: Entity<EntryViewState>, pub title_editor: Entity<Editor>, pub config_options_view: Option<Entity<ConfigOptionsView>>, pub mode_selector: Option<Entity<ModeSelector>>, pub model_selector: Option<Entity<AcpModelSelectorPopover>>, pub profile_selector: Option<Entity<ProfileSelector>>, pub permission_dropdown_handle: PopoverMenuHandle<ContextMenu>, pub thread_retry_status: Option<RetryStatus>, pub(super) thread_error: Option<ThreadError>, pub thread_error_markdown: Option<Entity<Markdown>>, pub token_limit_callout_dismissed: bool, pub last_token_limit_telemetry: Option<acp_thread::TokenUsageRatio>, thread_feedback: ThreadFeedbackState, pub list_state: ListState, pub prompt_capabilities: Rc<RefCell<PromptCapabilities>>, pub available_commands: Rc<RefCell<Vec<agent_client_protocol::AvailableCommand>>>, /// Tracks which tool calls have their content/output expanded. /// Used for showing/hiding tool call results, terminal output, etc. pub expanded_tool_calls: HashSet<agent_client_protocol::ToolCallId>, pub expanded_tool_call_raw_inputs: HashSet<agent_client_protocol::ToolCallId>, pub expanded_thinking_blocks: HashSet<(usize, usize)>, pub subagent_scroll_handles: RefCell<HashMap<agent_client_protocol::SessionId, ScrollHandle>>, pub edits_expanded: bool, pub plan_expanded: bool, pub queue_expanded: bool, pub editor_expanded: bool, pub should_be_following: bool, pub editing_message: Option<usize>, pub local_queued_messages: Vec<QueuedMessage>, pub queued_message_editors: Vec<Entity<MessageEditor>>, pub queued_message_editor_subscriptions: Vec<Subscription>, pub last_synced_queue_length: usize, pub turn_fields: TurnFields, pub discarded_partial_edits: HashSet<agent_client_protocol::ToolCallId>, pub is_loading_contents: bool, pub new_server_version_available: Option<SharedString>, pub resumed_without_history: bool, /// Tracks the selected granularity index for each tool call's permission dropdown. /// The index corresponds to the position in the allow_options list. /// Default is the last option (index pointing to "Only this time"). pub selected_permission_granularity: HashMap<agent_client_protocol::ToolCallId, usize>, pub resume_thread_metadata: Option<AgentSessionInfo>, pub _cancel_task: Option<Task<()>>, pub skip_queue_processing_count: usize, pub user_interrupted_generation: bool, pub can_fast_track_queue: bool, pub hovered_edited_file_buttons: Option<usize>, pub in_flight_prompt: Option<Vec<acp::ContentBlock>>, pub _subscriptions: Vec<Subscription>, pub message_editor: Entity<MessageEditor>, pub add_context_menu_handle: PopoverMenuHandle<ContextMenu>, pub thinking_effort_menu_handle: PopoverMenuHandle<ContextMenu>, pub project: WeakEntity<Project>, pub recent_history_entries: Vec<AgentSessionInfo>, pub hovered_recent_history_item: Option<usize>, pub show_codex_windows_warning: bool, pub history: Entity<AcpThreadHistory>, pub _history_subscription: Subscription, } impl Focusable for AcpThreadView { fn focus_handle(&self, cx: &App) -> FocusHandle { if self.parent_id.is_some() { self.focus_handle.clone() } else { self.active_editor(cx).focus_handle(cx) } } } #[derive(Default)] pub struct TurnFields { pub _turn_timer_task: Option<Task<()>>, pub last_turn_duration: Option<Duration>, pub last_turn_tokens: Option<u64>, pub turn_generation: usize, pub turn_started_at: Option<Instant>, pub turn_tokens: Option<u64>, } impl AcpThreadView { pub(crate) fn new( parent_id: Option<acp::SessionId>, thread: Entity<AcpThread>, conversation: Entity<super::Conversation>, login: Option<task::SpawnInTerminal>, server_view: WeakEntity<AcpServerView>, agent_icon: IconName, agent_name: SharedString, agent_display_name: SharedString, workspace: WeakEntity<Workspace>, entry_view_state: Entity<EntryViewState>, config_options_view: Option<Entity<ConfigOptionsView>>, mode_selector: Option<Entity<ModeSelector>>, model_selector: Option<Entity<AcpModelSelectorPopover>>, profile_selector: Option<Entity<ProfileSelector>>, list_state: ListState, prompt_capabilities: Rc<RefCell<PromptCapabilities>>, available_commands: Rc<RefCell<Vec<agent_client_protocol::AvailableCommand>>>, resumed_without_history: bool, resume_thread_metadata: Option<AgentSessionInfo>, project: WeakEntity<Project>, thread_store: Option<Entity<ThreadStore>>, history: Entity<AcpThreadHistory>, prompt_store: Option<Entity<PromptStore>>, initial_content: Option<AgentInitialContent>, mut subscriptions: Vec<Subscription>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let id = thread.read(cx).session_id().clone(); let placeholder = placeholder_text(agent_display_name.as_ref(), false); let history_subscription = cx.observe(&history, |this, history, cx| { this.update_recent_history_from_cache(&history, cx); }); let mut should_auto_submit = false; let message_editor = cx.new(|cx| { let mut editor = MessageEditor::new( workspace.clone(), project.clone(), thread_store, history.downgrade(), prompt_store, prompt_capabilities.clone(), available_commands.clone(), agent_name.clone(), &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(content) = initial_content { match content { AgentInitialContent::ThreadSummary(entry) => { editor.insert_thread_summary(entry, window, cx); } AgentInitialContent::ContentBlock { blocks, auto_submit, } => { should_auto_submit = auto_submit; editor.set_message(blocks, window, cx); } } } editor }); let show_codex_windows_warning = cfg!(windows) && project.upgrade().is_some_and(|p| p.read(cx).is_local()) && agent_name == "Codex"; let title_editor = { let can_edit = thread.update(cx, |thread, cx| thread.can_set_title(cx)); let editor = cx.new(|cx| { let mut editor = Editor::single_line(window, cx); editor.set_text(thread.read(cx).title(), window, cx); editor.set_read_only(!can_edit); editor }); subscriptions.push(cx.subscribe_in(&editor, window, Self::handle_title_editor_event)); editor }; subscriptions.push(cx.subscribe_in( &entry_view_state, window, Self::handle_entry_view_event, )); subscriptions.push(cx.subscribe_in( &message_editor, window, Self::handle_message_editor_event, )); let recent_history_entries = history.read(cx).get_recent_sessions(3); let mut this = Self { id, parent_id, focus_handle: cx.focus_handle(), thread, conversation, login, server_view, agent_icon, agent_name, workspace, entry_view_state, title_editor, config_options_view, mode_selector, model_selector, profile_selector, list_state, prompt_capabilities, available_commands, resumed_without_history, resume_thread_metadata, _subscriptions: subscriptions, permission_dropdown_handle: PopoverMenuHandle::default(), thread_retry_status: None, thread_error: None, thread_error_markdown: None, token_limit_callout_dismissed: false, last_token_limit_telemetry: None, thread_feedback: Default::default(), expanded_tool_calls: HashSet::default(), expanded_tool_call_raw_inputs: HashSet::default(), expanded_thinking_blocks: HashSet::default(), subagent_scroll_handles: RefCell::new(HashMap::default()), edits_expanded: false, plan_expanded: false, queue_expanded: true, editor_expanded: false, should_be_following: false, editing_message: None, local_queued_messages: Vec::new(), queued_message_editors: Vec::new(), queued_message_editor_subscriptions: Vec::new(), last_synced_queue_length: 0, turn_fields: TurnFields::default(), discarded_partial_edits: HashSet::default(), is_loading_contents: false, new_server_version_available: None, selected_permission_granularity: HashMap::default(), _cancel_task: None, skip_queue_processing_count: 0, user_interrupted_generation: false, can_fast_track_queue: false, hovered_edited_file_buttons: None, in_flight_prompt: None, message_editor, add_context_menu_handle: PopoverMenuHandle::default(), thinking_effort_menu_handle: PopoverMenuHandle::default(), project, recent_history_entries, hovered_recent_history_item: None, history, _history_subscription: history_subscription, show_codex_windows_warning, }; if should_auto_submit { this.send(window, cx); } this } pub fn handle_message_editor_event( &mut self, _editor: &Entity<MessageEditor>, event: &MessageEditorEvent, window: &mut Window, cx: &mut Context<Self>, ) { match event { MessageEditorEvent::Send => self.send(window, cx), MessageEditorEvent::SendImmediately => self.interrupt_and_send(window, cx), MessageEditorEvent::Cancel => self.cancel_generation(cx), MessageEditorEvent::Focus => { self.cancel_editing(&Default::default(), window, cx); } MessageEditorEvent::LostFocus => {} } } pub(crate) fn as_native_connection( &self, cx: &App, ) -> Option<Rc<agent::NativeAgentConnection>> { let acp_thread = self.thread.read(cx); acp_thread.connection().clone().downcast() } pub(crate) fn as_native_thread(&self, cx: &App) -> Option<Entity<agent::Thread>> { let acp_thread = self.thread.read(cx); self.as_native_connection(cx)? .thread(acp_thread.session_id(), cx) } pub fn current_model_id(&self, cx: &App) -> Option<String> { let selector = self.model_selector.as_ref()?; let model = selector.read(cx).active_model(cx)?; Some(model.id.to_string()) } pub fn current_mode_id(&self, cx: &App) -> Option<Arc<str>> { if let Some(thread) = self.as_native_thread(cx) { Some(thread.read(cx).profile().0.clone()) } else { let mode_selector = self.mode_selector.as_ref()?; Some(mode_selector.read(cx).mode().0) } }