From c4d920ff5beca1b21dcb82ef5ae6a0fd58c89d0b Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Tue, 22 Jul 2025 19:01:29 -0600 Subject: [PATCH] WIPWIPWIPW --- Cargo.lock | 10 + Cargo.toml | 1 + crates/acp_thread/Cargo.toml | 1 + crates/acp_thread/src/acp_thread.rs | 613 ++++++++++-------- crates/agent_servers/src/claude.rs | 8 +- crates/agent_servers/src/codex.rs | 6 +- crates/agent_servers/src/mcp_server.rs | 12 +- .../agent_servers/src/stdio_agent_server.rs | 4 +- crates/agent_ui/src/acp/thread_view.rs | 11 +- 9 files changed, 372 insertions(+), 294 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 772fb2492b6cc55cd8c4efb6ecf6c53217f96a2a..80b0dcf037f0b10beb67c4efe06c1f6b57efee57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,7 @@ version = 4 name = "acp_thread" version = "0.1.0" dependencies = [ + "agent-client-protocol", "agentic-coding-protocol", "anyhow", "assistant_tool", @@ -135,6 +136,15 @@ dependencies = [ "zstd", ] +[[package]] +name = "agent-client-protocol" +version = "0.0.10" +dependencies = [ + "schemars", + "serde", + "serde_json", +] + [[package]] name = "agent_servers" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index ea8690f2b3af444900d8760c7a678124702c7f93..bdb2d78bd66f3e515f06f4c4e327a6cac455ae8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -413,6 +413,7 @@ zlog_settings = { path = "crates/zlog_settings" } # agentic-coding-protocol = "0.0.10" +agent-client-protocol = { path = "../agent-client-protocol" } aho-corasick = "1.1" alacritty_terminal = { git = "https://github.com/zed-industries/alacritty.git", branch = "add-hush-login-flag" } any_vec = "0.14" diff --git a/crates/acp_thread/Cargo.toml b/crates/acp_thread/Cargo.toml index b44c25ccc998f5924277e6ca6ef393ca15e8e345..011f26f364ad2c2a7cdb97331f9ec90f8f06ed82 100644 --- a/crates/acp_thread/Cargo.toml +++ b/crates/acp_thread/Cargo.toml @@ -16,6 +16,7 @@ doctest = false test-support = ["gpui/test-support", "project/test-support"] [dependencies] +agent-client-protocol.workspace = true agentic-coding-protocol.workspace = true anyhow.workspace = true assistant_tool.workspace = true diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 9af1eeb1872fb9c44e3159a3d1772b68c98e67d7..eb0887cfcec1648a6817991e5aa9370bdc03eec0 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -1,11 +1,9 @@ mod connection; pub use connection::*; -pub use acp::ToolCallId; -use agentic_coding_protocol::{ - self as acp, AgentRequest, ProtocolVersion, ToolCallConfirmationOutcome, ToolCallLocation, - UserMessageChunk, -}; +pub use acp_old::ToolCallId; +use agent_client_protocol::{self as acp}; +use agentic_coding_protocol as acp_old; use anyhow::{Context as _, Result}; use assistant_tool::ActionLog; use buffer_diff::BufferDiff; @@ -31,36 +29,26 @@ use std::{ use ui::{App, IconName}; use util::ResultExt; -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Debug)] pub struct UserMessage { - pub content: Entity, + pub content: ContentBlock, } impl UserMessage { pub fn from_acp( - message: &acp::SendUserMessageParams, + message: &[acp::ContentBlock], language_registry: Arc, cx: &mut App, ) -> Self { - let mut md_source = String::new(); - - for chunk in &message.chunks { - match chunk { - UserMessageChunk::Text { text } => md_source.push_str(&text), - UserMessageChunk::Path { path } => { - write!(&mut md_source, "{}", MentionPath(&path)).unwrap() - } - } - } - - Self { - content: cx - .new(|cx| Markdown::new(md_source.into(), Some(language_registry), None, cx)), + 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.read(cx).source()) + format!("## User\n{}\n", self.content.to_markdown(cx)) } } @@ -96,7 +84,7 @@ impl Display for MentionPath<'_> { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Debug, PartialEq)] pub struct AssistantMessage { pub chunks: Vec, } @@ -113,41 +101,30 @@ impl AssistantMessage { } } -#[derive(Clone, Debug, Eq, PartialEq)] +#[derive(Debug, PartialEq)] pub enum AssistantMessageChunk { - Text { chunk: Entity }, - Thought { chunk: Entity }, + Message { block: ContentBlock }, + Thought { block: ContentBlock }, } impl AssistantMessageChunk { - pub fn from_acp( - chunk: acp::AssistantMessageChunk, - language_registry: Arc, - cx: &mut App, - ) -> Self { - match chunk { - acp::AssistantMessageChunk::Text { text } => Self::Text { - chunk: cx.new(|cx| Markdown::new(text.into(), Some(language_registry), None, cx)), - }, - acp::AssistantMessageChunk::Thought { thought } => Self::Thought { - chunk: cx - .new(|cx| Markdown::new(thought.into(), Some(language_registry), None, cx)), - }, - } - } - - pub fn from_str(chunk: &str, language_registry: Arc, cx: &mut App) -> Self { - Self::Text { - chunk: cx.new(|cx| { - Markdown::new(chunk.to_owned().into(), Some(language_registry), None, cx) - }), + pub fn from_str(chunk: &str, language_registry: &Arc, cx: &mut App) -> Self { + Self::Message { + block: ContentBlock::new( + &acp::ContentBlock::TextContent(acp::TextContent { + text: chunk.to_owned().into(), + ..Default::default() + }), + language_registry, + cx, + ), } } fn to_markdown(&self, cx: &App) -> String { match self { - Self::Text { chunk } => chunk.read(cx).source().to_string(), - Self::Thought { chunk } => { + Self::Message { block: chunk } => chunk.read(cx).source().to_string(), + Self::Thought { block: chunk } => { format!("\n{}\n", chunk.read(cx).source()) } } @@ -182,7 +159,7 @@ impl AgentThreadEntry { } } - pub fn locations(&self) -> Option<&[acp::ToolCallLocation]> { + pub fn locations(&self) -> Option<&[acp_old::ToolCallLocation]> { if let AgentThreadEntry::ToolCall(ToolCall { locations, .. }) = self { Some(locations) } else { @@ -195,8 +172,8 @@ impl AgentThreadEntry { pub struct ToolCall { pub id: acp::ToolCallId, pub label: Entity, - pub icon: IconName, - pub content: Option, + pub kind: acp::ToolKind, + pub content: Vec, pub status: ToolCallStatus, pub locations: Vec, } @@ -219,8 +196,8 @@ impl ToolCall { #[derive(Debug)] pub enum ToolCallStatus { WaitingForConfirmation { - confirmation: ToolCallConfirmation, - respond_tx: oneshot::Sender, + possible_grants: Vec, + respond_tx: oneshot::Sender>, }, Allowed { status: acp::ToolCallStatus, @@ -237,9 +214,9 @@ impl Display for ToolCallStatus { match self { ToolCallStatus::WaitingForConfirmation { .. } => "Waiting for confirmation", ToolCallStatus::Allowed { status } => match status { - acp::ToolCallStatus::Running => "Running", - acp::ToolCallStatus::Finished => "Finished", - acp::ToolCallStatus::Error => "Error", + acp_old::ToolCallStatus::Running => "Running", + acp_old::ToolCallStatus::Finished => "Finished", + acp_old::ToolCallStatus::Error => "Error", }, ToolCallStatus::Rejected => "Rejected", ToolCallStatus::Canceled => "Canceled", @@ -275,7 +252,7 @@ pub enum ToolCallConfirmation { impl ToolCallConfirmation { pub fn from_acp( - confirmation: acp::ToolCallConfirmation, + confirmation: acp_old::ToolCallConfirmation, language_registry: Arc, cx: &mut App, ) -> Self { @@ -291,10 +268,10 @@ impl ToolCallConfirmation { }; match confirmation { - acp::ToolCallConfirmation::Edit { description } => Self::Edit { + acp_old::ToolCallConfirmation::Edit { description } => Self::Edit { description: description.map(|description| to_md(description, cx)), }, - acp::ToolCallConfirmation::Execute { + acp_old::ToolCallConfirmation::Execute { command, root_command, description, @@ -303,7 +280,7 @@ impl ToolCallConfirmation { root_command, description: description.map(|description| to_md(description, cx)), }, - acp::ToolCallConfirmation::Mcp { + acp_old::ToolCallConfirmation::Mcp { server_name, tool_name, tool_display_name, @@ -314,20 +291,84 @@ impl ToolCallConfirmation { tool_display_name, description: description.map(|description| to_md(description, cx)), }, - acp::ToolCallConfirmation::Fetch { urls, description } => Self::Fetch { + acp_old::ToolCallConfirmation::Fetch { urls, description } => Self::Fetch { urls: urls.iter().map(|url| url.into()).collect(), description: description.map(|description| to_md(description, cx)), }, - acp::ToolCallConfirmation::Other { description } => Self::Other { + acp_old::ToolCallConfirmation::Other { description } => Self::Other { description: to_md(description, cx), }, } } } +#[derive(Debug, PartialEq, Clone)] +enum ContentBlock { + Empty, + Markdown { markdown: Entity }, +} + +impl ContentBlock { + pub fn new( + block: &acp::ContentBlock, + language_registry: &Arc, + cx: &mut App, + ) -> Self { + let mut this = Self::Empty; + this.append(block, language_registry, cx); + this + } + + pub fn append( + &mut self, + block: &acp::ContentBlock, + language_registry: &Arc, + cx: &mut App, + ) { + let new_content = match block { + acp::ContentBlock::TextContent(text_content) => text_content.text, + acp::ContentBlock::ResourceLink(resource_link) => { + if let Some(path) = resource_link.uri.strip_prefix("file://") { + format!("{}", MentionPath(path.as_ref())) + } else { + resource_link.uri + } + } + acp::ContentBlock::ImageContent(_) + | acp::ContentBlock::AudioContent(_) + | acp::ContentBlock::EmbeddedResource(_) => 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(&self, cx: &App) -> &str { + match self { + ContentBlock::Empty => "", + ContentBlock::Markdown { markdown } => markdown.read(cx).source(), + } + } +} + #[derive(Debug)] pub enum ToolCallContent { - Markdown { markdown: Entity }, + ContentBlock { content: ContentBlock }, Diff { diff: Diff }, } @@ -338,10 +379,10 @@ impl ToolCallContent { cx: &mut App, ) -> Self { match content { - acp::ToolCallContent::Markdown { markdown } => Self::Markdown { + acp_old::ToolCallContent::Markdown { markdown } => Self::Markdown { markdown: cx.new(|cx| Markdown::new_text(markdown.into(), cx)), }, - acp::ToolCallContent::Diff { diff } => Self::Diff { + acp_old::ToolCallContent::Diff { diff } => Self::Diff { diff: Diff::from_acp(diff, language_registry, cx), }, } @@ -366,11 +407,11 @@ pub struct Diff { impl Diff { pub fn from_acp( - diff: acp::Diff, + diff: acp_old::Diff, language_registry: Arc, cx: &mut App, ) -> Self { - let acp::Diff { + let acp_old::Diff { path, old_text, new_text, @@ -479,13 +520,13 @@ impl Plan { for entry in &self.entries { match &entry.status { - acp::PlanEntryStatus::Pending => { + acp_old::PlanEntryStatus::Pending => { stats.pending += 1; } - acp::PlanEntryStatus::InProgress => { + acp_old::PlanEntryStatus::InProgress => { stats.in_progress_entry = stats.in_progress_entry.or(Some(entry)); } - acp::PlanEntryStatus::Completed => { + acp_old::PlanEntryStatus::Completed => { stats.completed += 1; } } @@ -498,12 +539,12 @@ impl Plan { #[derive(Debug)] pub struct PlanEntry { pub content: Entity, - pub priority: acp::PlanEntryPriority, - pub status: acp::PlanEntryStatus, + pub priority: acp_old::PlanEntryPriority, + pub status: acp_old::PlanEntryStatus, } impl PlanEntry { - pub fn from_acp(entry: acp::PlanEntry, cx: &mut App) -> Self { + pub fn from_acp(entry: acp_old::PlanEntry, cx: &mut App) -> Self { Self { content: cx.new(|cx| Markdown::new_text(entry.content.into(), cx)), priority: entry.priority, @@ -585,7 +626,7 @@ impl AcpThread { } /// Send a request to the agent and wait for a response. - pub fn request( + pub fn request( &self, params: R, ) -> impl use + Future> { @@ -632,7 +673,7 @@ impl AcpThread { AgentThreadEntry::ToolCall(ToolCall { status: ToolCallStatus::Allowed { - status: acp::ToolCallStatus::Running, + status: acp_old::ToolCallStatus::Running, .. }, content: Some(ToolCallContent::Diff { .. }), @@ -652,42 +693,37 @@ impl AcpThread { pub fn push_assistant_chunk( &mut self, - chunk: acp::AssistantMessageChunk, + chunk: acp::ContentBlock, + is_thought: bool, cx: &mut Context, ) { + let language_registry = self.project.read(cx).languages().clone(); let entries_len = self.entries.len(); if let Some(last_entry) = self.entries.last_mut() && let AgentThreadEntry::AssistantMessage(AssistantMessage { chunks }) = last_entry { cx.emit(AcpThreadEvent::EntryUpdated(entries_len - 1)); - - match (chunks.last_mut(), &chunk) { - ( - Some(AssistantMessageChunk::Text { chunk: old_chunk }), - acp::AssistantMessageChunk::Text { text: new_chunk }, - ) - | ( - Some(AssistantMessageChunk::Thought { chunk: old_chunk }), - acp::AssistantMessageChunk::Thought { thought: new_chunk }, - ) => { - old_chunk.update(cx, |old_chunk, cx| { - old_chunk.append(&new_chunk, cx); - }); + match (chunks.last_mut(), is_thought) { + (Some(AssistantMessageChunk::Message { block }), false) + | (Some(AssistantMessageChunk::Thought { block }), true) => { + block.append(&chunk, &language_registry, cx) } _ => { - chunks.push(AssistantMessageChunk::from_acp( - chunk, - self.project.read(cx).languages().clone(), - cx, - )); + let block = ContentBlock::new(&chunk, &language_registry, cx); + if is_thought { + chunks.push(AssistantMessageChunk::Thought { block }) + } else { + chunks.push(AssistantMessageChunk::Message { block }) + } } } } else { - let chunk = AssistantMessageChunk::from_acp( - chunk, - self.project.read(cx).languages().clone(), - cx, - ); + let block = ContentBlock::new(&chunk, &language_registry, cx); + let chunk = if is_thought { + AssistantMessageChunk::Thought { block } + } else { + AssistantMessageChunk::Message { block } + }; self.push_entry( AgentThreadEntry::AssistantMessage(AssistantMessage { @@ -698,30 +734,131 @@ impl AcpThread { } } - pub fn request_new_tool_call( + pub fn update_tool_call( &mut self, - tool_call: acp::RequestToolCallConfirmationParams, + tool_call: acp::ToolCall, + cx: &mut Context, + ) -> Result<()> { + let language_registry = self.project.read(cx).languages().clone(); + + let new_tool_call = ToolCall { + 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, cx)) + .collect(), + locations: tool_call.locations, + status: ToolCallStatus::Allowed { + status: tool_call.status, + }, + }; + + if let Some((ix, current_call)) = self.tool_call_mut(tool_call.id) { + match &mut current_call.status { + ToolCallStatus::Allowed { status } => { + *status = tool_call.status; + } + ToolCallStatus::WaitingForConfirmation { .. } => { + anyhow::bail!("Tool call hasn't been authorized yet") + } + ToolCallStatus::Rejected => { + anyhow::bail!("Tool call was rejected and therefore can't be updated") + } + ToolCallStatus::Canceled => { + current_call.status = ToolCallStatus::Allowed { status: new_status }; + } + } + + *current_call = new_tool_call; + + let location = current_call.locations.last().cloned(); + if let Some(location) = location { + self.set_project_location(location, cx) + } + + cx.emit(AcpThreadEvent::EntryUpdated(ix)); + } else { + let language_registry = self.project.read(cx).languages().clone(); + let call = ToolCall { + 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, cx)) + .collect(), + locations: tool_call.locations, + status: ToolCallStatus::Allowed { + status: tool_call.status, + }, + }; + + let location = call.locations.last().cloned(); + if let Some(location) = location { + self.set_project_location(location, cx) + } + + self.push_entry(AgentThreadEntry::ToolCall(call), cx); + } + + Ok(()) + } + + fn tool_call_mut(&mut self, id: acp::ToolCallId) -> Option<(usize, &mut ToolCall)> { + self.entries + .iter_mut() + .enumerate() + .rev() + .find_map(|(index, tool_call)| { + if let AgentThreadEntry::ToolCall(tool_call) = tool_call + && tool_call.id == id + { + Some((index, tool_call)) + } else { + None + } + }) + } + + pub fn request_tool_call_permission( + &mut self, + tool_call: acp::ToolCall, + possible_grants: Vec, cx: &mut Context, ) -> ToolCallRequest { let (tx, rx) = oneshot::channel(); let status = ToolCallStatus::WaitingForConfirmation { - confirmation: ToolCallConfirmation::from_acp( - tool_call.confirmation, - self.project.read(cx).languages().clone(), - cx, - ), + possible_grants, respond_tx: tx, }; - let id = self.insert_tool_call(tool_call.tool_call, status, cx); + let id = self.insert_tool_call(tool_call, status, cx); ToolCallRequest { id, outcome: rx } } pub fn request_tool_call_confirmation( &mut self, tool_call_id: ToolCallId, - confirmation: acp::ToolCallConfirmation, + confirmation: acp_old::ToolCallConfirmation, cx: &mut Context, ) -> Result { let project = self.project.read(cx).languages().clone(); @@ -744,26 +881,14 @@ impl AcpThread { }) } - pub fn push_tool_call( - &mut self, - request: acp::PushToolCallParams, - cx: &mut Context, - ) -> acp::ToolCallId { - let status = ToolCallStatus::Allowed { - status: acp::ToolCallStatus::Running, - }; - - self.insert_tool_call(request, status, cx) - } - fn insert_tool_call( &mut self, - tool_call: acp::PushToolCallParams, + tool_call: acp_old::PushToolCallParams, status: ToolCallStatus, cx: &mut Context, - ) -> acp::ToolCallId { + ) -> acp_old::ToolCallId { let language_registry = self.project.read(cx).languages().clone(); - let id = acp::ToolCallId(self.entries.len() as u64); + let id = acp_old::ToolCallId(self.entries.len() as u64); let call = ToolCall { id, label: cx.new(|cx| { @@ -794,19 +919,19 @@ impl AcpThread { pub fn authorize_tool_call( &mut self, - id: acp::ToolCallId, - outcome: acp::ToolCallConfirmationOutcome, + id: acp_old::ToolCallId, + outcome: acp_old::ToolCallConfirmationOutcome, cx: &mut Context, ) { let Some((ix, call)) = self.tool_call_mut(id) else { return; }; - let new_status = if outcome == acp::ToolCallConfirmationOutcome::Reject { + let new_status = if outcome == acp_old::ToolCallConfirmationOutcome::Reject { ToolCallStatus::Rejected } else { ToolCallStatus::Allowed { - status: acp::ToolCallStatus::Running, + status: acp_old::ToolCallStatus::Running, } }; @@ -821,70 +946,11 @@ impl AcpThread { cx.emit(AcpThreadEvent::EntryUpdated(ix)); } - pub fn update_tool_call( - &mut self, - id: acp::ToolCallId, - new_status: acp::ToolCallStatus, - new_content: Option, - cx: &mut Context, - ) -> Result<()> { - let language_registry = self.project.read(cx).languages().clone(); - let (ix, call) = self.tool_call_mut(id).context("Entry not found")?; - - if let Some(new_content) = new_content { - call.content = Some(ToolCallContent::from_acp( - new_content, - language_registry, - cx, - )); - } - - match &mut call.status { - ToolCallStatus::Allowed { status } => { - *status = new_status; - } - ToolCallStatus::WaitingForConfirmation { .. } => { - anyhow::bail!("Tool call hasn't been authorized yet") - } - ToolCallStatus::Rejected => { - anyhow::bail!("Tool call was rejected and therefore can't be updated") - } - ToolCallStatus::Canceled => { - call.status = ToolCallStatus::Allowed { status: new_status }; - } - } - - let location = call.locations.last().cloned(); - if let Some(location) = location { - self.set_project_location(location, cx) - } - - cx.emit(AcpThreadEvent::EntryUpdated(ix)); - Ok(()) - } - - fn tool_call_mut(&mut self, id: acp::ToolCallId) -> Option<(usize, &mut ToolCall)> { - let entry = self.entries.get_mut(id.0 as usize); - debug_assert!( - entry.is_some(), - "We shouldn't give out ids to entries that don't exist" - ); - match entry { - Some(AgentThreadEntry::ToolCall(call)) if call.id == id => Some((id.0 as usize, call)), - _ => { - if cfg!(debug_assertions) { - panic!("entry is not a tool call"); - } - None - } - } - } - pub fn plan(&self) -> &Plan { &self.plan } - pub fn update_plan(&mut self, request: acp::UpdatePlanParams, cx: &mut Context) { + pub fn update_plan(&mut self, request: acp_old::UpdatePlanParams, cx: &mut Context) { self.plan = Plan { entries: request .entries @@ -899,11 +965,11 @@ impl AcpThread { pub fn clear_completed_plan_entries(&mut self, cx: &mut Context) { self.plan .entries - .retain(|entry| !matches!(entry.status, acp::PlanEntryStatus::Completed)); + .retain(|entry| !matches!(entry.status, acp_old::PlanEntryStatus::Completed)); cx.notify(); } - pub fn set_project_location(&self, location: ToolCallLocation, cx: &mut Context) { + pub fn set_project_location(&self, location: acp::ToolCallLocation, cx: &mut Context) { self.project.update(cx, |project, cx| { let Some(path) = project.project_path_for_absolute_path(&location.path, cx) else { return; @@ -953,14 +1019,14 @@ impl AcpThread { false } - pub fn initialize(&self) -> impl use<> + Future> { - self.request(acp::InitializeParams { - protocol_version: ProtocolVersion::latest(), + pub fn initialize(&self) -> impl use<> + Future> { + self.request(acp_old::InitializeParams { + protocol_version: acp_old::ProtocolVersion::latest(), }) } pub fn authenticate(&self) -> impl use<> + Future> { - self.request(acp::AuthenticateParams) + self.request(acp_old::AuthenticateParams) } #[cfg(any(test, feature = "test-support"))] @@ -968,10 +1034,10 @@ impl AcpThread { &mut self, message: &str, cx: &mut Context, - ) -> BoxFuture<'static, Result<(), acp::Error>> { + ) -> BoxFuture<'static, Result<(), acp_old::Error>> { self.send( - acp::SendUserMessageParams { - chunks: vec![acp::UserMessageChunk::Text { + acp_old::SendUserMessageParams { + chunks: vec![acp_old::UserMessageChunk::Text { text: message.to_string(), }], }, @@ -981,9 +1047,9 @@ impl AcpThread { pub fn send( &mut self, - message: acp::SendUserMessageParams, + message: acp_old::SendUserMessageParams, cx: &mut Context, - ) -> BoxFuture<'static, Result<(), acp::Error>> { + ) -> BoxFuture<'static, Result<(), acp_old::Error>> { self.push_entry( AgentThreadEntry::UserMessage(UserMessage::from_acp( &message, @@ -1018,9 +1084,9 @@ impl AcpThread { .boxed() } - pub fn cancel(&mut self, cx: &mut Context) -> Task> { + pub fn cancel(&mut self, cx: &mut Context) -> Task> { if self.send_task.take().is_some() { - let request = self.request(acp::CancelSendMessageParams); + let request = self.request(acp_old::CancelSendMessageParams); cx.spawn(async move |this, cx| { request.await?; this.update(cx, |this, _cx| { @@ -1030,7 +1096,7 @@ impl AcpThread { call.status, ToolCallStatus::WaitingForConfirmation { .. } | ToolCallStatus::Allowed { - status: acp::ToolCallStatus::Running + status: acp_old::ToolCallStatus::Running } ); @@ -1043,7 +1109,7 @@ impl AcpThread { } = curr_status { respond_tx - .send(acp::ToolCallConfirmationOutcome::Cancel) + .send(acp_old::ToolCallConfirmationOutcome::Cancel) .ok(); } } @@ -1059,7 +1125,7 @@ impl AcpThread { pub fn read_text_file( &self, - request: acp::ReadTextFileParams, + request: acp_old::ReadTextFileParams, reuse_shared_snapshot: bool, cx: &mut Context, ) -> Task> { @@ -1209,13 +1275,13 @@ impl AcpThread { } #[derive(Clone)] -pub struct AcpClientDelegate { +pub struct OldAcpClientDelegate { thread: WeakEntity, cx: AsyncApp, // sent_buffer_versions: HashMap, HashMap>, } -impl AcpClientDelegate { +impl OldAcpClientDelegate { pub fn new(thread: WeakEntity, cx: AsyncApp) -> Self { Self { thread, cx } } @@ -1234,8 +1300,8 @@ impl AcpClientDelegate { pub async fn request_existing_tool_call_confirmation( &self, tool_call_id: ToolCallId, - confirmation: acp::ToolCallConfirmation, - ) -> Result { + confirmation: acp_old::ToolCallConfirmation, + ) -> Result { let cx = &mut self.cx.clone(); let ToolCallRequest { outcome, .. } = cx .update(|cx| { @@ -1250,8 +1316,8 @@ impl AcpClientDelegate { pub async fn read_text_file_reusing_snapshot( &self, - request: acp::ReadTextFileParams, - ) -> Result { + request: acp_old::ReadTextFileParams, + ) -> Result { let content = self .cx .update(|cx| { @@ -1260,15 +1326,15 @@ impl AcpClientDelegate { })? .context("Failed to update thread")? .await?; - Ok(acp::ReadTextFileResponse { content }) + Ok(acp_old::ReadTextFileResponse { content }) } } -impl acp::Client for AcpClientDelegate { +impl acp_old::Client for OldAcpClientDelegate { async fn stream_assistant_message_chunk( &self, - params: acp::StreamAssistantMessageChunkParams, - ) -> Result<(), acp::Error> { + params: acp_old::StreamAssistantMessageChunkParams, + ) -> Result<(), acp_old::Error> { let cx = &mut self.cx.clone(); cx.update(|cx| { @@ -1284,8 +1350,8 @@ impl acp::Client for AcpClientDelegate { async fn request_tool_call_confirmation( &self, - request: acp::RequestToolCallConfirmationParams, - ) -> Result { + request: acp_old::RequestToolCallConfirmationParams, + ) -> Result { let cx = &mut self.cx.clone(); let ToolCallRequest { id, outcome } = cx .update(|cx| { @@ -1294,16 +1360,16 @@ impl acp::Client for AcpClientDelegate { })? .context("Failed to update thread")?; - Ok(acp::RequestToolCallConfirmationResponse { + Ok(acp_old::RequestToolCallConfirmationResponse { id, - outcome: outcome.await.map_err(acp::Error::into_internal_error)?, + outcome: outcome.await.map_err(acp_old::Error::into_internal_error)?, }) } async fn push_tool_call( &self, - request: acp::PushToolCallParams, - ) -> Result { + request: acp_old::PushToolCallParams, + ) -> Result { let cx = &mut self.cx.clone(); let id = cx .update(|cx| { @@ -1312,10 +1378,13 @@ impl acp::Client for AcpClientDelegate { })? .context("Failed to update thread")?; - Ok(acp::PushToolCallResponse { id }) + Ok(acp_old::PushToolCallResponse { id }) } - async fn update_tool_call(&self, request: acp::UpdateToolCallParams) -> Result<(), acp::Error> { + async fn update_tool_call( + &self, + request: acp_old::UpdateToolCallParams, + ) -> Result<(), acp_old::Error> { let cx = &mut self.cx.clone(); cx.update(|cx| { @@ -1328,7 +1397,7 @@ impl acp::Client for AcpClientDelegate { Ok(()) } - async fn update_plan(&self, request: acp::UpdatePlanParams) -> Result<(), acp::Error> { + async fn update_plan(&self, request: acp_old::UpdatePlanParams) -> Result<(), acp_old::Error> { let cx = &mut self.cx.clone(); cx.update(|cx| { @@ -1342,8 +1411,8 @@ impl acp::Client for AcpClientDelegate { async fn read_text_file( &self, - request: acp::ReadTextFileParams, - ) -> Result { + request: acp_old::ReadTextFileParams, + ) -> Result { let content = self .cx .update(|cx| { @@ -1352,10 +1421,13 @@ impl acp::Client for AcpClientDelegate { })? .context("Failed to update thread")? .await?; - Ok(acp::ReadTextFileResponse { content }) + Ok(acp_old::ReadTextFileResponse { content }) } - async fn write_text_file(&self, request: acp::WriteTextFileParams) -> Result<(), acp::Error> { + async fn write_text_file( + &self, + request: acp_old::WriteTextFileParams, + ) -> Result<(), acp_old::Error> { self.cx .update(|cx| { self.thread.update(cx, |thread, cx| { @@ -1369,24 +1441,19 @@ impl acp::Client for AcpClientDelegate { } } -fn acp_icon_to_ui_icon(icon: acp::Icon) -> IconName { +fn acp_icon_to_ui_icon(icon: acp_old::Icon) -> IconName { match icon { - acp::Icon::FileSearch => IconName::ToolSearch, - acp::Icon::Folder => IconName::ToolFolder, - acp::Icon::Globe => IconName::ToolWeb, - acp::Icon::Hammer => IconName::ToolHammer, - acp::Icon::LightBulb => IconName::ToolBulb, - acp::Icon::Pencil => IconName::ToolPencil, - acp::Icon::Regex => IconName::ToolRegex, - acp::Icon::Terminal => IconName::ToolTerminal, + acp_old::Icon::FileSearch => IconName::ToolSearch, + acp_old::Icon::Folder => IconName::ToolFolder, + acp_old::Icon::Globe => IconName::ToolWeb, + acp_old::Icon::Hammer => IconName::ToolHammer, + acp_old::Icon::LightBulb => IconName::ToolBulb, + acp_old::Icon::Pencil => IconName::ToolPencil, + acp_old::Icon::Regex => IconName::ToolRegex, + acp_old::Icon::Terminal => IconName::ToolTerminal, } } -pub struct ToolCallRequest { - pub id: acp::ToolCallId, - pub outcome: oneshot::Receiver, -} - #[cfg(test)] mod tests { use super::*; @@ -1424,8 +1491,8 @@ mod tests { fake_server.on_user_message(move |_, server, mut cx| async move { server .update(&mut cx, |server, _| { - server.send_to_zed(acp::StreamAssistantMessageChunkParams { - chunk: acp::AssistantMessageChunk::Thought { + server.send_to_zed(acp_old::StreamAssistantMessageChunkParams { + chunk: acp_old::AssistantMessageChunk::Thought { thought: "Thinking ".into(), }, }) @@ -1434,8 +1501,8 @@ mod tests { .unwrap(); server .update(&mut cx, |server, _| { - server.send_to_zed(acp::StreamAssistantMessageChunkParams { - chunk: acp::AssistantMessageChunk::Thought { + server.send_to_zed(acp_old::StreamAssistantMessageChunkParams { + chunk: acp_old::AssistantMessageChunk::Thought { thought: "hard!".into(), }, }) @@ -1501,7 +1568,7 @@ mod tests { async move { let content = server .update(&mut cx, |server, _| { - server.send_to_zed(acp::ReadTextFileParams { + server.send_to_zed(acp_old::ReadTextFileParams { path: path!("/tmp/foo").into(), line: None, limit: None, @@ -1513,7 +1580,7 @@ mod tests { read_file_tx.take().unwrap().send(()).unwrap(); server .update(&mut cx, |server, _| { - server.send_to_zed(acp::WriteTextFileParams { + server.send_to_zed(acp_old::WriteTextFileParams { path: path!("/tmp/foo").into(), content: "one\ntwo\nthree\nfour\nfive\n".to_string(), }) @@ -1564,9 +1631,9 @@ mod tests { async move { let tool_call_result = server .update(&mut cx, |server, _| { - server.send_to_zed(acp::PushToolCallParams { + server.send_to_zed(acp_old::PushToolCallParams { label: "Fetch".to_string(), - icon: acp::Icon::Globe, + icon: acp_old::Icon::Globe, content: None, locations: vec![], }) @@ -1592,7 +1659,7 @@ mod tests { thread.entries[1], AgentThreadEntry::ToolCall(ToolCall { status: ToolCallStatus::Allowed { - status: acp::ToolCallStatus::Running, + status: acp_old::ToolCallStatus::Running, .. }, .. @@ -1619,9 +1686,9 @@ mod tests { fake_server .update(cx, |fake_server, _| { - fake_server.send_to_zed(acp::UpdateToolCallParams { + fake_server.send_to_zed(acp_old::UpdateToolCallParams { tool_call_id: tool_call_id.borrow().unwrap(), - status: acp::ToolCallStatus::Finished, + status: acp_old::ToolCallStatus::Finished, content: None, }) }) @@ -1636,7 +1703,7 @@ mod tests { thread.entries[1], AgentThreadEntry::ToolCall(ToolCall { status: ToolCallStatus::Allowed { - status: acp::ToolCallStatus::Finished, + status: acp_old::ToolCallStatus::Finished, .. }, .. @@ -1681,8 +1748,8 @@ mod tests { let thread = cx.new(|cx| { let foreground_executor = cx.foreground_executor().clone(); - let (connection, io_fut) = acp::AgentConnection::connect_to_agent( - AcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()), + let (connection, io_fut) = acp_old::AgentConnection::connect_to_agent( + OldAcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()), stdin_tx, stdout_rx, move |fut| { @@ -1703,16 +1770,16 @@ mod tests { } pub struct FakeAcpServer { - connection: acp::ClientConnection, + connection: acp_old::ClientConnection, _io_task: Task<()>, on_user_message: Option< Rc< dyn Fn( - acp::SendUserMessageParams, + acp_old::SendUserMessageParams, Entity, AsyncApp, - ) -> LocalBoxFuture<'static, Result<(), acp::Error>>, + ) -> LocalBoxFuture<'static, Result<(), acp_old::Error>>, >, >, } @@ -1723,29 +1790,29 @@ mod tests { cx: AsyncApp, } - impl acp::Agent for FakeAgent { + impl acp_old::Agent for FakeAgent { async fn initialize( &self, - params: acp::InitializeParams, - ) -> Result { - Ok(acp::InitializeResponse { + params: acp_old::InitializeParams, + ) -> Result { + Ok(acp_old::InitializeResponse { protocol_version: params.protocol_version, is_authenticated: true, }) } - async fn authenticate(&self) -> Result<(), acp::Error> { + async fn authenticate(&self) -> Result<(), acp_old::Error> { Ok(()) } - async fn cancel_send_message(&self) -> Result<(), acp::Error> { + async fn cancel_send_message(&self) -> Result<(), acp_old::Error> { Ok(()) } async fn send_user_message( &self, - request: acp::SendUserMessageParams, - ) -> Result<(), acp::Error> { + request: acp_old::SendUserMessageParams, + ) -> Result<(), acp_old::Error> { let mut cx = self.cx.clone(); let handler = self .server @@ -1768,7 +1835,7 @@ mod tests { }; let foreground_executor = cx.foreground_executor().clone(); - let (connection, io_fut) = acp::ClientConnection::connect_to_client( + let (connection, io_fut) = acp_old::ClientConnection::connect_to_client( agent.clone(), stdout, stdin, @@ -1787,10 +1854,14 @@ mod tests { fn on_user_message( &mut self, - handler: impl for<'a> Fn(acp::SendUserMessageParams, Entity, AsyncApp) -> F + handler: impl for<'a> Fn( + acp_old::SendUserMessageParams, + Entity, + AsyncApp, + ) -> F + 'static, ) where - F: Future> + 'static, + F: Future> + 'static, { self.on_user_message .replace(Rc::new(move |request, server, cx| { @@ -1798,7 +1869,7 @@ mod tests { })); } - fn send_to_zed( + fn send_to_zed( &self, message: T, ) -> BoxedLocal> { diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index 5c7deb489ba319c49569620b85d15af586ae84e9..d91babbd21a56f44db464c13ecc394560687e55f 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -32,7 +32,7 @@ use util::ResultExt; use crate::claude::tools::ClaudeTool; use crate::mcp_server::{self, McpConfig, ZedMcpServer}; use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings}; -use acp_thread::{AcpClientDelegate, AcpThread, AgentConnection}; +use acp_thread::{OldAcpClientDelegate, AcpThread, AgentConnection}; #[derive(Clone)] pub struct ClaudeCode; @@ -157,7 +157,7 @@ impl AgentServer for ClaudeCode { cx.new(|cx| { let end_turn_tx = Rc::new(RefCell::new(None)); - let delegate = AcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()); + let delegate = OldAcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()); delegate_tx.send(Some(delegate.clone())).log_err(); let handler_task = cx.foreground_executor().spawn({ @@ -328,7 +328,7 @@ async fn spawn_claude( } struct ClaudeAgentConnection { - delegate: AcpClientDelegate, + delegate: OldAcpClientDelegate, session_id: Uuid, outgoing_tx: UnboundedSender, end_turn_tx: Rc>>>>, @@ -339,7 +339,7 @@ struct ClaudeAgentConnection { impl ClaudeAgentConnection { async fn handle_message( - delegate: AcpClientDelegate, + delegate: OldAcpClientDelegate, message: SdkMessage, end_turn_tx: Rc>>>>, tool_id_map: Rc>>, diff --git a/crates/agent_servers/src/codex.rs b/crates/agent_servers/src/codex.rs index 4e2244ba9b933f9cc92acacd2b604f799924271f..c443e688d62ecb96ebe972d2170e807f30ec7c96 100644 --- a/crates/agent_servers/src/codex.rs +++ b/crates/agent_servers/src/codex.rs @@ -25,7 +25,7 @@ use util::ResultExt; use crate::mcp_server::{McpConfig, ZedMcpServer}; use crate::{AgentServer, AgentServerCommand, AllAgentServersSettings}; -use acp_thread::{AcpClientDelegate, AcpThread, AgentConnection}; +use acp_thread::{AcpThread, AgentConnection, OldAcpClientDelegate}; #[derive(Clone)] pub struct Codex; @@ -220,7 +220,7 @@ impl AgentServer for Codex { }); cx.new(|cx| { - let delegate = AcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()); + let delegate = OldAcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()); delegate_tx.send(Some(delegate.clone())).log_err(); let handler_task = cx.spawn({ @@ -421,7 +421,7 @@ struct CodexAgentConnection { impl CodexAgentConnection { async fn handle_acp_notification( - delegate: &AcpClientDelegate, + delegate: &OldAcpClientDelegate, event: AcpNotification, tool_id_map: &Rc>>, ) -> Result<()> { diff --git a/crates/agent_servers/src/mcp_server.rs b/crates/agent_servers/src/mcp_server.rs index bcba8a71e02344183c077e7e09814213b08b5d9d..dc4726b8c02be7137548906b583aefe4ab13e095 100644 --- a/crates/agent_servers/src/mcp_server.rs +++ b/crates/agent_servers/src/mcp_server.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, path::PathBuf, rc::Rc}; -use acp_thread::AcpClientDelegate; +use acp_thread::OldAcpClientDelegate; use agentic_coding_protocol::{self as acp, Client, ReadTextFileParams, WriteTextFileParams}; use anyhow::{Context, Result}; use collections::HashMap; @@ -51,7 +51,7 @@ enum PermissionToolBehavior { impl ZedMcpServer { pub async fn new( - delegate: watch::Receiver>, + delegate: watch::Receiver>, tool_id_map: Rc>>, cx: &AsyncApp, ) -> Result { @@ -147,7 +147,7 @@ impl ZedMcpServer { fn handle_call_tool( request: CallToolParams, - mut delegate_watch: watch::Receiver>, + mut delegate_watch: watch::Receiver>, tool_id_map: Rc>>, cx: &App, ) -> Task> { @@ -202,7 +202,7 @@ impl ZedMcpServer { fn handle_read_tool_call( params: ReadToolParams, - delegate: AcpClientDelegate, + delegate: OldAcpClientDelegate, cx: &AsyncApp, ) -> Task> { cx.foreground_executor().spawn(async move { @@ -222,7 +222,7 @@ impl ZedMcpServer { fn handle_edit_tool_call( params: EditToolParams, - delegate: AcpClientDelegate, + delegate: OldAcpClientDelegate, cx: &AsyncApp, ) -> Task> { cx.foreground_executor().spawn(async move { @@ -252,7 +252,7 @@ impl ZedMcpServer { fn handle_permissions_tool_call( params: PermissionToolParams, - delegate: AcpClientDelegate, + delegate: OldAcpClientDelegate, tool_id_map: Rc>>, cx: &AsyncApp, ) -> Task> { diff --git a/crates/agent_servers/src/stdio_agent_server.rs b/crates/agent_servers/src/stdio_agent_server.rs index e60dd39de45925223196f43fcb6025c49281c4c9..4751f2bb41a983ca7ab5868f5610b228358ceb47 100644 --- a/crates/agent_servers/src/stdio_agent_server.rs +++ b/crates/agent_servers/src/stdio_agent_server.rs @@ -1,5 +1,5 @@ use crate::{AgentServer, AgentServerCommand, AgentServerVersion}; -use acp_thread::{AcpClientDelegate, AcpThread, LoadError}; +use acp_thread::{OldAcpClientDelegate, AcpThread, LoadError}; use agentic_coding_protocol as acp; use anyhow::{Result, anyhow}; use gpui::{App, AsyncApp, Entity, Task, prelude::*}; @@ -77,7 +77,7 @@ impl AgentServer for T { let foreground_executor = cx.foreground_executor().clone(); let (connection, io_fut) = acp::AgentConnection::connect_to_agent( - AcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()), + OldAcpClientDelegate::new(cx.entity().downgrade(), cx.to_async()), stdin, stdout, move |fut| foreground_executor.spawn(fut).detach(), diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 95f4f81205f198f270eb5d4dec6597f9b04efd2f..26cd7a059041f06a19f159b6d4436ecc7dc30f17 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -732,16 +732,11 @@ impl AcpThreadView { .gap_2p5() .children(chunks.iter().enumerate().map(|(chunk_ix, chunk)| { match chunk { - AssistantMessageChunk::Text { chunk } => self + AssistantMessageChunk::Message { block: chunk } => self .render_markdown(chunk.clone(), style.clone()) .into_any_element(), - AssistantMessageChunk::Thought { chunk } => self.render_thinking_block( - index, - chunk_ix, - chunk.clone(), - window, - cx, - ), + AssistantMessageChunk::Thought { block: chunk } => self + .render_thinking_block(index, chunk_ix, chunk.clone(), window, cx), } })) .into_any();