acp: Update to agent-client-protocol rust sdk v0.9.0 (#44373)

Ben Brandt created

Release Notes:

- N/A

Change summary

Cargo.lock                                | 11 ++--
Cargo.toml                                |  2 
crates/acp_thread/src/acp_thread.rs       |  2 
crates/acp_thread/src/terminal.rs         | 26 ++++--------
crates/agent/src/tests/mod.rs             |  2 
crates/agent/src/thread.rs                | 53 +++++++++++++-----------
crates/agent/src/tools/edit_file_tool.rs  |  6 --
crates/agent/src/tools/find_path_tool.rs  |  2 
crates/agent/src/tools/read_file_tool.rs  |  9 +--
crates/agent/src/tools/web_search_tool.rs |  2 
crates/agent_servers/src/acp.rs           | 36 +++++++++-------
crates/agent_ui/src/acp/message_editor.rs | 16 ++----
crates/agent_ui/src/acp/thread_view.rs    |  4 
13 files changed, 81 insertions(+), 90 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -216,9 +216,9 @@ dependencies = [
 
 [[package]]
 name = "agent-client-protocol"
-version = "0.8.0"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e639d6b544ad39f5b4e05802db5eb04e1518284eb05fda1839931003e0244c8"
+checksum = "c2ffe7d502c1e451aafc5aff655000f84d09c9af681354ac0012527009b1af13"
 dependencies = [
  "agent-client-protocol-schema",
  "anyhow",
@@ -233,15 +233,16 @@ dependencies = [
 
 [[package]]
 name = "agent-client-protocol-schema"
-version = "0.9.1"
+version = "0.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f182f5e14bef8232b239719bd99166bb11e986c08fc211f28e392f880d3093ba"
+checksum = "8af81cc2d5c3f9c04f73db452efd058333735ba9d51c2cf7ef33c9fee038e7e6"
 dependencies = [
  "anyhow",
  "derive_more 2.0.1",
  "schemars",
  "serde",
  "serde_json",
+ "strum 0.27.2",
 ]
 
 [[package]]
@@ -8147,7 +8148,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
 dependencies = [
  "equivalent",
- "hashbrown 0.15.5",
+ "hashbrown 0.16.1",
  "serde",
  "serde_core",
 ]

Cargo.toml 🔗

@@ -443,7 +443,7 @@ ztracing_macro = { path = "crates/ztracing_macro" }
 # External crates
 #
 
-agent-client-protocol = { version = "=0.8.0", features = ["unstable"] }
+agent-client-protocol = { version = "=0.9.0", features = ["unstable"] }
 aho-corasick = "1.1"
 alacritty_terminal = "0.25.1-rc1"
 any_vec = "0.14"

crates/acp_thread/src/acp_thread.rs 🔗

@@ -2929,7 +2929,7 @@ mod tests {
             .await
             .unwrap_err();
 
-        assert_eq!(err.code, acp::ErrorCode::RESOURCE_NOT_FOUND.code);
+        assert_eq!(err.code, acp::ErrorCode::ResourceNotFound);
     }
 
     #[gpui::test]

crates/acp_thread/src/terminal.rs 🔗

@@ -75,15 +75,9 @@ impl Terminal {
 
                     let exit_status = exit_status.map(portable_pty::ExitStatus::from);
 
-                    let mut status = acp::TerminalExitStatus::new();
-
-                    if let Some(exit_status) = exit_status.as_ref() {
-                        status = status.exit_code(exit_status.exit_code());
-                        if let Some(signal) = exit_status.signal() {
-                            status = status.signal(signal);
-                        }
-                    }
-                    status
+                    acp::TerminalExitStatus::new()
+                        .exit_code(exit_status.as_ref().map(|e| e.exit_code()))
+                        .signal(exit_status.and_then(|e| e.signal().map(ToOwned::to_owned)))
                 })
                 .shared(),
         }
@@ -105,19 +99,17 @@ impl Terminal {
 
     pub fn current_output(&self, cx: &App) -> acp::TerminalOutputResponse {
         if let Some(output) = self.output.as_ref() {
-            let mut exit_status = acp::TerminalExitStatus::new();
-            if let Some(status) = output.exit_status.map(portable_pty::ExitStatus::from) {
-                exit_status = exit_status.exit_code(status.exit_code());
-                if let Some(signal) = status.signal() {
-                    exit_status = exit_status.signal(signal);
-                }
-            }
+            let exit_status = output.exit_status.map(portable_pty::ExitStatus::from);
 
             acp::TerminalOutputResponse::new(
                 output.content.clone(),
                 output.original_content_len > output.content.len(),
             )
-            .exit_status(exit_status)
+            .exit_status(
+                acp::TerminalExitStatus::new()
+                    .exit_code(exit_status.as_ref().map(|e| e.exit_code()))
+                    .signal(exit_status.and_then(|e| e.signal().map(ToOwned::to_owned))),
+            )
         } else {
             let (current_content, original_len) = self.truncated_output(cx);
             let truncated = current_content.len() < original_len;

crates/agent/src/tests/mod.rs 🔗

@@ -2094,7 +2094,7 @@ async fn test_tool_updates_to_completion(cx: &mut TestAppContext) {
             "1",
             acp::ToolCallUpdateFields::new()
                 .status(acp::ToolCallStatus::Completed)
-                .raw_output("Finished thinking.".into())
+                .raw_output("Finished thinking.")
         )
     );
 }

crates/agent/src/thread.rs 🔗

@@ -766,20 +766,22 @@ impl Thread {
                 .log_err();
         }
 
-        let mut fields = acp::ToolCallUpdateFields::new().status(tool_result.as_ref().map_or(
-            acp::ToolCallStatus::Failed,
-            |result| {
-                if result.is_error {
-                    acp::ToolCallStatus::Failed
-                } else {
-                    acp::ToolCallStatus::Completed
-                }
-            },
-        ));
-        if let Some(output) = output {
-            fields = fields.raw_output(output);
-        }
-        stream.update_tool_call_fields(&tool_use.id, fields);
+        stream.update_tool_call_fields(
+            &tool_use.id,
+            acp::ToolCallUpdateFields::new()
+                .status(
+                    tool_result
+                        .as_ref()
+                        .map_or(acp::ToolCallStatus::Failed, |result| {
+                            if result.is_error {
+                                acp::ToolCallStatus::Failed
+                            } else {
+                                acp::ToolCallStatus::Completed
+                            }
+                        }),
+                )
+                .raw_output(output),
+        );
     }
 
     pub fn from_db(
@@ -1259,15 +1261,16 @@ impl Thread {
             while let Some(tool_result) = tool_results.next().await {
                 log::debug!("Tool finished {:?}", tool_result);
 
-                let mut fields = acp::ToolCallUpdateFields::new().status(if tool_result.is_error {
-                    acp::ToolCallStatus::Failed
-                } else {
-                    acp::ToolCallStatus::Completed
-                });
-                if let Some(output) = &tool_result.output {
-                    fields = fields.raw_output(output.clone());
-                }
-                event_stream.update_tool_call_fields(&tool_result.tool_use_id, fields);
+                event_stream.update_tool_call_fields(
+                    &tool_result.tool_use_id,
+                    acp::ToolCallUpdateFields::new()
+                        .status(if tool_result.is_error {
+                            acp::ToolCallStatus::Failed
+                        } else {
+                            acp::ToolCallStatus::Completed
+                        })
+                        .raw_output(tool_result.output.clone()),
+                );
                 this.update(cx, |this, _cx| {
                     this.pending_message()
                         .tool_results
@@ -1545,7 +1548,7 @@ impl Thread {
             event_stream.update_tool_call_fields(
                 &tool_use.id,
                 acp::ToolCallUpdateFields::new()
-                    .title(title)
+                    .title(title.as_str())
                     .kind(kind)
                     .raw_input(tool_use.input.clone()),
             );
@@ -2461,7 +2464,7 @@ impl ToolCallEventStream {
                 ToolCallAuthorization {
                     tool_call: acp::ToolCallUpdate::new(
                         self.tool_use_id.to_string(),
-                        acp::ToolCallUpdateFields::new().title(title),
+                        acp::ToolCallUpdateFields::new().title(title.into()),
                     ),
                     options: vec![
                         acp::PermissionOption::new(

crates/agent/src/tools/edit_file_tool.rs 🔗

@@ -384,11 +384,7 @@ impl AgentTool for EditFileTool {
                                 range.start.to_point(&buffer.snapshot()).row
                             }).ok();
                             if let Some(abs_path) = abs_path.clone() {
-                                let mut location = ToolCallLocation::new(abs_path);
-                                if let Some(line) = line {
-                                    location = location.line(line);
-                                }
-                                event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![location]));
+                                event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![ToolCallLocation::new(abs_path).line(line)]));
                             }
                             emitted_location = true;
                         }

crates/agent/src/tools/read_file_tool.rs 🔗

@@ -152,12 +152,11 @@ impl AgentTool for ReadFileTool {
         }
 
         let file_path = input.path.clone();
-        let mut location = acp::ToolCallLocation::new(&abs_path);
-        if let Some(line) = input.start_line {
-            location = location.line(line.saturating_sub(1));
-        }
 
-        event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![location]));
+        event_stream.update_fields(ToolCallUpdateFields::new().locations(vec![
+                acp::ToolCallLocation::new(&abs_path)
+                    .line(input.start_line.map(|line| line.saturating_sub(1))),
+            ]));
 
         if image_store::is_image_file(&self.project, &project_path, cx) {
             return cx.spawn(async move |cx| {

crates/agent_servers/src/acp.rs 🔗

@@ -173,10 +173,6 @@ impl AcpConnection {
             });
         })?;
 
-        let mut client_info = acp::Implementation::new("zed", version);
-        if let Some(release_channel) = release_channel {
-            client_info = client_info.title(release_channel);
-        }
         let response = connection
             .initialize(
                 acp::InitializeRequest::new(acp::ProtocolVersion::V1)
@@ -192,7 +188,10 @@ impl AcpConnection {
                                 ("terminal-auth".into(), true.into()),
                             ])),
                     )
-                    .client_info(client_info),
+                    .client_info(
+                        acp::Implementation::new("zed", version)
+                            .title(release_channel.map(ToOwned::to_owned)),
+                    ),
             )
             .await?;
 
@@ -302,10 +301,10 @@ impl AgentConnection for AcpConnection {
                 .new_session(acp::NewSessionRequest::new(cwd).mcp_servers(mcp_servers))
                 .await
                 .map_err(|err| {
-                    if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
+                    if err.code == acp::ErrorCode::AuthRequired {
                         let mut error = AuthRequired::new();
 
-                        if err.message != acp::ErrorCode::AUTH_REQUIRED.message {
+                        if err.message != acp::ErrorCode::AuthRequired.to_string() {
                             error = error.with_description(err.message);
                         }
 
@@ -467,11 +466,11 @@ impl AgentConnection for AcpConnection {
             match result {
                 Ok(response) => Ok(response),
                 Err(err) => {
-                    if err.code == acp::ErrorCode::AUTH_REQUIRED.code {
+                    if err.code == acp::ErrorCode::AuthRequired {
                         return Err(anyhow!(acp::Error::auth_required()));
                     }
 
-                    if err.code != ErrorCode::INTERNAL_ERROR.code {
+                    if err.code != ErrorCode::InternalError {
                         anyhow::bail!(err)
                     }
 
@@ -838,13 +837,18 @@ impl acp::Client for ClientDelegate {
                 if let Some(term_exit) = meta.get("terminal_exit") {
                     if let Some(id_str) = term_exit.get("terminal_id").and_then(|v| v.as_str()) {
                         let terminal_id = acp::TerminalId::new(id_str);
-                        let mut status = acp::TerminalExitStatus::new();
-                        if let Some(code) = term_exit.get("exit_code").and_then(|v| v.as_u64()) {
-                            status = status.exit_code(code as u32)
-                        }
-                        if let Some(signal) = term_exit.get("signal").and_then(|v| v.as_str()) {
-                            status = status.signal(signal);
-                        }
+                        let status = acp::TerminalExitStatus::new()
+                            .exit_code(
+                                term_exit
+                                    .get("exit_code")
+                                    .and_then(|v| v.as_u64())
+                                    .map(|i| i as u32),
+                            )
+                            .signal(
+                                term_exit
+                                    .get("signal")
+                                    .and_then(|v| v.as_str().map(|s| s.to_string())),
+                            );
 
                         let _ = session.thread.update(&mut self.cx.clone(), |thread, cx| {
                             thread.on_terminal_provider_event(

crates/agent_ui/src/acp/message_editor.rs 🔗

@@ -417,13 +417,12 @@ impl MessageEditor {
                                     ))
                                 }
                             }
-                            Mention::Image(mention_image) => {
-                                let mut image = acp::ImageContent::new(
+                            Mention::Image(mention_image) => acp::ContentBlock::Image(
+                                acp::ImageContent::new(
                                     mention_image.data.clone(),
                                     mention_image.format.mime_type(),
-                                );
-
-                                if let Some(uri) = match uri {
+                                )
+                                .uri(match uri {
                                     MentionUri::File { .. } => Some(uri.to_uri().to_string()),
                                     MentionUri::PastedImage => None,
                                     other => {
@@ -433,11 +432,8 @@ impl MessageEditor {
                                         );
                                         None
                                     }
-                                } {
-                                    image = image.uri(uri)
-                                };
-                                acp::ContentBlock::Image(image)
-                            }
+                                }),
+                            ),
                             Mention::Link => acp::ContentBlock::ResourceLink(
                                 acp::ResourceLink::new(uri.name(), uri.to_uri().to_string()),
                             ),

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -100,7 +100,7 @@ impl ThreadError {
         {
             Self::ModelRequestLimitReached(error.plan)
         } else if let Some(acp_error) = error.downcast_ref::<acp::Error>()
-            && acp_error.code == acp::ErrorCode::AUTH_REQUIRED.code
+            && acp_error.code == acp::ErrorCode::AuthRequired
         {
             Self::AuthenticationRequired(acp_error.message.clone().into())
         } else {
@@ -6243,7 +6243,7 @@ pub(crate) mod tests {
             StubAgentConnection::new().with_permission_requests(HashMap::from_iter([(
                 tool_call_id,
                 vec![acp::PermissionOption::new(
-                    "1".into(),
+                    "1",
                     "Allow",
                     acp::PermissionOptionKind::AllowOnce,
                 )],