Update acp to 0.2.1 (#38068)

Agus Zubiaga created

Release Notes:

- N/A

Change summary

Cargo.lock                                     |  4 
Cargo.toml                                     |  2 
crates/acp_thread/src/acp_thread.rs            | 33 +++++++
crates/acp_thread/src/connection.rs            |  7 +
crates/acp_thread/src/terminal.rs              |  4 +
crates/agent2/src/agent.rs                     |  9 ++
crates/agent2/src/tests/mod.rs                 |  7 +
crates/agent2/src/thread.rs                    | 12 +++
crates/agent2/src/tools/edit_file_tool.rs      |  3 
crates/agent2/src/tools/find_path_tool.rs      |  1 
crates/agent2/src/tools/read_file_tool.rs      |  1 
crates/agent2/src/tools/web_search_tool.rs     |  1 
crates/agent_servers/src/acp.rs                | 76 +++++++++++++++----
crates/agent_servers/src/e2e_tests.rs          |  3 
crates/agent_ui/src/acp/completion_provider.rs | 14 +-
crates/agent_ui/src/acp/entry_view_state.rs    | 12 +-
crates/agent_ui/src/acp/message_editor.rs      | 41 ++++++----
crates/agent_ui/src/acp/thread_view.rs         | 25 +++++-
18 files changed, 191 insertions(+), 64 deletions(-)

Detailed changes

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",

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"

crates/acp_thread/src/acp_thread.rs 🔗

@@ -862,7 +862,7 @@ impl AcpThread {
         mut prompt_capabilities_rx: watch::Receiver<acp::PromptCapabilities>,
         cx: &mut Context<Self>,
     ) -> 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<dyn AgentConnection> {
@@ -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,
                 }))
             }
         }

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,
                     })
                 })
             }

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,
             }
         }
     }

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(),
                 ],

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,
         }
     );
 }

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<UserMessageContent> 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(),

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()
                                 });
                             }

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()
         });

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(),

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<Result<()>> {
         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<acp::WriteTextFileResponse, acp::Error> {
         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<acp::KillTerminalCommandResponse, acp::Error> {
         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<str>,
+        _params: Arc<serde_json::value::RawValue>,
+    ) -> Result<Arc<serde_json::value::RawValue>, acp::Error> {
+        Err(acp::Error::method_not_found())
+    }
+
+    async fn ext_notification(
+        &self,
+        _name: Arc<str>,
+        _params: Arc<serde_json::value::RawValue>,
+    ) -> Result<(), acp::Error> {
+        Err(acp::Error::method_not_found())
+    }
+
+    async fn release_terminal(
+        &self,
+        args: acp::ReleaseTerminalRequest,
+    ) -> Result<acp::ReleaseTerminalResponse, acp::Error> {
         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,
+        })
     }
 }
 

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,

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<Workspace>,
     history_store: Entity<HistoryStore>,
     prompt_store: Option<Entity<PromptStore>>,
-    prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
+    prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
     available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
 }
 
@@ -78,7 +78,7 @@ impl ContextPickerCompletionProvider {
         workspace: WeakEntity<Workspace>,
         history_store: Entity<HistoryStore>,
         prompt_store: Option<Entity<PromptStore>>,
-        prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
+        prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
         available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
     ) -> 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<Workspace>,
         cx: &mut App,
     ) -> Vec<ContextPickerEntry> {
-        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

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<HistoryStore>,
     prompt_store: Option<Entity<PromptStore>>,
     entries: Vec<Entry>,
-    prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
+    prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
     available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
     agent_name: SharedString,
 }
@@ -41,7 +37,7 @@ impl EntryViewState {
         project: Entity<Project>,
         history_store: Entity<HistoryStore>,
         prompt_store: Option<Entity<PromptStore>>,
-        prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
+        prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
         available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
         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

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<Workspace>,
     history_store: Entity<HistoryStore>,
     prompt_store: Option<Entity<PromptStore>>,
-    prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
+    prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
     available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
     agent_name: SharedString,
     _subscriptions: Vec<Subscription>,
@@ -89,7 +89,7 @@ impl MessageEditor {
         project: Entity<Project>,
         history_store: Entity<HistoryStore>,
         prompt_store: Option<Entity<PromptStore>>,
-        prompt_capabilities: Rc<Cell<acp::PromptCapabilities>>,
+        prompt_capabilities: Rc<RefCell<acp::PromptCapabilities>>,
         available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
         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<Self>) {
-        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: "<name>".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

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<usize>,
-    prompt_capabilities: Rc<Cell<PromptCapabilities>>,
+    prompt_capabilities: Rc<RefCell<PromptCapabilities>>,
     available_commands: Rc<RefCell<Vec<acp::AvailableCommand>>>,
     is_loading_contents: bool,
     new_server_version_available: Option<SharedString>,
@@ -334,7 +334,7 @@ impl AcpThreadView {
         window: &mut Window,
         cx: &mut Context<Self>,
     ) -> 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<gpui::Result<acp::PromptResponse>> {
             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,