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. Load more
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)