diff --git a/Cargo.lock b/Cargo.lock index 3f88c7ad113311ac931289e427e3a5ec6ac8d887..47cb256348e32965afdcb50dd6c1334bc7c36c29 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,6 +18,7 @@ dependencies = [ "env_logger 0.11.8", "futures 0.3.31", "gpui", + "indoc", "language", "log", "markdown", diff --git a/crates/acp/Cargo.toml b/crates/acp/Cargo.toml index 2c98f2469ce39c68cd7d99627c64a9965d1219a7..68a416cec694fb81371b6e33a006ec5d9d61e3b5 100644 --- a/crates/acp/Cargo.toml +++ b/crates/acp/Cargo.toml @@ -45,6 +45,7 @@ zed_actions.workspace = true async-pipe.workspace = true env_logger.workspace = true gpui = { workspace = true, "features" = ["test-support"] } +indoc.workspace = true project = { workspace = true, "features" = ["test-support"] } serde_json.workspace = true util.workspace = true diff --git a/crates/acp/src/acp.rs b/crates/acp/src/acp.rs index 6e0c2e399919b3bf9b286e7904fc752cc362485a..7f75a3ea046b3165aa17b6d43b5402efef12cd04 100644 --- a/crates/acp/src/acp.rs +++ b/crates/acp/src/acp.rs @@ -748,6 +748,45 @@ impl AcpThread { Task::ready(Ok(())) } } + + #[cfg(test)] + pub fn to_string(&self, cx: &App) -> String { + let mut result = String::new(); + for entry in &self.entries { + match &entry.content { + AgentThreadEntryContent::UserMessage(user_message) => { + result.push_str("# User\n"); + for chunk in &user_message.chunks { + match chunk { + UserMessageChunk::Text { chunk } => { + result.push_str(chunk.read(cx).source()); + result.push('\n'); + } + _ => unimplemented!(), + } + } + } + AgentThreadEntryContent::AssistantMessage(assistant_message) => { + result.push_str("# Assistant\n"); + for chunk in &assistant_message.chunks { + match chunk { + AssistantMessageChunk::Text { chunk } => { + result.push_str(chunk.read(cx).source()); + result.push('\n') + } + AssistantMessageChunk::Thought { chunk } => { + result.push_str("\n"); + result.push_str(chunk.read(cx).source()); + result.push_str("\n\n"); + } + } + } + } + AgentThreadEntryContent::ToolCall(_tool_call) => unimplemented!(), + } + } + result + } } fn acp_icon_to_ui_icon(icon: acp::Icon) -> IconName { @@ -775,10 +814,11 @@ mod tests { use async_trait::async_trait; use futures::{FutureExt as _, channel::mpsc, future::LocalBoxFuture, select}; use gpui::{AsyncApp, TestAppContext}; + use indoc::indoc; use project::FakeFs; use serde_json::json; use settings::SettingsStore; - use smol::stream::StreamExt as _; + use smol::{future::BoxedLocal, stream::StreamExt as _}; use std::{env, path::Path, process::Stdio, rc::Rc, time::Duration}; use util::path; @@ -793,7 +833,7 @@ mod tests { } #[gpui::test] - async fn test_message_receipt(cx: &mut TestAppContext) { + async fn test_thinking_concatenation(cx: &mut TestAppContext) { init_test(cx); cx.executor().allow_parking(); @@ -804,40 +844,54 @@ mod tests { server.initialize().await.unwrap(); + let thread = server.create_thread(&mut cx.to_async()).await.unwrap(); + fake_server.update(cx, |fake_server, _| { fake_server.on_user_message(move |params, server, mut cx| async move { server - .update(&mut cx, |server, cx| { - server.send_to_zed( - acp::StreamAssistantMessageChunkParams { - thread_id: params.thread_id.clone(), - chunk: acp::AssistantMessageChunk::Thought { - chunk: "Thinking ".into(), - }, + .update(&mut cx, |server, _| { + server.send_to_zed(acp::StreamAssistantMessageChunkParams { + thread_id: params.thread_id.clone(), + chunk: acp::AssistantMessageChunk::Thought { + chunk: "Thinking ".into(), }, - cx, - ) + }) })? .await .unwrap(); server - .update(&mut cx, |server, cx| { - server.send_to_zed( - acp::StreamAssistantMessageChunkParams { - thread_id: params.thread_id, - chunk: acp::AssistantMessageChunk::Thought { - chunk: "hard!".into(), - }, + .update(&mut cx, |server, _| { + server.send_to_zed(acp::StreamAssistantMessageChunkParams { + thread_id: params.thread_id, + chunk: acp::AssistantMessageChunk::Thought { + chunk: "hard!".into(), }, - cx, - ) + }) })? .await .unwrap(); Ok(acp::SendUserMessageResponse) }) - }) + }); + + thread + .update(cx, |thread, cx| thread.send("Hello from Zed!", cx)) + .await + .unwrap(); + + let output = thread.read_with(cx, |thread, cx| thread.to_string(cx)); + assert_eq!( + output, + indoc! {r#" + # User + Hello from Zed! + # Assistant + + Thinking hard! + + "#} + ); } #[gpui::test] @@ -1207,10 +1261,8 @@ mod tests { fn send_to_zed( &self, message: T, - cx: &Context, - ) -> Task> { - let future = self.connection.request(message); - cx.foreground_executor().spawn(future) + ) -> BoxedLocal> { + self.connection.request(message).boxed_local() } } }