diff --git a/Cargo.lock b/Cargo.lock index 0667a62f8eb25fd1c7f1149dbc190c4fddb5cecc..b4088336611d0434ce7938f8216cdd30c1aec709 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -196,9 +196,9 @@ dependencies = [ [[package]] name = "agent-client-protocol" -version = "0.2.0-alpha.8" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08539e8d6b2ccca6cd00afdd42211698f7677adef09108a09414c11f1f45fdaf" +checksum = "003fb91bf1b8d6e15f72c45fb9171839af8241e81e3839fbb73536af113b7a79" dependencies = [ "anyhow", "async-broadcast", diff --git a/Cargo.toml b/Cargo.toml index c8983efc7784a483fd7c5c759c7d9c10f1dab680..042bd8cf421b737130344242c2a58ad4cfb2a7f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -434,7 +434,7 @@ zlog_settings = { path = "crates/zlog_settings" } # External crates # -agent-client-protocol = { version = "0.2.0-alpha.8", features = ["unstable"] } +agent-client-protocol = { version = "0.2.1", features = ["unstable"] } 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/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index c7279abdc6d63ff77644549bb64db160abc446bf..afbb4781f61d5ccf1ea753df1fd0379e533e8e46 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -862,7 +862,7 @@ impl AcpThread { mut prompt_capabilities_rx: watch::Receiver, cx: &mut Context, ) -> Self { - let prompt_capabilities = *prompt_capabilities_rx.borrow(); + let prompt_capabilities = prompt_capabilities_rx.borrow().clone(); let task = cx.spawn::<_, anyhow::Result<()>>(async move |this, cx| { loop { let caps = prompt_capabilities_rx.recv().await?; @@ -906,7 +906,7 @@ impl AcpThread { } pub fn prompt_capabilities(&self) -> acp::PromptCapabilities { - self.prompt_capabilities + self.prompt_capabilities.clone() } pub fn connection(&self) -> &Rc { @@ -1446,6 +1446,7 @@ impl AcpThread { vec![acp::ContentBlock::Text(acp::TextContent { text: message.to_string(), annotations: None, + meta: None, })], cx, ) @@ -1464,6 +1465,7 @@ impl AcpThread { let request = acp::PromptRequest { prompt: message.clone(), session_id: self.session_id.clone(), + meta: None, }; let git_store = self.project.read(cx).git_store().clone(); @@ -1555,7 +1557,8 @@ impl AcpThread { let canceled = matches!( result, Ok(Ok(acp::PromptResponse { - stop_reason: acp::StopReason::Cancelled + stop_reason: acp::StopReason::Cancelled, + meta: None, })) ); @@ -1571,6 +1574,7 @@ impl AcpThread { // Handle refusal - distinguish between user prompt and tool call refusals if let Ok(Ok(acp::PromptResponse { stop_reason: acp::StopReason::Refusal, + meta: _, })) = result { if let Some((user_msg_ix, _)) = this.last_user_message() { @@ -2163,6 +2167,7 @@ mod tests { acp::ContentBlock::Text(acp::TextContent { annotations: None, text: "Hello, ".to_string(), + meta: None, }), cx, ); @@ -2186,6 +2191,7 @@ mod tests { acp::ContentBlock::Text(acp::TextContent { annotations: None, text: "world!".to_string(), + meta: None, }), cx, ); @@ -2207,6 +2213,7 @@ mod tests { acp::ContentBlock::Text(acp::TextContent { annotations: None, text: "Assistant response".to_string(), + meta: None, }), false, cx, @@ -2220,6 +2227,7 @@ mod tests { acp::ContentBlock::Text(acp::TextContent { annotations: None, text: "New user message".to_string(), + meta: None, }), cx, ); @@ -2265,6 +2273,7 @@ mod tests { })?; Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -2335,6 +2344,7 @@ mod tests { .unwrap(); Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -2403,6 +2413,7 @@ mod tests { locations: vec![], raw_input: None, raw_output: None, + meta: None, }), cx, ) @@ -2411,6 +2422,7 @@ mod tests { .unwrap(); Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -2459,6 +2471,7 @@ mod tests { status: Some(acp::ToolCallStatus::Completed), ..Default::default() }, + meta: None, }), cx, ) @@ -2501,11 +2514,13 @@ mod tests { path: "/test/test.txt".into(), old_text: None, new_text: "foo".into(), + meta: None, }, }], locations: vec![], raw_input: None, raw_output: None, + meta: None, }), cx, ) @@ -2514,6 +2529,7 @@ mod tests { .unwrap(); Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -2576,6 +2592,7 @@ mod tests { })?; Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -2743,6 +2760,7 @@ mod tests { raw_output: Some( serde_json::json!({"result": "inappropriate content"}), ), + meta: None, }), cx, ) @@ -2752,10 +2770,12 @@ mod tests { // Now return refusal because of the tool result Ok(acp::PromptResponse { stop_reason: acp::StopReason::Refusal, + meta: None, }) } else { Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } } @@ -2789,6 +2809,7 @@ mod tests { vec![acp::ContentBlock::Text(acp::TextContent { text: "Hello".into(), annotations: None, + meta: None, })], cx, ) @@ -2841,6 +2862,7 @@ mod tests { async move { Ok(acp::PromptResponse { stop_reason: acp::StopReason::Refusal, + meta: None, }) } .boxed_local() @@ -2848,6 +2870,7 @@ mod tests { async move { Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -2909,6 +2932,7 @@ mod tests { if refuse_next.load(SeqCst) { return Ok(acp::PromptResponse { stop_reason: acp::StopReason::Refusal, + meta: None, }); } @@ -2927,6 +2951,7 @@ mod tests { })?; Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) } .boxed_local() @@ -3082,6 +3107,7 @@ mod tests { image: true, audio: true, embedded_context: true, + meta: None, }), cx, ) @@ -3113,6 +3139,7 @@ mod tests { } else { Task::ready(Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, })) } } diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index dfb1e3763d504e65bfbef636fb8c592643ce92c9..10c9dd22b6ec476f17fabeae7f6bd4f1a9672db7 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -354,6 +354,7 @@ mod test_support { image: true, audio: true, embedded_context: true, + meta: None, }), cx, ) @@ -393,7 +394,10 @@ mod test_support { response_tx.replace(tx); cx.spawn(async move |_| { let stop_reason = rx.await?; - Ok(acp::PromptResponse { stop_reason }) + Ok(acp::PromptResponse { + stop_reason, + meta: None, + }) }) } else { for update in self.next_prompt_updates.lock().drain(..) { @@ -432,6 +436,7 @@ mod test_support { try_join_all(tasks).await?; Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) }) } diff --git a/crates/acp_thread/src/terminal.rs b/crates/acp_thread/src/terminal.rs index 6b4cdb73469d9dd7d1a1759bf3aa28d005d1f13e..a927083b0bd576f1580ba261d4028407fcea7a5c 100644 --- a/crates/acp_thread/src/terminal.rs +++ b/crates/acp_thread/src/terminal.rs @@ -75,6 +75,7 @@ impl Terminal { acp::TerminalExitStatus { exit_code: exit_status.as_ref().map(|e| e.exit_code()), signal: exit_status.and_then(|e| e.signal().map(Into::into)), + meta: None, } }) .shared(), @@ -105,7 +106,9 @@ impl Terminal { exit_status: Some(acp::TerminalExitStatus { exit_code: exit_status.as_ref().map(|e| e.exit_code()), signal: exit_status.and_then(|e| e.signal().map(Into::into)), + meta: None, }), + meta: None, } } else { let (current_content, original_len) = self.truncated_output(cx); @@ -114,6 +117,7 @@ impl Terminal { truncated: current_content.len() < original_len, output: current_content, exit_status: None, + meta: None, } } } diff --git a/crates/agent2/src/agent.rs b/crates/agent2/src/agent.rs index 6e0df0cffd8e83c446c4acd1fde74c0f8e4b5b8c..d72216208769de98c6ad408fec0c17133090b79b 100644 --- a/crates/agent2/src/agent.rs +++ b/crates/agent2/src/agent.rs @@ -747,6 +747,7 @@ impl NativeAgentConnection { acp::ContentBlock::Text(acp::TextContent { text, annotations: None, + meta: None, }), false, cx, @@ -759,6 +760,7 @@ impl NativeAgentConnection { acp::ContentBlock::Text(acp::TextContent { text, annotations: None, + meta: None, }), true, cx, @@ -804,7 +806,10 @@ impl NativeAgentConnection { } ThreadEvent::Stop(stop_reason) => { log::debug!("Assistant message complete: {:?}", stop_reason); - return Ok(acp::PromptResponse { stop_reason }); + return Ok(acp::PromptResponse { + stop_reason, + meta: None, + }); } } } @@ -818,6 +823,7 @@ impl NativeAgentConnection { log::debug!("Response stream completed"); anyhow::Ok(acp::PromptResponse { stop_reason: acp::StopReason::EndTurn, + meta: None, }) }) } @@ -1441,6 +1447,7 @@ mod tests { mime_type: None, size: None, title: None, + meta: None, }), " mean?".into(), ], diff --git a/crates/agent2/src/tests/mod.rs b/crates/agent2/src/tests/mod.rs index 884580ed69009d168b3266870acf4f698a2f5450..c0f693afe6dc0decdce4447471191bd78cf345f1 100644 --- a/crates/agent2/src/tests/mod.rs +++ b/crates/agent2/src/tests/mod.rs @@ -1299,6 +1299,7 @@ async fn test_cancellation(cx: &mut TestAppContext) { status: Some(acp::ToolCallStatus::Completed), .. }, + meta: None, }, )) if Some(&id) == echo_id.as_ref() => { echo_completed = true; @@ -1926,6 +1927,7 @@ async fn test_agent_connection(cx: &mut TestAppContext) { acp::PromptRequest { session_id: session_id.clone(), prompt: vec!["ghi".into()], + meta: None, }, cx, ) @@ -1990,6 +1992,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { locations: vec![], raw_input: Some(json!({})), raw_output: None, + meta: None, } ); let update = expect_tool_call_update_fields(&mut events).await; @@ -2003,6 +2006,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { raw_input: Some(json!({ "content": "Thinking hard!" })), ..Default::default() }, + meta: None, } ); let update = expect_tool_call_update_fields(&mut events).await; @@ -2014,6 +2018,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { status: Some(acp::ToolCallStatus::InProgress), ..Default::default() }, + meta: None, } ); let update = expect_tool_call_update_fields(&mut events).await; @@ -2025,6 +2030,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { content: Some(vec!["Thinking hard!".into()]), ..Default::default() }, + meta: None, } ); let update = expect_tool_call_update_fields(&mut events).await; @@ -2037,6 +2043,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) { raw_output: Some("Finished thinking.".into()), ..Default::default() }, + meta: None, } ); } diff --git a/crates/agent2/src/thread.rs b/crates/agent2/src/thread.rs index 20c4cd07533b7cf9bd1dd00e666bbb66552db9d7..b19d34adafe4a7b6567be9a1864b88ea2504bf12 100644 --- a/crates/agent2/src/thread.rs +++ b/crates/agent2/src/thread.rs @@ -614,6 +614,7 @@ impl Thread { fn prompt_capabilities(model: Option<&dyn LanguageModel>) -> acp::PromptCapabilities { let image = model.map_or(true, |model| model.supports_images()); acp::PromptCapabilities { + meta: None, image, audio: false, embedded_context: true, @@ -728,6 +729,7 @@ impl Thread { stream .0 .unbounded_send(Ok(ThreadEvent::ToolCall(acp::ToolCall { + meta: None, id: acp::ToolCallId(tool_use.id.to_string().into()), title: tool_use.name.to_string(), kind: acp::ToolKind::Other, @@ -2333,6 +2335,7 @@ impl ThreadEventStream { input: serde_json::Value, ) -> acp::ToolCall { acp::ToolCall { + meta: None, id: acp::ToolCallId(id.to_string().into()), title, kind, @@ -2352,6 +2355,7 @@ impl ThreadEventStream { self.0 .unbounded_send(Ok(ThreadEvent::ToolCallUpdate( acp::ToolCallUpdate { + meta: None, id: acp::ToolCallId(tool_use_id.to_string().into()), fields, } @@ -2437,6 +2441,7 @@ impl ToolCallEventStream { .unbounded_send(Ok(ThreadEvent::ToolCallAuthorization( ToolCallAuthorization { tool_call: acp::ToolCallUpdate { + meta: None, id: acp::ToolCallId(self.tool_use_id.to_string().into()), fields: acp::ToolCallUpdateFields { title: Some(title.into()), @@ -2448,16 +2453,19 @@ impl ToolCallEventStream { id: acp::PermissionOptionId("always_allow".into()), name: "Always Allow".into(), kind: acp::PermissionOptionKind::AllowAlways, + meta: None, }, acp::PermissionOption { id: acp::PermissionOptionId("allow".into()), name: "Allow".into(), kind: acp::PermissionOptionKind::AllowOnce, + meta: None, }, acp::PermissionOption { id: acp::PermissionOptionId("deny".into()), name: "Deny".into(), kind: acp::PermissionOptionKind::RejectOnce, + meta: None, }, ], response: response_tx, @@ -2611,17 +2619,21 @@ impl From for acp::ContentBlock { UserMessageContent::Text(text) => acp::ContentBlock::Text(acp::TextContent { text, annotations: None, + meta: None, }), UserMessageContent::Image(image) => acp::ContentBlock::Image(acp::ImageContent { data: image.source.to_string(), mime_type: "image/png".to_string(), + meta: None, annotations: None, uri: None, }), UserMessageContent::Mention { uri, content } => { acp::ContentBlock::Resource(acp::EmbeddedResource { + meta: None, resource: acp::EmbeddedResourceResource::TextResourceContents( acp::TextResourceContents { + meta: None, mime_type: None, text: content, uri: uri.to_uri().to_string(), diff --git a/crates/agent2/src/tools/edit_file_tool.rs b/crates/agent2/src/tools/edit_file_tool.rs index 9237961bce513d740989c7e3076395ed68473859..1e725b8f59d1219a0761334c5364940ee0e8bf6a 100644 --- a/crates/agent2/src/tools/edit_file_tool.rs +++ b/crates/agent2/src/tools/edit_file_tool.rs @@ -274,6 +274,7 @@ impl AgentTool for EditFileTool { locations: Some(vec![acp::ToolCallLocation { path: abs_path, line: None, + meta: None, }]), ..Default::default() }); @@ -353,7 +354,7 @@ impl AgentTool for EditFileTool { }).ok(); if let Some(abs_path) = abs_path.clone() { event_stream.update_fields(ToolCallUpdateFields { - locations: Some(vec![ToolCallLocation { path: abs_path, line }]), + locations: Some(vec![ToolCallLocation { path: abs_path, line, meta: None }]), ..Default::default() }); } diff --git a/crates/agent2/src/tools/find_path_tool.rs b/crates/agent2/src/tools/find_path_tool.rs index 735ec67cffa31969e4eef741d6a23de05f3e15dc..b8b60f79f4cf9808a730b0c6428885b23b32d998 100644 --- a/crates/agent2/src/tools/find_path_tool.rs +++ b/crates/agent2/src/tools/find_path_tool.rs @@ -138,6 +138,7 @@ impl AgentTool for FindPathTool { mime_type: None, size: None, title: None, + meta: None, }), }) .collect(), diff --git a/crates/agent2/src/tools/read_file_tool.rs b/crates/agent2/src/tools/read_file_tool.rs index 09f2ff3ca9f5ecb30189c7d1625c1728a8ff3b11..87163e769c26b0cee053fcf149d047fc451c470f 100644 --- a/crates/agent2/src/tools/read_file_tool.rs +++ b/crates/agent2/src/tools/read_file_tool.rs @@ -149,6 +149,7 @@ impl AgentTool for ReadFileTool { locations: Some(vec![acp::ToolCallLocation { path: abs_path.clone(), line: input.start_line.map(|line| line.saturating_sub(1)), + meta: None, }]), ..Default::default() }); diff --git a/crates/agent2/src/tools/web_search_tool.rs b/crates/agent2/src/tools/web_search_tool.rs index ce26bccddeeb998abf6d39cbe2acfe91cecc6d1b..b65c89167d6f5ed026bb4ebb5e1990fa4e1c17ce 100644 --- a/crates/agent2/src/tools/web_search_tool.rs +++ b/crates/agent2/src/tools/web_search_tool.rs @@ -122,6 +122,7 @@ fn emit_update(response: &WebSearchResponse, event_stream: &ToolCallEventStream) mime_type: None, annotations: None, size: None, + meta: None, }), }) .collect(), diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index 97bc172a41157a6e9434b34bb20b7225e9eb0821..cc897d85e7b4de149a0dca84df84d2b8c2c5bc98 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -13,7 +13,7 @@ use util::ResultExt as _; use std::path::PathBuf; use std::{any::Any, cell::RefCell}; -use std::{path::Path, rc::Rc}; +use std::{path::Path, rc::Rc, sync::Arc}; use thiserror::Error; use anyhow::{Context as _, Result}; @@ -156,9 +156,12 @@ impl AcpConnection { fs: acp::FileSystemCapability { read_text_file: true, write_text_file: true, + meta: None, }, terminal: true, + meta: None, }, + meta: None, }) .await?; @@ -226,6 +229,7 @@ impl AgentConnection for AcpConnection { .map(|(name, value)| acp::EnvVariable { name: name.clone(), value: value.clone(), + meta: None, }) .collect() } else { @@ -243,7 +247,7 @@ impl AgentConnection for AcpConnection { cx.spawn(async move |cx| { let response = conn - .new_session(acp::NewSessionRequest { mcp_servers, cwd }) + .new_session(acp::NewSessionRequest { mcp_servers, cwd, meta: None }) .await .map_err(|err| { if err.code == acp::ErrorCode::AUTH_REQUIRED.code { @@ -277,6 +281,7 @@ impl AgentConnection for AcpConnection { let result = conn.set_session_mode(acp::SetSessionModeRequest { session_id, mode_id: default_mode, + meta: None, }) .await.log_err(); @@ -316,7 +321,7 @@ impl AgentConnection for AcpConnection { action_log, session_id.clone(), // ACP doesn't currently support per-session prompt capabilities or changing capabilities dynamically. - watch::Receiver::constant(self.agent_capabilities.prompt_capabilities), + watch::Receiver::constant(self.agent_capabilities.prompt_capabilities.clone()), cx, ) })?; @@ -339,13 +344,13 @@ impl AgentConnection for AcpConnection { fn authenticate(&self, method_id: acp::AuthMethodId, cx: &mut App) -> Task> { let conn = self.connection.clone(); cx.foreground_executor().spawn(async move { - let result = conn - .authenticate(acp::AuthenticateRequest { - method_id: method_id.clone(), - }) - .await?; + conn.authenticate(acp::AuthenticateRequest { + method_id: method_id.clone(), + meta: None, + }) + .await?; - Ok(result) + Ok(()) }) } @@ -396,6 +401,7 @@ impl AgentConnection for AcpConnection { { Ok(acp::PromptResponse { stop_reason: acp::StopReason::Cancelled, + meta: None, }) } else { Err(anyhow!(details)) @@ -415,6 +421,7 @@ impl AgentConnection for AcpConnection { let conn = self.connection.clone(); let params = acp::CancelNotification { session_id: session_id.clone(), + meta: None, }; cx.foreground_executor() .spawn(async move { conn.cancel(params).await }) @@ -478,6 +485,7 @@ impl acp_thread::AgentSessionModes for AcpSessionModes { .set_session_mode(acp::SetSessionModeRequest { session_id, mode_id, + meta: None, }) .await; @@ -526,13 +534,16 @@ impl acp::Client for ClientDelegate { let outcome = task.await; - Ok(acp::RequestPermissionResponse { outcome }) + Ok(acp::RequestPermissionResponse { + outcome, + meta: None, + }) } async fn write_text_file( &self, arguments: acp::WriteTextFileRequest, - ) -> Result<(), acp::Error> { + ) -> Result { let cx = &mut self.cx.clone(); let task = self .session_thread(&arguments.session_id)? @@ -542,7 +553,7 @@ impl acp::Client for ClientDelegate { task.await?; - Ok(()) + Ok(Default::default()) } async fn read_text_file( @@ -558,7 +569,10 @@ impl acp::Client for ClientDelegate { let content = task.await?; - Ok(acp::ReadTextFileResponse { content }) + Ok(acp::ReadTextFileResponse { + content, + meta: None, + }) } async fn session_notification( @@ -607,26 +621,49 @@ impl acp::Client for ClientDelegate { Ok( terminal.read_with(&self.cx, |terminal, _| acp::CreateTerminalResponse { terminal_id: terminal.id().clone(), + meta: None, })?, ) } - async fn kill_terminal(&self, args: acp::KillTerminalRequest) -> Result<(), acp::Error> { + async fn kill_terminal_command( + &self, + args: acp::KillTerminalCommandRequest, + ) -> Result { self.session_thread(&args.session_id)? .update(&mut self.cx.clone(), |thread, cx| { thread.kill_terminal(args.terminal_id, cx) })??; - Ok(()) + Ok(Default::default()) } - async fn release_terminal(&self, args: acp::ReleaseTerminalRequest) -> Result<(), acp::Error> { + async fn ext_method( + &self, + _name: Arc, + _params: Arc, + ) -> Result, acp::Error> { + Err(acp::Error::method_not_found()) + } + + async fn ext_notification( + &self, + _name: Arc, + _params: Arc, + ) -> Result<(), acp::Error> { + Err(acp::Error::method_not_found()) + } + + async fn release_terminal( + &self, + args: acp::ReleaseTerminalRequest, + ) -> Result { self.session_thread(&args.session_id)? .update(&mut self.cx.clone(), |thread, cx| { thread.release_terminal(args.terminal_id, cx) })??; - Ok(()) + Ok(Default::default()) } async fn terminal_output( @@ -655,7 +692,10 @@ impl acp::Client for ClientDelegate { })?? .await; - Ok(acp::WaitForTerminalExitResponse { exit_status }) + Ok(acp::WaitForTerminalExitResponse { + exit_status, + meta: None, + }) } } diff --git a/crates/agent_servers/src/e2e_tests.rs b/crates/agent_servers/src/e2e_tests.rs index a4af1b6ad5c6048d27764653322c116f655f85fb..1ee2e099f0ae355267b5f0a5aaddb3371f427240 100644 --- a/crates/agent_servers/src/e2e_tests.rs +++ b/crates/agent_servers/src/e2e_tests.rs @@ -83,6 +83,7 @@ where acp::ContentBlock::Text(acp::TextContent { text: "Read the file ".into(), annotations: None, + meta: None, }), acp::ContentBlock::ResourceLink(acp::ResourceLink { uri: "foo.rs".into(), @@ -92,10 +93,12 @@ where mime_type: None, size: None, title: None, + meta: None, }), acp::ContentBlock::Text(acp::TextContent { text: " and tell me what the content of the println! is".into(), annotations: None, + meta: None, }), ], cx, diff --git a/crates/agent_ui/src/acp/completion_provider.rs b/crates/agent_ui/src/acp/completion_provider.rs index 5ef2e222d05e11c9848d8835800b86579f31f4de..8c14bd1642b05623b9236701630127c23f5e30af 100644 --- a/crates/agent_ui/src/acp/completion_provider.rs +++ b/crates/agent_ui/src/acp/completion_provider.rs @@ -1,4 +1,4 @@ -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::ops::Range; use std::rc::Rc; use std::sync::Arc; @@ -68,7 +68,7 @@ pub struct ContextPickerCompletionProvider { workspace: WeakEntity, history_store: Entity, prompt_store: Option>, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, } @@ -78,7 +78,7 @@ impl ContextPickerCompletionProvider { workspace: WeakEntity, history_store: Entity, prompt_store: Option>, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, ) -> Self { Self { @@ -600,7 +600,7 @@ impl ContextPickerCompletionProvider { }), ); - if self.prompt_capabilities.get().embedded_context { + if self.prompt_capabilities.borrow().embedded_context { const RECENT_COUNT: usize = 2; let threads = self .history_store @@ -622,7 +622,7 @@ impl ContextPickerCompletionProvider { workspace: &Entity, cx: &mut App, ) -> Vec { - let embedded_context = self.prompt_capabilities.get().embedded_context; + let embedded_context = self.prompt_capabilities.borrow().embedded_context; let mut entries = if embedded_context { vec![ ContextPickerEntry::Mode(ContextPickerMode::File), @@ -694,7 +694,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { ContextCompletion::try_parse( line, offset_to_line, - self.prompt_capabilities.get().embedded_context, + self.prompt_capabilities.borrow().embedded_context, ) }); let Some(state) = state else { @@ -896,7 +896,7 @@ impl CompletionProvider for ContextPickerCompletionProvider { ContextCompletion::try_parse( line, offset_to_line, - self.prompt_capabilities.get().embedded_context, + self.prompt_capabilities.borrow().embedded_context, ) .map(|completion| { completion.source_range().start <= offset_to_line + position.column as usize diff --git a/crates/agent_ui/src/acp/entry_view_state.rs b/crates/agent_ui/src/acp/entry_view_state.rs index ec57ea7e6df3244b6ea1bcb99212d845fa68c457..0d4dfb0c206a78b2af932d5f3ef7d57c9bfbfc16 100644 --- a/crates/agent_ui/src/acp/entry_view_state.rs +++ b/crates/agent_ui/src/acp/entry_view_state.rs @@ -1,8 +1,4 @@ -use std::{ - cell::{Cell, RefCell}, - ops::Range, - rc::Rc, -}; +use std::{cell::RefCell, ops::Range, rc::Rc}; use acp_thread::{AcpThread, AgentThreadEntry}; use agent_client_protocol::{self as acp, ToolCallId}; @@ -30,7 +26,7 @@ pub struct EntryViewState { history_store: Entity, prompt_store: Option>, entries: Vec, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, agent_name: SharedString, } @@ -41,7 +37,7 @@ impl EntryViewState { project: Entity, history_store: Entity, prompt_store: Option>, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, agent_name: SharedString, ) -> Self { @@ -448,11 +444,13 @@ mod tests { path: "/project/hello.txt".into(), old_text: Some("hi world".into()), new_text: "hello world".into(), + meta: None, }, }], locations: vec![], raw_input: None, raw_output: None, + meta: None, }; let connection = Rc::new(StubAgentConnection::new()); let thread = cx diff --git a/crates/agent_ui/src/acp/message_editor.rs b/crates/agent_ui/src/acp/message_editor.rs index 2c14e284d79534168dfbaad3f524a6b05af713d4..ab4e8d680925c96e64fdb2e7707bea9c1e177b5c 100644 --- a/crates/agent_ui/src/acp/message_editor.rs +++ b/crates/agent_ui/src/acp/message_editor.rs @@ -36,7 +36,7 @@ use prompt_store::{PromptId, PromptStore}; use rope::Point; use settings::Settings; use std::{ - cell::{Cell, RefCell}, + cell::RefCell, ffi::OsStr, fmt::Write, ops::{Range, RangeInclusive}, @@ -64,7 +64,7 @@ pub struct MessageEditor { workspace: WeakEntity, history_store: Entity, prompt_store: Option>, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, agent_name: SharedString, _subscriptions: Vec, @@ -89,7 +89,7 @@ impl MessageEditor { project: Entity, history_store: Entity, prompt_store: Option>, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, agent_name: SharedString, placeholder: &str, @@ -428,7 +428,7 @@ impl MessageEditor { .unwrap_or_default(); if Img::extensions().contains(&extension) && !extension.contains("svg") { - if !self.prompt_capabilities.get().image { + if !self.prompt_capabilities.borrow().image { return Task::ready(Err(anyhow!("This model does not support images yet"))); } let task = self @@ -789,7 +789,7 @@ impl MessageEditor { let contents = self .mention_set - .contents(&self.prompt_capabilities.get(), cx); + .contents(&self.prompt_capabilities.borrow(), cx); let editor = self.editor.clone(); cx.spawn(async move |_, cx| { @@ -834,8 +834,10 @@ impl MessageEditor { mime_type: None, text: content.clone(), uri: uri.to_uri().to_string(), + meta: None, }, ), + meta: None, }) } Mention::Image(mention_image) => { @@ -855,6 +857,7 @@ impl MessageEditor { data: mention_image.data.to_string(), mime_type: mention_image.format.mime_type().into(), uri, + meta: None, }) } Mention::UriOnly => { @@ -866,6 +869,7 @@ impl MessageEditor { mime_type: None, size: None, title: None, + meta: None, }) } }; @@ -920,7 +924,7 @@ impl MessageEditor { } fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context) { - if !self.prompt_capabilities.get().image { + if !self.prompt_capabilities.borrow().image { return; } @@ -1188,6 +1192,7 @@ impl MessageEditor { data, mime_type, annotations: _, + meta: _, }) => { let mention_uri = if let Some(uri) = uri { MentionUri::parse(&uri) @@ -1571,13 +1576,7 @@ impl Addon for MessageEditorAddon { #[cfg(test)] mod tests { - use std::{ - cell::{Cell, RefCell}, - ops::Range, - path::Path, - rc::Rc, - sync::Arc, - }; + use std::{cell::RefCell, ops::Range, path::Path, rc::Rc, sync::Arc}; use acp_thread::MentionUri; use agent_client_protocol as acp; @@ -1724,7 +1723,7 @@ mod tests { let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await; let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); - let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); + let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default())); // Start with no available commands - simulating Claude which doesn't support slash commands let available_commands = Rc::new(RefCell::new(vec![])); @@ -1773,6 +1772,7 @@ mod tests { name: "help".to_string(), description: "Get help".to_string(), input: None, + meta: None, }]); // Test that unsupported slash commands trigger an error when we have a list of available commands @@ -1887,12 +1887,13 @@ mod tests { let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); - let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); + let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default())); let available_commands = Rc::new(RefCell::new(vec![ acp::AvailableCommand { name: "quick-math".to_string(), description: "2 + 2 = 4 - 1 = 3".to_string(), input: None, + meta: None, }, acp::AvailableCommand { name: "say-hello".to_string(), @@ -1900,6 +1901,7 @@ mod tests { input: Some(acp::AvailableCommandInput::Unstructured { hint: "".to_string(), }), + meta: None, }, ])); @@ -2134,7 +2136,7 @@ mod tests { let context_store = cx.new(|cx| ContextStore::fake(project.clone(), cx)); let history_store = cx.new(|cx| HistoryStore::new(context_store, cx)); - let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); + let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default())); let (message_editor, editor) = workspace.update_in(&mut cx, |workspace, window, cx| { let workspace_handle = cx.weak_entity(); @@ -2189,10 +2191,11 @@ mod tests { editor.set_text("", window, cx); }); - prompt_capabilities.set(acp::PromptCapabilities { + prompt_capabilities.replace(acp::PromptCapabilities { image: true, audio: true, embedded_context: true, + meta: None, }); cx.simulate_input("Lorem "); @@ -2264,6 +2267,7 @@ mod tests { image: true, audio: true, embedded_context: true, + meta: None, }; let contents = message_editor @@ -2640,8 +2644,9 @@ mod tests { cx, ); // Enable embedded context so files are actually included - editor.prompt_capabilities.set(acp::PromptCapabilities { + editor.prompt_capabilities.replace(acp::PromptCapabilities { embedded_context: true, + meta: None, ..Default::default() }); editor diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index a25b95701571421f2266034e20b39ac6dcaabebf..c205f24cd3aff16737b6b66f44d36b1e2e223fdc 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -37,7 +37,7 @@ use project::{Project, ProjectEntryId}; use prompt_store::{PromptId, PromptStore}; use rope::Point; use settings::{Settings as _, SettingsStore}; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::path::Path; use std::sync::Arc; use std::time::Instant; @@ -290,7 +290,7 @@ pub struct AcpThreadView { editor_expanded: bool, should_be_following: bool, editing_message: Option, - prompt_capabilities: Rc>, + prompt_capabilities: Rc>, available_commands: Rc>>, is_loading_contents: bool, new_server_version_available: Option, @@ -334,7 +334,7 @@ impl AcpThreadView { window: &mut Window, cx: &mut Context, ) -> Self { - let prompt_capabilities = Rc::new(Cell::new(acp::PromptCapabilities::default())); + let prompt_capabilities = Rc::new(RefCell::new(acp::PromptCapabilities::default())); let available_commands = Rc::new(RefCell::new(vec![])); let placeholder = if agent.name() == "Zed Agent" { @@ -559,7 +559,7 @@ impl AcpThreadView { let action_log = thread.read(cx).action_log().clone(); this.prompt_capabilities - .set(thread.read(cx).prompt_capabilities()); + .replace(thread.read(cx).prompt_capabilities()); let count = thread.read(cx).entries().len(); this.entry_view_state.update(cx, |view_state, cx| { @@ -1373,7 +1373,7 @@ impl AcpThreadView { } AcpThreadEvent::PromptCapabilitiesUpdated => { self.prompt_capabilities - .set(thread.read(cx).prompt_capabilities()); + .replace(thread.read(cx).prompt_capabilities()); } AcpThreadEvent::TokenUsageUpdated => {} AcpThreadEvent::AvailableCommandsUpdated(available_commands) => { @@ -1390,11 +1390,13 @@ impl AcpThreadView { name: "login".to_owned(), description: "Authenticate".to_owned(), input: None, + meta: None, }); available_commands.push(acp::AvailableCommand { name: "logout".to_owned(), description: "Authenticate".to_owned(), input: None, + meta: None, }); } @@ -5722,6 +5724,7 @@ pub(crate) mod tests { locations: vec![], raw_input: None, raw_output: None, + meta: None, }; let connection = StubAgentConnection::new().with_permission_requests(HashMap::from_iter([( @@ -5730,6 +5733,7 @@ pub(crate) mod tests { id: acp::PermissionOptionId("1".into()), name: "Allow".into(), kind: acp::PermissionOptionKind::AllowOnce, + meta: None, }], )])); @@ -5906,6 +5910,7 @@ pub(crate) mod tests { image: true, audio: true, embedded_context: true, + meta: None, }), cx, ) @@ -5965,6 +5970,7 @@ pub(crate) mod tests { image: true, audio: true, embedded_context: true, + meta: None, }), cx, ) @@ -5991,6 +5997,7 @@ pub(crate) mod tests { ) -> Task> { Task::ready(Ok(acp::PromptResponse { stop_reason: acp::StopReason::Refusal, + meta: None, })) } @@ -6074,11 +6081,13 @@ pub(crate) mod tests { path: "/project/test1.txt".into(), old_text: Some("old content 1".into()), new_text: "new content 1".into(), + meta: None, }, }], locations: vec![], raw_input: None, raw_output: None, + meta: None, })]); thread @@ -6115,11 +6124,13 @@ pub(crate) mod tests { path: "/project/test2.txt".into(), old_text: Some("old content 2".into()), new_text: "new content 2".into(), + meta: None, }, }], locations: vec![], raw_input: None, raw_output: None, + meta: None, })]); thread @@ -6197,6 +6208,7 @@ pub(crate) mod tests { content: acp::ContentBlock::Text(acp::TextContent { text: "Response".into(), annotations: None, + meta: None, }), }]); @@ -6286,6 +6298,7 @@ pub(crate) mod tests { content: acp::ContentBlock::Text(acp::TextContent { text: "Response".into(), annotations: None, + meta: None, }), }]); @@ -6329,6 +6342,7 @@ pub(crate) mod tests { content: acp::ContentBlock::Text(acp::TextContent { text: "New Response".into(), annotations: None, + meta: None, }), }]); @@ -6421,6 +6435,7 @@ pub(crate) mod tests { content: acp::ContentBlock::Text(acp::TextContent { text: "Response".into(), annotations: None, + meta: None, }), }, cx,