7976 lines · 304.1 KB · Rust
RawDownload
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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t