1757 lines · 54.7 KB · Rust
RawDownload
mod connection;mod old_acp_support;pub use connection::*;pub use old_acp_support::*; use agent_client_protocol as acp;use anyhow::{Context as _, Result};use assistant_tool::ActionLog;use buffer_diff::BufferDiff;use editor::{Bias, MultiBuffer, PathKey};use futures::{FutureExt, channel::oneshot, future::BoxFuture};use gpui::{AppContext, Context, Entity, EventEmitter, SharedString, Task};use itertools::Itertools;use language::{    Anchor, Buffer, BufferSnapshot, Capability, LanguageRegistry, OffsetRangeExt as _, Point,    text_diff,};use markdown::Markdown;use project::{AgentLocation, Project};use std::collections::HashMap;use std::error::Error;use std::fmt::Formatter;use std::rc::Rc;use std::{    fmt::Display,    mem,    path::{Path, PathBuf},    sync::Arc,};use ui::App;use util::ResultExt; #[derive(Debug)]pub struct UserMessage {    pub content: ContentBlock,} impl UserMessage {    pub fn from_acp(        message: impl IntoIterator<Item = acp::ContentBlock>,        language_registry: Arc<LanguageRegistry>,        cx: &mut App,    ) -> Self {        let mut content = ContentBlock::Empty;        for chunk in message {            content.append(chunk, &language_registry, cx)        }        Self { content: content }    }     fn to_markdown(&self, cx: &App) -> String {        format!("## User\n\n{}\n\n", self.content.to_markdown(cx))    }} #[derive(Debug)]pub struct MentionPath<'a>(&'a Path); impl<'a> MentionPath<'a> {    const PREFIX: &'static str = "@file:";     pub fn new(path: &'a Path) -> Self {        MentionPath(path)    }     pub fn try_parse(url: &'a str) -> Option<Self> {        let path = url.strip_prefix(Self::PREFIX)?;        Some(MentionPath(Path::new(path)))    }     pub fn path(&self) -> &Path {        self.0    }} impl Display for MentionPath<'_> {    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {        write!(            f,            "[@{}]({}{})",            self.0.file_name().unwrap_or_default().display(),            Self::PREFIX,            self.0.display()        )    }} #[derive(Debug, PartialEq)]pub struct AssistantMessage {    pub chunks: Vec<AssistantMessageChunk>,} 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>, cx: &mut App) -> Self {        Self::Message {            block: ContentBlock::new(chunk.into(), language_registry, 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 {    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 diffs(&self) -> impl Iterator<Item = &Diff> {        if let AgentThreadEntry::ToolCall(call) = self {            itertools::Either::Left(call.diffs())        } else {            itertools::Either::Right(std::iter::empty())        }    }     pub fn locations(&self) -> Option<&[acp::ToolCallLocation]> {        if let AgentThreadEntry::ToolCall(ToolCall { locations, .. }) = self {            Some(locations)        } 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 raw_input: Option<serde_json::Value>,} impl ToolCall {    fn from_acp(        tool_call: acp::ToolCall,        status: ToolCallStatus,        language_registry: Arc<LanguageRegistry>,        cx: &mut App,    ) -> Self {        Self {            id: tool_call.id,            label: cx.new(|cx| {                Markdown::new(                    tool_call.label.into(),                    Some(language_registry.clone()),                    None,                    cx,                )            }),            kind: tool_call.kind,            content: tool_call                .content                .into_iter()                .map(|content| ToolCallContent::from_acp(content, language_registry.clone(), cx))                .collect(),            locations: tool_call.locations,            status,            raw_input: tool_call.raw_input,        }    }     fn update(        &mut self,        fields: acp::ToolCallUpdateFields,        language_registry: Arc<LanguageRegistry>,        cx: &mut App,    ) {        let acp::ToolCallUpdateFields {            kind,            status,            label,            content,            locations,            raw_input,        } = fields;         if let Some(kind) = kind {            self.kind = kind;        }         if let Some(status) = status {            self.status = ToolCallStatus::Allowed { status };        }         if let Some(label) = label {            self.label = cx.new(|cx| Markdown::new_text(label.into(), cx));        }         if let Some(content) = content {            self.content = content                .into_iter()                .map(|chunk| ToolCallContent::from_acp(chunk, language_registry.clone(), cx))                .collect();        }         if let Some(locations) = locations {            self.locations = locations;        }         if let Some(raw_input) = raw_input {            self.raw_input = Some(raw_input);        }    }     pub fn diffs(&self) -> impl Iterator<Item = &Diff> {        self.content.iter().filter_map(|content| match content {            ToolCallContent::ContentBlock { .. } => None,            ToolCallContent::Diff { diff } => Some(diff),        })    }     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    }} #[derive(Debug)]pub enum ToolCallStatus {    WaitingForConfirmation {        options: Vec<acp::PermissionOption>,        respond_tx: oneshot::Sender<acp::PermissionOptionId>,    },    Allowed {        status: acp::ToolCallStatus,    },    Rejected,    Canceled,} impl Display for ToolCallStatus {    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {        write!(            f,            "{}",            match self {                ToolCallStatus::WaitingForConfirmation { .. } => "Waiting for confirmation",                ToolCallStatus::Allowed { status } => match status {                    acp::ToolCallStatus::Pending => "Pending",                    acp::ToolCallStatus::InProgress => "In Progress",                    acp::ToolCallStatus::Completed => "Completed",                    acp::ToolCallStatus::Failed => "Failed",                },                ToolCallStatus::Rejected => "Rejected",                ToolCallStatus::Canceled => "Canceled",            }        )    }} #[derive(Debug, PartialEq, Clone)]pub enum ContentBlock {    Empty,    Markdown { markdown: Entity<Markdown> },} impl ContentBlock {    pub fn new(        block: acp::ContentBlock,        language_registry: &Arc<LanguageRegistry>,        cx: &mut App,    ) -> Self {        let mut this = Self::Empty;        this.append(block, language_registry, cx);        this    }     pub fn new_combined(        blocks: impl IntoIterator<Item = acp::ContentBlock>,        language_registry: Arc<LanguageRegistry>,        cx: &mut App,    ) -> Self {        let mut this = Self::Empty;        for block in blocks {            this.append(block, &language_registry, cx);        }        this    }     pub fn append(        &mut self,        block: acp::ContentBlock,        language_registry: &Arc<LanguageRegistry>,        cx: &mut App,    ) {        let new_content = match block {            acp::ContentBlock::Text(text_content) => text_content.text.clone(),            acp::ContentBlock::ResourceLink(resource_link) => {                if let Some(path) = resource_link.uri.strip_prefix("file://") {                    format!("{}", MentionPath(path.as_ref()))                } else {                    resource_link.uri.clone()                }            }            acp::ContentBlock::Image(_)            | acp::ContentBlock::Audio(_)            | acp::ContentBlock::Resource(_) => String::new(),        };         match self {            ContentBlock::Empty => {                *self = ContentBlock::Markdown {                    markdown: cx.new(|cx| {                        Markdown::new(                            new_content.into(),                            Some(language_registry.clone()),                            None,                            cx,                        )                    }),                };            }            ContentBlock::Markdown { markdown } => {                markdown.update(cx, |markdown, cx| markdown.append(&new_content, cx));            }        }    }     fn to_markdown<'a>(&'a self, cx: &'a App) -> &'a str {        match self {            ContentBlock::Empty => "",            ContentBlock::Markdown { markdown } => markdown.read(cx).source(),        }    }     pub fn markdown(&self) -> Option<&Entity<Markdown>> {        match self {            ContentBlock::Empty => None,            ContentBlock::Markdown { markdown } => Some(markdown),        }    }} #[derive(Debug)]pub enum ToolCallContent {    ContentBlock { content: ContentBlock },    Diff { diff: Diff },} impl ToolCallContent {    pub fn from_acp(        content: acp::ToolCallContent,        language_registry: Arc<LanguageRegistry>,        cx: &mut App,    ) -> Self {        match content {            acp::ToolCallContent::ContentBlock(content) => Self::ContentBlock {                content: ContentBlock::new(content, &language_registry, cx),            },            acp::ToolCallContent::Diff { diff } => Self::Diff {                diff: Diff::from_acp(diff, language_registry, cx),            },        }    }     pub fn to_markdown(&self, cx: &App) -> String {        match self {            Self::ContentBlock { content } => content.to_markdown(cx).to_string(),            Self::Diff { diff } => diff.to_markdown(cx),        }    }} #[derive(Debug)]pub struct Diff {    pub multibuffer: Entity<MultiBuffer>,    pub path: PathBuf,    pub new_buffer: Entity<Buffer>,    pub old_buffer: Entity<Buffer>,    _task: Task<Result<()>>,} impl Diff {    pub fn from_acp(        diff: acp::Diff,        language_registry: Arc<LanguageRegistry>,        cx: &mut App,    ) -> Self {        let acp::Diff {            path,            old_text,            new_text,        } = diff;         let multibuffer = cx.new(|_cx| MultiBuffer::without_headers(Capability::ReadOnly));         let new_buffer = cx.new(|cx| Buffer::local(new_text, cx));        let old_buffer = cx.new(|cx| Buffer::local(old_text.unwrap_or("".into()), cx));        let new_buffer_snapshot = new_buffer.read(cx).text_snapshot();        let old_buffer_snapshot = old_buffer.read(cx).snapshot();        let buffer_diff = cx.new(|cx| BufferDiff::new(&new_buffer_snapshot, cx));        let diff_task = buffer_diff.update(cx, |diff, cx| {            diff.set_base_text(                old_buffer_snapshot,                Some(language_registry.clone()),                new_buffer_snapshot,                cx,            )        });         let task = cx.spawn({            let multibuffer = multibuffer.clone();            let path = path.clone();            let new_buffer = new_buffer.clone();            async move |cx| {                diff_task.await?;                 multibuffer                    .update(cx, |multibuffer, cx| {                        let hunk_ranges = {                            let buffer = new_buffer.read(cx);                            let diff = buffer_diff.read(cx);                            diff.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &buffer, cx)                                .map(|diff_hunk| diff_hunk.buffer_range.to_point(&buffer))                                .collect::<Vec<_>>()                        };                         multibuffer.set_excerpts_for_path(                            PathKey::for_buffer(&new_buffer, cx),                            new_buffer.clone(),                            hunk_ranges,                            editor::DEFAULT_MULTIBUFFER_CONTEXT,                            cx,                        );                        multibuffer.add_diff(buffer_diff.clone(), cx);                    })                    .log_err();                 if let Some(language) = language_registry                    .language_for_file_path(&path)                    .await                    .log_err()                {                    new_buffer.update(cx, |buffer, cx| buffer.set_language(Some(language), cx))?;                }                 anyhow::Ok(())            }        });         Self {            multibuffer,            path,            new_buffer,            old_buffer,            _task: task,        }    }     fn to_markdown(&self, cx: &App) -> String {        let buffer_text = self            .multibuffer            .read(cx)

Lines 1–500 of 1757.