4145 lines · 139.0 KB · Rust
RawDownload
mod connection;mod diff;mod mention;mod terminal; use agent_settings::AgentSettings;use collections::HashSet;pub use connection::*;pub use diff::*;use language::language_settings::FormatOnSave;pub use mention::*;use project::lsp_store::{FormatTrigger, LspFormatTarget};use serde::{Deserialize, Serialize};use serde_json::to_string_pretty;use settings::Settings as _;use task::{Shell, ShellBuilder};pub use terminal::*; use action_log::{ActionLog, ActionLogTelemetry};use agent_client_protocol::{self as acp};use anyhow::{Context as _, Result, anyhow};use editor::Bias;use futures::{FutureExt, channel::oneshot, future::BoxFuture};use gpui::{AppContext, AsyncApp, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};use itertools::Itertools;use language::{Anchor, Buffer, BufferSnapshot, LanguageRegistry, Point, ToPoint, text_diff};use markdown::Markdown;use project::{AgentLocation, Project, git_store::GitStoreCheckpoint};use std::collections::HashMap;use std::error::Error;use std::fmt::{Formatter, Write};use std::ops::Range;use std::process::ExitStatus;use std::rc::Rc;use std::time::{Duration, Instant};use std::{fmt::Display, mem, path::PathBuf, sync::Arc};use ui::App;use util::{ResultExt, get_default_system_shell_preferring_bash, paths::PathStyle};use uuid::Uuid; #[derive(Debug)]pub struct UserMessage {    pub id: Option<UserMessageId>,    pub content: ContentBlock,    pub chunks: Vec<acp::ContentBlock>,    pub checkpoint: Option<Checkpoint>,    pub indented: bool,} #[derive(Debug)]pub struct Checkpoint {    git_checkpoint: GitStoreCheckpoint,    pub show: bool,} impl UserMessage {    fn to_markdown(&self, cx: &App) -> String {        let mut markdown = String::new();        if self            .checkpoint            .as_ref()            .is_some_and(|checkpoint| checkpoint.show)        {            writeln!(markdown, "## User (checkpoint)").unwrap();        } else {            writeln!(markdown, "## User").unwrap();        }        writeln!(markdown).unwrap();        writeln!(markdown, "{}", self.content.to_markdown(cx)).unwrap();        writeln!(markdown).unwrap();        markdown    }} #[derive(Debug, PartialEq)]pub struct AssistantMessage {    pub chunks: Vec<AssistantMessageChunk>,    pub indented: bool,} impl AssistantMessage {    pub fn to_markdown(&self, cx: &App) -> String {        format!(            "## Assistant\n\n{}\n\n",            self.chunks                .iter()                .map(|chunk| chunk.to_markdown(cx))                .join("\n\n")        )    }} #[derive(Debug, PartialEq)]pub enum AssistantMessageChunk {    Message { block: ContentBlock },    Thought { block: ContentBlock },} impl AssistantMessageChunk {    pub fn from_str(        chunk: &str,        language_registry: &Arc<LanguageRegistry>,        path_style: PathStyle,        cx: &mut App,    ) -> Self {        Self::Message {            block: ContentBlock::new(chunk.into(), language_registry, path_style, cx),        }    }     fn to_markdown(&self, cx: &App) -> String {        match self {            Self::Message { block } => block.to_markdown(cx).to_string(),            Self::Thought { block } => {                format!("<thinking>\n{}\n</thinking>", block.to_markdown(cx))            }        }    }} #[derive(Debug)]pub enum AgentThreadEntry {    UserMessage(UserMessage),    AssistantMessage(AssistantMessage),    ToolCall(ToolCall),} impl AgentThreadEntry {    pub fn is_indented(&self) -> bool {        match self {            Self::UserMessage(message) => message.indented,            Self::AssistantMessage(message) => message.indented,            Self::ToolCall(_) => false,        }    }     pub fn to_markdown(&self, cx: &App) -> String {        match self {            Self::UserMessage(message) => message.to_markdown(cx),            Self::AssistantMessage(message) => message.to_markdown(cx),            Self::ToolCall(tool_call) => tool_call.to_markdown(cx),        }    }     pub fn user_message(&self) -> Option<&UserMessage> {        if let AgentThreadEntry::UserMessage(message) = self {            Some(message)        } else {            None        }    }     pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> {        if let AgentThreadEntry::ToolCall(call) = self {            itertools::Either::Left(call.diffs())        } else {            itertools::Either::Right(std::iter::empty())        }    }     pub fn terminals(&self) -> impl Iterator<Item = &Entity<Terminal>> {        if let AgentThreadEntry::ToolCall(call) = self {            itertools::Either::Left(call.terminals())        } else {            itertools::Either::Right(std::iter::empty())        }    }     pub fn location(&self, ix: usize) -> Option<(acp::ToolCallLocation, AgentLocation)> {        if let AgentThreadEntry::ToolCall(ToolCall {            locations,            resolved_locations,            ..        }) = self        {            Some((                locations.get(ix)?.clone(),                resolved_locations.get(ix)?.clone()?,            ))        } else {            None        }    }} #[derive(Debug)]pub struct ToolCall {    pub id: acp::ToolCallId,    pub label: Entity<Markdown>,    pub kind: acp::ToolKind,    pub content: Vec<ToolCallContent>,    pub status: ToolCallStatus,    pub locations: Vec<acp::ToolCallLocation>,    pub resolved_locations: Vec<Option<AgentLocation>>,    pub raw_input: Option<serde_json::Value>,    pub raw_input_markdown: Option<Entity<Markdown>>,    pub raw_output: Option<serde_json::Value>,} impl ToolCall {    fn from_acp(        tool_call: acp::ToolCall,        status: ToolCallStatus,        language_registry: Arc<LanguageRegistry>,        path_style: PathStyle,        terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,        cx: &mut App,    ) -> Result<Self> {        let title = if let Some((first_line, _)) = tool_call.title.split_once("\n") {            first_line.to_owned() + "…"        } else {            tool_call.title        };        let mut content = Vec::with_capacity(tool_call.content.len());        for item in tool_call.content {            if let Some(item) = ToolCallContent::from_acp(                item,                language_registry.clone(),                path_style,                terminals,                cx,            )? {                content.push(item);            }        }         let raw_input_markdown = tool_call            .raw_input            .as_ref()            .and_then(|input| markdown_for_raw_output(input, &language_registry, cx));         let result = Self {            id: tool_call.tool_call_id,            label: cx                .new(|cx| Markdown::new(title.into(), Some(language_registry.clone()), None, cx)),            kind: tool_call.kind,            content,            locations: tool_call.locations,            resolved_locations: Vec::default(),            status,            raw_input: tool_call.raw_input,            raw_input_markdown,            raw_output: tool_call.raw_output,        };        Ok(result)    }     fn update_fields(        &mut self,        fields: acp::ToolCallUpdateFields,        language_registry: Arc<LanguageRegistry>,        path_style: PathStyle,        terminals: &HashMap<acp::TerminalId, Entity<Terminal>>,        cx: &mut App,    ) -> Result<()> {        let acp::ToolCallUpdateFields {            kind,            status,            title,            content,            locations,            raw_input,            raw_output,            ..        } = fields;         if let Some(kind) = kind {            self.kind = kind;        }         if let Some(status) = status {            self.status = status.into();        }         if let Some(title) = title {            self.label.update(cx, |label, cx| {                if let Some((first_line, _)) = title.split_once("\n") {                    label.replace(first_line.to_owned() + "…", cx)                } else {                    label.replace(title, cx);                }            });        }         if let Some(content) = content {            let mut new_content_len = content.len();            let mut content = content.into_iter();             // Reuse existing content if we can            for (old, new) in self.content.iter_mut().zip(content.by_ref()) {                let valid_content =                    old.update_from_acp(new, language_registry.clone(), path_style, terminals, cx)?;                if !valid_content {                    new_content_len -= 1;                }            }            for new in content {                if let Some(new) = ToolCallContent::from_acp(                    new,                    language_registry.clone(),                    path_style,                    terminals,                    cx,                )? {                    self.content.push(new);                } else {                    new_content_len -= 1;                }            }            self.content.truncate(new_content_len);        }         if let Some(locations) = locations {            self.locations = locations;        }         if let Some(raw_input) = raw_input {            self.raw_input_markdown = markdown_for_raw_output(&raw_input, &language_registry, cx);            self.raw_input = Some(raw_input);        }         if let Some(raw_output) = raw_output {            if self.content.is_empty()                && let Some(markdown) = markdown_for_raw_output(&raw_output, &language_registry, cx)            {                self.content                    .push(ToolCallContent::ContentBlock(ContentBlock::Markdown {                        markdown,                    }));            }            self.raw_output = Some(raw_output);        }        Ok(())    }     pub fn diffs(&self) -> impl Iterator<Item = &Entity<Diff>> {        self.content.iter().filter_map(|content| match content {            ToolCallContent::Diff(diff) => Some(diff),            ToolCallContent::ContentBlock(_) => None,            ToolCallContent::Terminal(_) => None,        })    }     pub fn terminals(&self) -> impl Iterator<Item = &Entity<Terminal>> {        self.content.iter().filter_map(|content| match content {            ToolCallContent::Terminal(terminal) => Some(terminal),            ToolCallContent::ContentBlock(_) => None,            ToolCallContent::Diff(_) => None,        })    }     fn to_markdown(&self, cx: &App) -> String {        let mut markdown = format!(            "**Tool Call: {}**\nStatus: {}\n\n",            self.label.read(cx).source(),            self.status        );        for content in &self.content {            markdown.push_str(content.to_markdown(cx).as_str());            markdown.push_str("\n\n");        }        markdown    }     async fn resolve_location(        location: acp::ToolCallLocation,        project: WeakEntity<Project>,        cx: &mut AsyncApp,    ) -> Option<ResolvedLocation> {        let buffer = project            .update(cx, |project, cx| {                project                    .project_path_for_absolute_path(&location.path, cx)                    .map(|path| project.open_buffer(path, cx))            })            .ok()??;        let buffer = buffer.await.log_err()?;        let position = buffer            .update(cx, |buffer, _| {                let snapshot = buffer.snapshot();                if let Some(row) = location.line {                    let column = snapshot.indent_size_for_line(row).len;                    let point = snapshot.clip_point(Point::new(row, column), Bias::Left);                    snapshot.anchor_before(point)                } else {                    Anchor::min_for_buffer(snapshot.remote_id())                }            })            .ok()?;         Some(ResolvedLocation { buffer, position })    }     fn resolve_locations(        &self,        project: Entity<Project>,        cx: &mut App,    ) -> Task<Vec<Option<ResolvedLocation>>> {        let locations = self.locations.clone();        project.update(cx, |_, cx| {            cx.spawn(async move |project, cx| {                let mut new_locations = Vec::new();                for location in locations {                    new_locations.push(Self::resolve_location(location, project.clone(), cx).await);                }                new_locations            })        })    }} // Separate so we can hold a strong reference to the buffer// for saving on the thread#[derive(Clone, Debug, PartialEq, Eq)]struct ResolvedLocation {    buffer: Entity<Buffer>,    position: Anchor,} impl From<&ResolvedLocation> for AgentLocation {    fn from(value: &ResolvedLocation) -> Self {        Self {            buffer: value.buffer.downgrade(),            position: value.position,        }    }} #[derive(Debug)]pub enum ToolCallStatus {    /// The tool call hasn't started running yet, but we start showing it to    /// the user.    Pending,    /// The tool call is waiting for confirmation from the user.    WaitingForConfirmation {        options: Vec<acp::PermissionOption>,        respond_tx: oneshot::Sender<acp::PermissionOptionId>,    },    /// The tool call is currently running.    InProgress,    /// The tool call completed successfully.    Completed,    /// The tool call failed.    Failed,    /// The user rejected the tool call.    Rejected,    /// The user canceled generation so the tool call was canceled.    Canceled,} impl From<acp::ToolCallStatus> for ToolCallStatus {    fn from(status: acp::ToolCallStatus) -> Self {        match status {            acp::ToolCallStatus::Pending => Self::Pending,            acp::ToolCallStatus::InProgress => Self::InProgress,            acp::ToolCallStatus::Completed => Self::Completed,            acp::ToolCallStatus::Failed => Self::Failed,            _ => Self::Pending,        }    }} impl Display for ToolCallStatus {    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {        write!(            f,            "{}",            match self {                ToolCallStatus::Pending => "Pending",                ToolCallStatus::WaitingForConfirmation { .. } => "Waiting for confirmation",                ToolCallStatus::InProgress => "In Progress",                ToolCallStatus::Completed => "Completed",                ToolCallStatus::Failed => "Failed",                ToolCallStatus::Rejected => "Rejected",                ToolCallStatus::Canceled => "Canceled",            }        )    }} #[derive(Debug, PartialEq, Clone)]pub enum ContentBlock {    Empty,    Markdown { markdown: Entity<Markdown> },    ResourceLink { resource_link: acp::ResourceLink },} impl ContentBlock {    pub fn new(        block: acp::ContentBlock,        language_registry: &Arc<LanguageRegistry>,        path_style: PathStyle,        cx: &mut App,    ) -> Self {        let mut this = Self::Empty;        this.append(block, language_registry, path_style, cx);        this    }     pub fn new_combined(

Lines 1–500 of 4145.