From 7cb2d836088a1c55c51eff38774c901cfc0e1f73 Mon Sep 17 00:00:00 2001 From: Ben Brandt Date: Mon, 27 Oct 2025 11:05:50 +0100 Subject: [PATCH] acp: Start sending Client Info to the Agent (#41265) Updates to acp crate 0.7, which allows us to send information about the client to the Agent. In the future, we can also use the AgentInfo on the response for internal metrics. Release Notes: - N/A --- Cargo.lock | 9 ++- Cargo.toml | 2 +- crates/acp_thread/src/acp_thread.rs | 40 ++++++---- crates/agent_servers/Cargo.toml | 1 + crates/agent_servers/src/acp.rs | 19 ++++- crates/agent_ui/src/acp/thread_view.rs | 104 +++++++++++++++---------- 6 files changed, 111 insertions(+), 64 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a4371765847..f3f558c32ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -210,9 +210,9 @@ dependencies = [ [[package]] name = "agent-client-protocol" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f655394a107cd601bd2e5375c2d909ea83adc65678a0e0e8d77613d3c848a7d" +checksum = "525705e39c11cd73f7bc784e3681a9386aa30c8d0630808d3dc2237eb4f9cb1b" dependencies = [ "agent-client-protocol-schema", "anyhow", @@ -228,9 +228,9 @@ dependencies = [ [[package]] name = "agent-client-protocol-schema" -version = "0.4.11" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61be4454304d7df1a5b44c4ae55e707ffe72eac4dfb1ef8762510ce8d8f6d924" +checksum = "ecf16c18fea41282d6bbadd1549a06be6836bddb1893f44a6235f340fa24e2af" dependencies = [ "anyhow", "derive_more 2.0.1", @@ -266,6 +266,7 @@ dependencies = [ "log", "nix 0.29.0", "project", + "release_channel", "reqwest_client", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 8e9ab428c31..d69d768f33e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -438,7 +438,7 @@ zlog_settings = { path = "crates/zlog_settings" } # External crates # -agent-client-protocol = { version = "0.5.0", features = ["unstable"] } +agent-client-protocol = { version = "0.7.0", features = ["unstable"] } aho-corasick = "1.1" alacritty_terminal = "0.25.1-rc1" any_vec = "0.14" diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 4d8c57dd8f5..99c62201fa0 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -1105,13 +1105,13 @@ impl AcpThread { cx: &mut Context, ) -> Result<(), acp::Error> { match update { - acp::SessionUpdate::UserMessageChunk { content } => { + acp::SessionUpdate::UserMessageChunk(acp::ContentChunk { content, .. }) => { self.push_user_content_block(None, content, cx); } - acp::SessionUpdate::AgentMessageChunk { content } => { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content, .. }) => { self.push_assistant_content_block(content, false, cx); } - acp::SessionUpdate::AgentThoughtChunk { content } => { + acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk { content, .. }) => { self.push_assistant_content_block(content, true, cx); } acp::SessionUpdate::ToolCall(tool_call) => { @@ -1123,12 +1123,14 @@ impl AcpThread { acp::SessionUpdate::Plan(plan) => { self.update_plan(plan, cx); } - acp::SessionUpdate::AvailableCommandsUpdate { available_commands } => { - cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands)) - } - acp::SessionUpdate::CurrentModeUpdate { current_mode_id } => { - cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id)) - } + acp::SessionUpdate::AvailableCommandsUpdate(acp::AvailableCommandsUpdate { + available_commands, + .. + }) => cx.emit(AcpThreadEvent::AvailableCommandsUpdated(available_commands)), + acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate { + current_mode_id, + .. + }) => cx.emit(AcpThreadEvent::ModeUpdated(current_mode_id)), } Ok(()) } @@ -2586,17 +2588,19 @@ mod tests { thread.update(&mut cx, |thread, cx| { thread .handle_session_update( - acp::SessionUpdate::AgentThoughtChunk { + acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk { content: "Thinking ".into(), - }, + meta: None, + }), cx, ) .unwrap(); thread .handle_session_update( - acp::SessionUpdate::AgentThoughtChunk { + acp::SessionUpdate::AgentThoughtChunk(acp::ContentChunk { content: "hard!".into(), - }, + meta: None, + }), cx, ) .unwrap(); @@ -3095,9 +3099,10 @@ mod tests { thread.update(&mut cx, |thread, cx| { thread .handle_session_update( - acp::SessionUpdate::AgentMessageChunk { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content: content.text.to_uppercase().into(), - }, + meta: None, + }), cx, ) .unwrap(); @@ -3454,9 +3459,10 @@ mod tests { thread.update(&mut cx, |thread, cx| { thread .handle_session_update( - acp::SessionUpdate::AgentMessageChunk { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content: content.text.to_uppercase().into(), - }, + meta: None, + }), cx, ) .unwrap(); diff --git a/crates/agent_servers/Cargo.toml b/crates/agent_servers/Cargo.toml index fcdba2301ee..d427290736f 100644 --- a/crates/agent_servers/Cargo.toml +++ b/crates/agent_servers/Cargo.toml @@ -38,6 +38,7 @@ language_model.workspace = true language_models.workspace = true log.workspace = true project.workspace = true +release_channel.workspace = true reqwest_client = { workspace = true, optional = true } serde.workspace = true serde_json.workspace = true diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index 6f92b958b2d..3a1d5c9fa84 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -105,6 +105,14 @@ impl AcpConnection { let sessions = Rc::new(RefCell::new(HashMap::default())); + let (release_channel, version) = cx.update(|cx| { + ( + release_channel::ReleaseChannel::try_global(cx) + .map(|release_channel| release_channel.display_name()), + release_channel::AppVersion::global(cx).to_string(), + ) + })?; + let client = ClientDelegate { sessions: sessions.clone(), cx: cx.clone(), @@ -172,6 +180,11 @@ impl AcpConnection { "terminal_output": true, })), }, + client_info: Some(acp::Implementation { + name: "zed".to_owned(), + title: release_channel.map(|c| c.to_owned()), + version, + }), meta: None, }) .await?; @@ -700,7 +713,11 @@ impl acp::Client for ClientDelegate { .get(¬ification.session_id) .context("Failed to get session")?; - if let acp::SessionUpdate::CurrentModeUpdate { current_mode_id } = ¬ification.update { + if let acp::SessionUpdate::CurrentModeUpdate(acp::CurrentModeUpdate { + current_mode_id, + .. + }) = ¬ification.update + { if let Some(session_modes) = &session.session_modes { session_modes.borrow_mut().current_mode_id = current_mode_id.clone(); } else { diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index 8e5396590fe..7e5d5b48a13 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -5981,9 +5981,12 @@ pub(crate) mod tests { impl StubAgentServer { fn default_response() -> Self { let conn = StubAgentConnection::new(); - conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { - content: "Default response".into(), - }]); + conn.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk { + content: "Default response".into(), + meta: None, + }, + )]); Self::new(conn) } } @@ -6334,13 +6337,16 @@ pub(crate) mod tests { let connection = StubAgentConnection::new(); - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { - content: acp::ContentBlock::Text(acp::TextContent { - text: "Response".into(), - annotations: None, + connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk { + content: acp::ContentBlock::Text(acp::TextContent { + text: "Response".into(), + annotations: None, + meta: None, + }), meta: None, - }), - }]); + }, + )]); let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await; add_to_workspace(thread_view.clone(), cx); @@ -6424,13 +6430,16 @@ pub(crate) mod tests { let connection = StubAgentConnection::new(); - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { - content: acp::ContentBlock::Text(acp::TextContent { - text: "Response".into(), - annotations: None, + connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk { + content: acp::ContentBlock::Text(acp::TextContent { + text: "Response".into(), + annotations: None, + meta: None, + }), meta: None, - }), - }]); + }, + )]); let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection.clone()), cx).await; @@ -6468,13 +6477,16 @@ pub(crate) mod tests { }); // Send - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { - content: acp::ContentBlock::Text(acp::TextContent { - text: "New Response".into(), - annotations: None, + connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk { + content: acp::ContentBlock::Text(acp::TextContent { + text: "New Response".into(), + annotations: None, + meta: None, + }), meta: None, - }), - }]); + }, + )]); user_message_editor.update_in(cx, |_editor, window, cx| { window.dispatch_action(Box::new(Chat), cx); @@ -6561,13 +6573,14 @@ pub(crate) mod tests { cx.update(|_, cx| { connection.send_update( session_id.clone(), - acp::SessionUpdate::AgentMessageChunk { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content: acp::ContentBlock::Text(acp::TextContent { text: "Response".into(), annotations: None, meta: None, }), - }, + meta: None, + }), cx, ); connection.end_turn(session_id, acp::StopReason::EndTurn); @@ -6619,9 +6632,10 @@ pub(crate) mod tests { cx.update(|_, cx| { connection.send_update( session_id.clone(), - acp::SessionUpdate::AgentMessageChunk { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content: "Message 1 resp".into(), - }, + meta: None, + }), cx, ); }); @@ -6655,9 +6669,10 @@ pub(crate) mod tests { // Simulate a response sent after beginning to cancel connection.send_update( session_id.clone(), - acp::SessionUpdate::AgentMessageChunk { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content: "onse".into(), - }, + meta: None, + }), cx, ); }); @@ -6688,9 +6703,10 @@ pub(crate) mod tests { cx.update(|_, cx| { connection.send_update( session_id.clone(), - acp::SessionUpdate::AgentMessageChunk { + acp::SessionUpdate::AgentMessageChunk(acp::ContentChunk { content: "Message 2 response".into(), - }, + meta: None, + }), cx, ); connection.end_turn(session_id.clone(), acp::StopReason::EndTurn); @@ -6728,13 +6744,16 @@ pub(crate) mod tests { init_test(cx); let connection = StubAgentConnection::new(); - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { - content: acp::ContentBlock::Text(acp::TextContent { - text: "Response".into(), - annotations: None, + connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk { + content: acp::ContentBlock::Text(acp::TextContent { + text: "Response".into(), + annotations: None, + meta: None, + }), meta: None, - }), - }]); + }, + )]); let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await; add_to_workspace(thread_view.clone(), cx); @@ -6811,13 +6830,16 @@ pub(crate) mod tests { init_test(cx); let connection = StubAgentConnection::new(); - connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk { - content: acp::ContentBlock::Text(acp::TextContent { - text: "Response".into(), - annotations: None, + connection.set_next_prompt_updates(vec![acp::SessionUpdate::AgentMessageChunk( + acp::ContentChunk { + content: acp::ContentBlock::Text(acp::TextContent { + text: "Response".into(), + annotations: None, + meta: None, + }), meta: None, - }), - }]); + }, + )]); let (thread_view, cx) = setup_thread_view(StubAgentServer::new(connection), cx).await; add_to_workspace(thread_view.clone(), cx);