From c2416d6bab7e693078f1d5f8697601a0bfe7ba20 Mon Sep 17 00:00:00 2001 From: Bennet Bo Fenner Date: Fri, 7 Nov 2025 11:07:57 +0100 Subject: [PATCH] Add agent metrics (#41991) Release Notes: - N/A --- Cargo.lock | 2 + crates/acp_thread/Cargo.toml | 1 + crates/acp_thread/src/acp_thread.rs | 32 ++- crates/acp_thread/src/connection.rs | 9 +- crates/action_log/Cargo.toml | 1 + crates/action_log/src/action_log.rs | 149 +++++++++++--- crates/agent/src/agent.rs | 8 +- crates/agent_servers/src/acp.rs | 9 + crates/agent_servers/src/claude.rs | 2 + crates/agent_servers/src/codex.rs | 2 + crates/agent_servers/src/custom.rs | 2 + crates/agent_servers/src/gemini.rs | 2 + .../src/acp/model_selector_popover.rs | 10 +- crates/agent_ui/src/acp/thread_view.rs | 92 ++++++--- crates/agent_ui/src/agent_diff.rs | 187 +++++++----------- crates/telemetry/src/telemetry.rs | 7 +- 16 files changed, 327 insertions(+), 188 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a3a8fed78dcaae90eae9b026d2968eaeb2f8aad2..c78d59a479636c40758393356de6c6c089dc1aef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -32,6 +32,7 @@ dependencies = [ "settings", "smol", "task", + "telemetry", "tempfile", "terminal", "ui", @@ -80,6 +81,7 @@ dependencies = [ "rand 0.9.2", "serde_json", "settings", + "telemetry", "text", "util", "watch", diff --git a/crates/acp_thread/Cargo.toml b/crates/acp_thread/Cargo.toml index 4030dd89c5497c3fdd4af06d725aed8755da3cf5..8ef6f1a52c8b207658d59a1e6b877964df9e42ce 100644 --- a/crates/acp_thread/Cargo.toml +++ b/crates/acp_thread/Cargo.toml @@ -39,6 +39,7 @@ serde_json.workspace = true settings.workspace = true smol.workspace = true task.workspace = true +telemetry.workspace = true terminal.workspace = true ui.workspace = true url.workspace = true diff --git a/crates/acp_thread/src/acp_thread.rs b/crates/acp_thread/src/acp_thread.rs index 37622d004a2e9cd27a3686263ffd1aa98979104f..ab534f691a657454daa97c11c625d0baef694809 100644 --- a/crates/acp_thread/src/acp_thread.rs +++ b/crates/acp_thread/src/acp_thread.rs @@ -15,7 +15,7 @@ use settings::Settings as _; use task::{Shell, ShellBuilder}; pub use terminal::*; -use action_log::ActionLog; +use action_log::{ActionLog, ActionLogTelemetry}; use agent_client_protocol::{self as acp}; use anyhow::{Context as _, Result, anyhow}; use editor::Bias; @@ -820,6 +820,15 @@ pub struct AcpThread { pending_terminal_exit: HashMap, } +impl From<&AcpThread> for ActionLogTelemetry { + fn from(value: &AcpThread) -> Self { + Self { + agent_telemetry_id: value.connection().telemetry_id(), + session_id: value.session_id.0.clone(), + } + } +} + #[derive(Debug)] pub enum AcpThreadEvent { NewEntry, @@ -1346,6 +1355,17 @@ impl AcpThread { let path_style = self.project.read(cx).path_style(cx); let id = update.id.clone(); + let agent = self.connection().telemetry_id(); + let session = self.session_id(); + if let ToolCallStatus::Completed | ToolCallStatus::Failed = status { + let status = if matches!(status, ToolCallStatus::Completed) { + "completed" + } else { + "failed" + }; + telemetry::event!("Agent Tool Call Completed", agent, session, status); + } + if let Some(ix) = self.index_for_tool_call(&id) { let AgentThreadEntry::ToolCall(call) = &mut self.entries[ix] else { unreachable!() @@ -1869,6 +1889,7 @@ impl AcpThread { return Task::ready(Err(anyhow!("not supported"))); }; + let telemetry = ActionLogTelemetry::from(&*self); cx.spawn(async move |this, cx| { cx.update(|cx| truncate.run(id.clone(), cx))?.await?; this.update(cx, |this, cx| { @@ -1877,8 +1898,9 @@ impl AcpThread { this.entries.truncate(ix); cx.emit(AcpThreadEvent::EntriesRemoved(range)); } - this.action_log() - .update(cx, |action_log, cx| action_log.reject_all_edits(cx)) + this.action_log().update(cx, |action_log, cx| { + action_log.reject_all_edits(Some(telemetry), cx) + }) })? .await; Ok(()) @@ -3614,6 +3636,10 @@ mod tests { } impl AgentConnection for FakeAgentConnection { + fn telemetry_id(&self) -> &'static str { + "fake" + } + fn auth_methods(&self) -> &[acp::AuthMethod] { &self.auth_methods } diff --git a/crates/acp_thread/src/connection.rs b/crates/acp_thread/src/connection.rs index fe66f954370f8118d054ee56f1e9f68f2de7e6f4..63ca65f22725c54476048542c90f5f5efcfd23ca 100644 --- a/crates/acp_thread/src/connection.rs +++ b/crates/acp_thread/src/connection.rs @@ -20,6 +20,8 @@ impl UserMessageId { } pub trait AgentConnection { + fn telemetry_id(&self) -> &'static str; + fn new_thread( self: Rc, project: Entity, @@ -106,9 +108,6 @@ pub trait AgentSessionSetTitle { } pub trait AgentTelemetry { - /// The name of the agent used for telemetry. - fn agent_name(&self) -> String; - /// A representation of the current thread state that can be serialized for /// storage with telemetry events. fn thread_data( @@ -318,6 +317,10 @@ mod test_support { } impl AgentConnection for StubAgentConnection { + fn telemetry_id(&self) -> &'static str { + "stub" + } + fn auth_methods(&self) -> &[acp::AuthMethod] { &[] } diff --git a/crates/action_log/Cargo.toml b/crates/action_log/Cargo.toml index a8395a943a2ce4e06d4971548c32bf765adb492d..699d5485934bb55ee52639ebab867a67720bdae1 100644 --- a/crates/action_log/Cargo.toml +++ b/crates/action_log/Cargo.toml @@ -20,6 +20,7 @@ futures.workspace = true gpui.workspace = true language.workspace = true project.workspace = true +telemetry.workspace = true text.workspace = true util.workspace = true watch.workspace = true diff --git a/crates/action_log/src/action_log.rs b/crates/action_log/src/action_log.rs index b7722f211afda3a77bc96292a50acf869e7424d6..094c2f4e81ff31f468ca64247a4f6b05fc7d5663 100644 --- a/crates/action_log/src/action_log.rs +++ b/crates/action_log/src/action_log.rs @@ -3,7 +3,9 @@ use buffer_diff::BufferDiff; use clock; use collections::BTreeMap; use futures::{FutureExt, StreamExt, channel::mpsc}; -use gpui::{App, AppContext, AsyncApp, Context, Entity, Subscription, Task, WeakEntity}; +use gpui::{ + App, AppContext, AsyncApp, Context, Entity, SharedString, Subscription, Task, WeakEntity, +}; use language::{Anchor, Buffer, BufferEvent, DiskState, Point, ToPoint}; use project::{Project, ProjectItem, lsp_store::OpenLspBufferHandle}; use std::{cmp, ops::Range, sync::Arc}; @@ -565,14 +567,17 @@ impl ActionLog { &mut self, buffer: Entity, buffer_range: Range, + telemetry: Option, cx: &mut Context, ) { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return; }; + let mut metrics = ActionLogMetrics::for_buffer(buffer.read(cx)); match tracked_buffer.status { TrackedBufferStatus::Deleted => { + metrics.add_edits(tracked_buffer.unreviewed_edits.edits()); self.tracked_buffers.remove(&buffer); cx.notify(); } @@ -581,7 +586,6 @@ impl ActionLog { let buffer_range = buffer_range.start.to_point(buffer)..buffer_range.end.to_point(buffer); let mut delta = 0i32; - tracked_buffer.unreviewed_edits.retain_mut(|edit| { edit.old.start = (edit.old.start as i32 + delta) as u32; edit.old.end = (edit.old.end as i32 + delta) as u32; @@ -613,6 +617,7 @@ impl ActionLog { .collect::(), ); delta += edit.new_len() as i32 - edit.old_len() as i32; + metrics.add_edit(edit); false } }); @@ -624,19 +629,24 @@ impl ActionLog { tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); } } + if let Some(telemetry) = telemetry { + telemetry_report_accepted_edits(&telemetry, metrics); + } } pub fn reject_edits_in_ranges( &mut self, buffer: Entity, buffer_ranges: Vec>, + telemetry: Option, cx: &mut Context, ) -> Task> { let Some(tracked_buffer) = self.tracked_buffers.get_mut(&buffer) else { return Task::ready(Ok(())); }; - match &tracked_buffer.status { + let mut metrics = ActionLogMetrics::for_buffer(buffer.read(cx)); + let task = match &tracked_buffer.status { TrackedBufferStatus::Created { existing_file_content, } => { @@ -686,6 +696,7 @@ impl ActionLog { } }; + metrics.add_edits(tracked_buffer.unreviewed_edits.edits()); self.tracked_buffers.remove(&buffer); cx.notify(); task @@ -699,6 +710,7 @@ impl ActionLog { .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx)); // Clear all tracked edits for this buffer and start over as if we just read it. + metrics.add_edits(tracked_buffer.unreviewed_edits.edits()); self.tracked_buffers.remove(&buffer); self.buffer_read(buffer.clone(), cx); cx.notify(); @@ -738,6 +750,7 @@ impl ActionLog { } if revert { + metrics.add_edit(edit); let old_range = tracked_buffer .diff_base .point_to_offset(Point::new(edit.old.start, 0)) @@ -758,12 +771,25 @@ impl ActionLog { self.project .update(cx, |project, cx| project.save_buffer(buffer, cx)) } + }; + if let Some(telemetry) = telemetry { + telemetry_report_rejected_edits(&telemetry, metrics); } + task } - pub fn keep_all_edits(&mut self, cx: &mut Context) { - self.tracked_buffers - .retain(|_buffer, tracked_buffer| match tracked_buffer.status { + pub fn keep_all_edits( + &mut self, + telemetry: Option, + cx: &mut Context, + ) { + self.tracked_buffers.retain(|buffer, tracked_buffer| { + let mut metrics = ActionLogMetrics::for_buffer(buffer.read(cx)); + metrics.add_edits(tracked_buffer.unreviewed_edits.edits()); + if let Some(telemetry) = telemetry.as_ref() { + telemetry_report_accepted_edits(telemetry, metrics); + } + match tracked_buffer.status { TrackedBufferStatus::Deleted => false, _ => { if let TrackedBufferStatus::Created { .. } = &mut tracked_buffer.status { @@ -774,13 +800,24 @@ impl ActionLog { tracked_buffer.schedule_diff_update(ChangeAuthor::User, cx); true } - }); + } + }); + cx.notify(); } - pub fn reject_all_edits(&mut self, cx: &mut Context) -> Task<()> { + pub fn reject_all_edits( + &mut self, + telemetry: Option, + cx: &mut Context, + ) -> Task<()> { let futures = self.changed_buffers(cx).into_keys().map(|buffer| { - let reject = self.reject_edits_in_ranges(buffer, vec![Anchor::MIN..Anchor::MAX], cx); + let reject = self.reject_edits_in_ranges( + buffer, + vec![Anchor::MIN..Anchor::MAX], + telemetry.clone(), + cx, + ); async move { reject.await.log_err(); @@ -788,8 +825,7 @@ impl ActionLog { }); let task = futures::future::join_all(futures); - - cx.spawn(async move |_, _| { + cx.background_spawn(async move { task.await; }) } @@ -819,6 +855,61 @@ impl ActionLog { } } +#[derive(Clone)] +pub struct ActionLogTelemetry { + pub agent_telemetry_id: &'static str, + pub session_id: Arc, +} + +struct ActionLogMetrics { + lines_removed: u32, + lines_added: u32, + language: Option, +} + +impl ActionLogMetrics { + fn for_buffer(buffer: &Buffer) -> Self { + Self { + language: buffer.language().map(|l| l.name().0), + lines_removed: 0, + lines_added: 0, + } + } + + fn add_edits(&mut self, edits: &[Edit]) { + for edit in edits { + self.add_edit(edit); + } + } + + fn add_edit(&mut self, edit: &Edit) { + self.lines_added += edit.new_len(); + self.lines_removed += edit.old_len(); + } +} + +fn telemetry_report_accepted_edits(telemetry: &ActionLogTelemetry, metrics: ActionLogMetrics) { + telemetry::event!( + "Agent Edits Accepted", + agent = telemetry.agent_telemetry_id, + session = telemetry.session_id, + language = metrics.language, + lines_added = metrics.lines_added, + lines_removed = metrics.lines_removed + ); +} + +fn telemetry_report_rejected_edits(telemetry: &ActionLogTelemetry, metrics: ActionLogMetrics) { + telemetry::event!( + "Agent Edits Rejected", + agent = telemetry.agent_telemetry_id, + session = telemetry.session_id, + language = metrics.language, + lines_added = metrics.lines_added, + lines_removed = metrics.lines_removed + ); +} + fn apply_non_conflicting_edits( patch: &Patch, edits: Vec>, @@ -1066,7 +1157,7 @@ mod tests { ); action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), Point::new(3, 0)..Point::new(4, 3), cx) + log.keep_edits_in_range(buffer.clone(), Point::new(3, 0)..Point::new(4, 3), None, cx) }); cx.run_until_parked(); assert_eq!( @@ -1082,7 +1173,7 @@ mod tests { ); action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(4, 3), cx) + log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(4, 3), None, cx) }); cx.run_until_parked(); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); @@ -1167,7 +1258,7 @@ mod tests { ); action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), Point::new(1, 0)..Point::new(1, 0), cx) + log.keep_edits_in_range(buffer.clone(), Point::new(1, 0)..Point::new(1, 0), None, cx) }); cx.run_until_parked(); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); @@ -1264,7 +1355,7 @@ mod tests { ); action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), cx) + log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), None, cx) }); cx.run_until_parked(); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); @@ -1368,7 +1459,7 @@ mod tests { ); action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), cx) + log.keep_edits_in_range(buffer.clone(), Point::new(0, 0)..Point::new(1, 0), None, cx) }); cx.run_until_parked(); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); @@ -1427,7 +1518,7 @@ mod tests { ); action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), 0..5, cx) + log.keep_edits_in_range(buffer.clone(), 0..5, None, cx) }); cx.run_until_parked(); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); @@ -1479,7 +1570,7 @@ mod tests { action_log .update(cx, |log, cx| { - log.reject_edits_in_ranges(buffer.clone(), vec![2..5], cx) + log.reject_edits_in_ranges(buffer.clone(), vec![2..5], None, cx) }) .await .unwrap(); @@ -1559,7 +1650,7 @@ mod tests { action_log .update(cx, |log, cx| { - log.reject_edits_in_ranges(buffer.clone(), vec![2..5], cx) + log.reject_edits_in_ranges(buffer.clone(), vec![2..5], None, cx) }) .await .unwrap(); @@ -1742,6 +1833,7 @@ mod tests { log.reject_edits_in_ranges( buffer.clone(), vec![Point::new(4, 0)..Point::new(4, 0)], + None, cx, ) }) @@ -1776,6 +1868,7 @@ mod tests { log.reject_edits_in_ranges( buffer.clone(), vec![Point::new(0, 0)..Point::new(1, 0)], + None, cx, ) }) @@ -1803,6 +1896,7 @@ mod tests { log.reject_edits_in_ranges( buffer.clone(), vec![Point::new(4, 0)..Point::new(4, 0)], + None, cx, ) }) @@ -1877,7 +1971,7 @@ mod tests { let range_2 = buffer.read(cx).anchor_before(Point::new(5, 0)) ..buffer.read(cx).anchor_before(Point::new(5, 3)); - log.reject_edits_in_ranges(buffer.clone(), vec![range_1, range_2], cx) + log.reject_edits_in_ranges(buffer.clone(), vec![range_1, range_2], None, cx) .detach(); assert_eq!( buffer.read_with(cx, |buffer, _| buffer.text()), @@ -1938,6 +2032,7 @@ mod tests { log.reject_edits_in_ranges( buffer.clone(), vec![Point::new(0, 0)..Point::new(0, 0)], + None, cx, ) }) @@ -1993,6 +2088,7 @@ mod tests { log.reject_edits_in_ranges( buffer.clone(), vec![Point::new(0, 0)..Point::new(0, 11)], + None, cx, ) }) @@ -2055,6 +2151,7 @@ mod tests { log.reject_edits_in_ranges( buffer.clone(), vec![Point::new(0, 0)..Point::new(100, 0)], + None, cx, ) }) @@ -2102,7 +2199,7 @@ mod tests { // User accepts the single hunk action_log.update(cx, |log, cx| { - log.keep_edits_in_range(buffer.clone(), Anchor::MIN..Anchor::MAX, cx) + log.keep_edits_in_range(buffer.clone(), Anchor::MIN..Anchor::MAX, None, cx) }); cx.run_until_parked(); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); @@ -2123,7 +2220,7 @@ mod tests { // User rejects the hunk action_log .update(cx, |log, cx| { - log.reject_edits_in_ranges(buffer.clone(), vec![Anchor::MIN..Anchor::MAX], cx) + log.reject_edits_in_ranges(buffer.clone(), vec![Anchor::MIN..Anchor::MAX], None, cx) }) .await .unwrap(); @@ -2167,7 +2264,7 @@ mod tests { cx.run_until_parked(); // User clicks "Accept All" - action_log.update(cx, |log, cx| log.keep_all_edits(cx)); + action_log.update(cx, |log, cx| log.keep_all_edits(None, cx)); cx.run_until_parked(); assert!(fs.is_file(path!("/dir/new_file").as_ref()).await); assert_eq!(unreviewed_hunks(&action_log, cx), vec![]); // Hunks are cleared @@ -2186,7 +2283,7 @@ mod tests { // User clicks "Reject All" action_log - .update(cx, |log, cx| log.reject_all_edits(cx)) + .update(cx, |log, cx| log.reject_all_edits(None, cx)) .await; cx.run_until_parked(); assert!(fs.is_file(path!("/dir/new_file").as_ref()).await); @@ -2226,7 +2323,7 @@ mod tests { action_log.update(cx, |log, cx| { let range = buffer.read(cx).random_byte_range(0, &mut rng); log::info!("keeping edits in range {:?}", range); - log.keep_edits_in_range(buffer.clone(), range, cx) + log.keep_edits_in_range(buffer.clone(), range, None, cx) }); } 25..50 => { @@ -2234,7 +2331,7 @@ mod tests { .update(cx, |log, cx| { let range = buffer.read(cx).random_byte_range(0, &mut rng); log::info!("rejecting edits in range {:?}", range); - log.reject_edits_in_ranges(buffer.clone(), vec![range], cx) + log.reject_edits_in_ranges(buffer.clone(), vec![range], None, cx) }) .await .unwrap(); diff --git a/crates/agent/src/agent.rs b/crates/agent/src/agent.rs index 631c1122f85421e8f4f19a7a64efd82da0528162..a9b3c65fa89ae9d9b89180fd776b0868754ab78e 100644 --- a/crates/agent/src/agent.rs +++ b/crates/agent/src/agent.rs @@ -967,6 +967,10 @@ impl acp_thread::AgentModelSelector for NativeAgentModelSelector { } impl acp_thread::AgentConnection for NativeAgentConnection { + fn telemetry_id(&self) -> &'static str { + "zed" + } + fn new_thread( self: Rc, project: Entity, @@ -1107,10 +1111,6 @@ impl acp_thread::AgentConnection for NativeAgentConnection { } impl acp_thread::AgentTelemetry for NativeAgentConnection { - fn agent_name(&self) -> String { - "Zed".into() - } - fn thread_data( &self, session_id: &acp::SessionId, diff --git a/crates/agent_servers/src/acp.rs b/crates/agent_servers/src/acp.rs index deb4a406d78ae53d4ffba732702233fd1572cb64..4e41b247599e511b6345882701a34a6b66f3c418 100644 --- a/crates/agent_servers/src/acp.rs +++ b/crates/agent_servers/src/acp.rs @@ -29,6 +29,7 @@ pub struct UnsupportedVersion; pub struct AcpConnection { server_name: SharedString, + telemetry_id: &'static str, connection: Rc, sessions: Rc>>, auth_methods: Vec, @@ -52,6 +53,7 @@ pub struct AcpSession { pub async fn connect( server_name: SharedString, + telemetry_id: &'static str, command: AgentServerCommand, root_dir: &Path, default_mode: Option, @@ -60,6 +62,7 @@ pub async fn connect( ) -> Result> { let conn = AcpConnection::stdio( server_name, + telemetry_id, command.clone(), root_dir, default_mode, @@ -75,6 +78,7 @@ const MINIMUM_SUPPORTED_VERSION: acp::ProtocolVersion = acp::V1; impl AcpConnection { pub async fn stdio( server_name: SharedString, + telemetry_id: &'static str, command: AgentServerCommand, root_dir: &Path, default_mode: Option, @@ -199,6 +203,7 @@ impl AcpConnection { root_dir: root_dir.to_owned(), connection, server_name, + telemetry_id, sessions, agent_capabilities: response.agent_capabilities, default_mode, @@ -226,6 +231,10 @@ impl Drop for AcpConnection { } impl AgentConnection for AcpConnection { + fn telemetry_id(&self) -> &'static str { + self.telemetry_id + } + fn new_thread( self: Rc, project: Entity, diff --git a/crates/agent_servers/src/claude.rs b/crates/agent_servers/src/claude.rs index b84a386679cee825be22d895634a6971b537fa89..cd3207824a7c05ddfaafeca965deea0918ccfb39 100644 --- a/crates/agent_servers/src/claude.rs +++ b/crates/agent_servers/src/claude.rs @@ -62,6 +62,7 @@ impl AgentServer for ClaudeCode { cx: &mut App, ) -> Task, Option)>> { let name = self.name(); + let telemetry_id = self.telemetry_id(); let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); @@ -85,6 +86,7 @@ impl AgentServer for ClaudeCode { .await?; let connection = crate::acp::connect( name, + telemetry_id, command, root_dir.as_ref(), default_mode, diff --git a/crates/agent_servers/src/codex.rs b/crates/agent_servers/src/codex.rs index 1b287063e5dc4363b6a1818434fc43285749b737..95375ad412c31272dbfce9262b4b5fd38fe55c50 100644 --- a/crates/agent_servers/src/codex.rs +++ b/crates/agent_servers/src/codex.rs @@ -63,6 +63,7 @@ impl AgentServer for Codex { cx: &mut App, ) -> Task, Option)>> { let name = self.name(); + let telemetry_id = self.telemetry_id(); let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); @@ -87,6 +88,7 @@ impl AgentServer for Codex { let connection = crate::acp::connect( name, + telemetry_id, command, root_dir.as_ref(), default_mode, diff --git a/crates/agent_servers/src/custom.rs b/crates/agent_servers/src/custom.rs index 406a18965111a44bc4e78469b20aaf199cbda037..a51ed8a51a24d28aa6f2867797207bb15643a67d 100644 --- a/crates/agent_servers/src/custom.rs +++ b/crates/agent_servers/src/custom.rs @@ -67,6 +67,7 @@ impl crate::AgentServer for CustomAgentServer { cx: &mut App, ) -> Task, Option)>> { let name = self.name(); + let telemetry_id = self.telemetry_id(); let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let default_mode = self.default_mode(cx); @@ -92,6 +93,7 @@ impl crate::AgentServer for CustomAgentServer { .await?; let connection = crate::acp::connect( name, + telemetry_id, command, root_dir.as_ref(), default_mode, diff --git a/crates/agent_servers/src/gemini.rs b/crates/agent_servers/src/gemini.rs index 8004f5caec4a7bd2e3e6b1d9a885f4943fa21147..feaa221cbccb789ed3a89bed9f23d544e1d3b5f7 100644 --- a/crates/agent_servers/src/gemini.rs +++ b/crates/agent_servers/src/gemini.rs @@ -31,6 +31,7 @@ impl AgentServer for Gemini { cx: &mut App, ) -> Task, Option)>> { let name = self.name(); + let telemetry_id = self.telemetry_id(); let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().into_owned()); let is_remote = delegate.project.read(cx).is_via_remote_server(); let store = delegate.store.downgrade(); @@ -64,6 +65,7 @@ impl AgentServer for Gemini { let connection = crate::acp::connect( name, + telemetry_id, command, root_dir.as_ref(), default_mode, diff --git a/crates/agent_ui/src/acp/model_selector_popover.rs b/crates/agent_ui/src/acp/model_selector_popover.rs index bd64756483032bee00ba8f56794bcb228bf91246..2e8ade95ffcb65d8c7742b60fa0facc70358ae1e 100644 --- a/crates/agent_ui/src/acp/model_selector_popover.rs +++ b/crates/agent_ui/src/acp/model_selector_popover.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use acp_thread::AgentModelSelector; +use acp_thread::{AgentModelInfo, AgentModelSelector}; use gpui::{Entity, FocusHandle}; use picker::popover_menu::PickerPopoverMenu; use ui::{ @@ -36,12 +36,8 @@ impl AcpModelSelectorPopover { self.menu_handle.toggle(window, cx); } - pub fn active_model_name(&self, cx: &App) -> Option { - self.selector - .read(cx) - .delegate - .active_model() - .map(|model| model.name.clone()) + pub fn active_model<'a>(&self, cx: &'a App) -> Option<&'a AgentModelInfo> { + self.selector.read(cx).delegate.active_model() } } diff --git a/crates/agent_ui/src/acp/thread_view.rs b/crates/agent_ui/src/acp/thread_view.rs index e6aab8c52adf112b07891178a3e0d98c8962b742..5907076fb9e302add143d1bdced2f46e2bbdb41a 100644 --- a/crates/agent_ui/src/acp/thread_view.rs +++ b/crates/agent_ui/src/acp/thread_view.rs @@ -4,7 +4,7 @@ use acp_thread::{ ToolCallStatus, UserMessageId, }; use acp_thread::{AgentConnection, Plan}; -use action_log::ActionLog; +use action_log::{ActionLog, ActionLogTelemetry}; use agent::{DbThreadMetadata, HistoryEntry, HistoryEntryId, HistoryStore, NativeAgentServer}; use agent_client_protocol::{self as acp, PromptCapabilities}; use agent_servers::{AgentServer, AgentServerDelegate}; @@ -169,7 +169,7 @@ impl ThreadFeedbackState { } } let session_id = thread.read(cx).session_id().clone(); - let agent_name = telemetry.agent_name(); + let agent = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); let rating = match feedback { ThreadFeedback::Positive => "positive", @@ -179,9 +179,9 @@ impl ThreadFeedbackState { let thread = task.await?; telemetry::event!( "Agent Thread Rated", + agent = agent, session_id = session_id, rating = rating, - agent = agent_name, thread = thread ); anyhow::Ok(()) @@ -206,15 +206,15 @@ impl ThreadFeedbackState { self.comments_editor.take(); let session_id = thread.read(cx).session_id().clone(); - let agent_name = telemetry.agent_name(); + let agent = thread.read(cx).connection().telemetry_id(); let task = telemetry.thread_data(&session_id, cx); cx.background_spawn(async move { let thread = task.await?; telemetry::event!( "Agent Thread Feedback Comments", + agent = agent, session_id = session_id, comments = comments, - agent = agent_name, thread = thread ); anyhow::Ok(()) @@ -1123,8 +1123,6 @@ impl AcpThreadView { message_editor.contents(full_mention_content, cx) }); - let agent_telemetry_id = self.agent.telemetry_id(); - self.thread_error.take(); self.editing_message.take(); self.thread_feedback.clear(); @@ -1132,6 +1130,8 @@ impl AcpThreadView { let Some(thread) = self.thread() else { return; }; + let agent_telemetry_id = self.agent.telemetry_id(); + let session_id = thread.read(cx).session_id().clone(); let thread = thread.downgrade(); if self.should_be_following { self.workspace @@ -1142,6 +1142,7 @@ impl AcpThreadView { } self.is_loading_contents = true; + let model_id = self.current_model_id(cx); let guard = cx.new(|_| ()); cx.observe_release(&guard, |this, _guard, cx| { this.is_loading_contents = false; @@ -1163,6 +1164,7 @@ impl AcpThreadView { message_editor.clear(window, cx); }); })?; + let turn_start_time = Instant::now(); let send = thread.update(cx, |thread, cx| { thread.action_log().update(cx, |action_log, cx| { for buffer in tracked_buffers { @@ -1171,11 +1173,27 @@ impl AcpThreadView { }); drop(guard); - telemetry::event!("Agent Message Sent", agent = agent_telemetry_id); + telemetry::event!( + "Agent Message Sent", + agent = agent_telemetry_id, + session = session_id, + model = model_id + ); thread.send(contents, cx) })?; - send.await + let res = send.await; + let turn_time_ms = turn_start_time.elapsed().as_millis(); + let status = if res.is_ok() { "success" } else { "failure" }; + telemetry::event!( + "Agent Turn Completed", + agent = agent_telemetry_id, + session = session_id, + model = model_id, + status, + turn_time_ms, + ); + res }); cx.spawn(async move |this, cx| { @@ -1377,7 +1395,7 @@ impl AcpThreadView { AcpThreadEvent::Refusal => { self.thread_retry_status.take(); self.thread_error = Some(ThreadError::Refusal); - let model_or_agent_name = self.get_current_model_name(cx); + let model_or_agent_name = self.current_model_name(cx); let notification_message = format!("{} refused to respond to this request", model_or_agent_name); self.notify_with_sound(¬ification_message, IconName::Warning, window, cx); @@ -1846,6 +1864,14 @@ impl AcpThreadView { let Some(thread) = self.thread() else { return; }; + + telemetry::event!( + "Agent Tool Call Authorized", + agent = self.agent.telemetry_id(), + session = thread.read(cx).session_id(), + option = option_kind + ); + thread.update(cx, |thread, cx| { thread.authorize_tool_call(tool_call_id, option_id, option_kind, cx); }); @@ -3578,6 +3604,7 @@ impl AcpThreadView { ) -> Option { let thread = thread_entity.read(cx); let action_log = thread.action_log(); + let telemetry = ActionLogTelemetry::from(thread); let changed_buffers = action_log.read(cx).changed_buffers(cx); let plan = thread.plan(); @@ -3625,6 +3652,7 @@ impl AcpThreadView { .when(self.edits_expanded, |parent| { parent.child(self.render_edited_files( action_log, + telemetry, &changed_buffers, pending_edits, cx, @@ -3905,6 +3933,7 @@ impl AcpThreadView { fn render_edited_files( &self, action_log: &Entity, + telemetry: ActionLogTelemetry, changed_buffers: &BTreeMap, Entity>, pending_edits: bool, cx: &Context, @@ -4024,12 +4053,14 @@ impl AcpThreadView { .on_click({ let buffer = buffer.clone(); let action_log = action_log.clone(); + let telemetry = telemetry.clone(); move |_, _, cx| { action_log.update(cx, |action_log, cx| { action_log .reject_edits_in_ranges( buffer.clone(), vec![Anchor::MIN..Anchor::MAX], + Some(telemetry.clone()), cx, ) .detach_and_log_err(cx); @@ -4044,11 +4075,13 @@ impl AcpThreadView { .on_click({ let buffer = buffer.clone(); let action_log = action_log.clone(); + let telemetry = telemetry.clone(); move |_, _, cx| { action_log.update(cx, |action_log, cx| { action_log.keep_edits_in_range( buffer.clone(), Anchor::MIN..Anchor::MAX, + Some(telemetry.clone()), cx, ); }) @@ -4264,17 +4297,23 @@ impl AcpThreadView { let Some(thread) = self.thread() else { return; }; + let telemetry = ActionLogTelemetry::from(thread.read(cx)); let action_log = thread.read(cx).action_log().clone(); - action_log.update(cx, |action_log, cx| action_log.keep_all_edits(cx)); + action_log.update(cx, |action_log, cx| { + action_log.keep_all_edits(Some(telemetry), cx) + }); } fn reject_all(&mut self, _: &RejectAll, _window: &mut Window, cx: &mut Context) { let Some(thread) = self.thread() else { return; }; + let telemetry = ActionLogTelemetry::from(thread.read(cx)); let action_log = thread.read(cx).action_log().clone(); action_log - .update(cx, |action_log, cx| action_log.reject_all_edits(cx)) + .update(cx, |action_log, cx| { + action_log.reject_all_edits(Some(telemetry), cx) + }) .detach(); } @@ -5334,20 +5373,21 @@ impl AcpThreadView { ) } - fn get_current_model_name(&self, cx: &App) -> SharedString { + fn current_model_id(&self, cx: &App) -> Option { + self.model_selector + .as_ref() + .and_then(|selector| selector.read(cx).active_model(cx).map(|m| m.id.to_string())) + } + + fn current_model_name(&self, cx: &App) -> SharedString { // For native agent (Zed Agent), use the specific model name (e.g., "Claude 3.5 Sonnet") // For ACP agents, use the agent name (e.g., "Claude Code", "Gemini CLI") // This provides better clarity about what refused the request - if self - .agent - .clone() - .downcast::() - .is_some() - { - // Native agent - use the model name + if self.as_native_connection(cx).is_some() { self.model_selector .as_ref() - .and_then(|selector| selector.read(cx).active_model_name(cx)) + .and_then(|selector| selector.read(cx).active_model(cx)) + .map(|model| model.name.clone()) .unwrap_or_else(|| SharedString::from("The model")) } else { // ACP agent - use the agent name (e.g., "Claude Code", "Gemini CLI") @@ -5356,7 +5396,7 @@ impl AcpThreadView { } fn render_refusal_error(&self, cx: &mut Context<'_, Self>) -> Callout { - let model_or_agent_name = self.get_current_model_name(cx); + let model_or_agent_name = self.current_model_name(cx); let refusal_message = format!( "{} refused to respond to this prompt. This can happen when a model believes the prompt violates its content policy or safety guidelines, so rephrasing it can sometimes address the issue.", model_or_agent_name @@ -6342,6 +6382,10 @@ pub(crate) mod tests { struct SaboteurAgentConnection; impl AgentConnection for SaboteurAgentConnection { + fn telemetry_id(&self) -> &'static str { + "saboteur" + } + fn new_thread( self: Rc, project: Entity, @@ -6402,6 +6446,10 @@ pub(crate) mod tests { struct RefusalAgentConnection; impl AgentConnection for RefusalAgentConnection { + fn telemetry_id(&self) -> &'static str { + "refusal" + } + fn new_thread( self: Rc, project: Entity, diff --git a/crates/agent_ui/src/agent_diff.rs b/crates/agent_ui/src/agent_diff.rs index 63eb2ac49731a5e57b4eae5bf33b821b2e223c25..7199c49546154dfcc0b23c67876e6a49b190c804 100644 --- a/crates/agent_ui/src/agent_diff.rs +++ b/crates/agent_ui/src/agent_diff.rs @@ -1,6 +1,6 @@ use crate::{Keep, KeepAll, OpenAgentDiff, Reject, RejectAll}; use acp_thread::{AcpThread, AcpThreadEvent}; -use action_log::ActionLog; +use action_log::ActionLogTelemetry; use agent_settings::AgentSettings; use anyhow::Result; use buffer_diff::DiffHunkStatus; @@ -40,79 +40,16 @@ use zed_actions::assistant::ToggleFocus; pub struct AgentDiffPane { multibuffer: Entity, editor: Entity, - thread: AgentDiffThread, + thread: Entity, focus_handle: FocusHandle, workspace: WeakEntity, title: SharedString, _subscriptions: Vec, } -#[derive(PartialEq, Eq, Clone)] -pub enum AgentDiffThread { - AcpThread(Entity), -} - -impl AgentDiffThread { - fn project(&self, cx: &App) -> Entity { - match self { - AgentDiffThread::AcpThread(thread) => thread.read(cx).project().clone(), - } - } - fn action_log(&self, cx: &App) -> Entity { - match self { - AgentDiffThread::AcpThread(thread) => thread.read(cx).action_log().clone(), - } - } - - fn title(&self, cx: &App) -> SharedString { - match self { - AgentDiffThread::AcpThread(thread) => thread.read(cx).title(), - } - } - - fn has_pending_edit_tool_uses(&self, cx: &App) -> bool { - match self { - AgentDiffThread::AcpThread(thread) => thread.read(cx).has_pending_edit_tool_calls(), - } - } - - fn downgrade(&self) -> WeakAgentDiffThread { - match self { - AgentDiffThread::AcpThread(thread) => { - WeakAgentDiffThread::AcpThread(thread.downgrade()) - } - } - } -} - -impl From> for AgentDiffThread { - fn from(entity: Entity) -> Self { - AgentDiffThread::AcpThread(entity) - } -} - -#[derive(PartialEq, Eq, Clone)] -pub enum WeakAgentDiffThread { - AcpThread(WeakEntity), -} - -impl WeakAgentDiffThread { - pub fn upgrade(&self) -> Option { - match self { - WeakAgentDiffThread::AcpThread(weak) => weak.upgrade().map(AgentDiffThread::AcpThread), - } - } -} - -impl From> for WeakAgentDiffThread { - fn from(entity: WeakEntity) -> Self { - WeakAgentDiffThread::AcpThread(entity) - } -} - impl AgentDiffPane { pub fn deploy( - thread: impl Into, + thread: Entity, workspace: WeakEntity, window: &mut Window, cx: &mut App, @@ -123,12 +60,11 @@ impl AgentDiffPane { } pub fn deploy_in_workspace( - thread: impl Into, + thread: Entity, workspace: &mut Workspace, window: &mut Window, cx: &mut Context, ) -> Entity { - let thread = thread.into(); let existing_diff = workspace .items_of_type::(cx) .find(|diff| diff.read(cx).thread == thread); @@ -145,7 +81,7 @@ impl AgentDiffPane { } pub fn new( - thread: AgentDiffThread, + thread: Entity, workspace: WeakEntity, window: &mut Window, cx: &mut Context, @@ -153,7 +89,7 @@ impl AgentDiffPane { let focus_handle = cx.focus_handle(); let multibuffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite)); - let project = thread.project(cx); + let project = thread.read(cx).project().clone(); let editor = cx.new(|cx| { let mut editor = Editor::for_multibuffer(multibuffer.clone(), Some(project.clone()), window, cx); @@ -164,19 +100,16 @@ impl AgentDiffPane { editor }); - let action_log = thread.action_log(cx); + let action_log = thread.read(cx).action_log().clone(); let mut this = Self { _subscriptions: vec![ cx.observe_in(&action_log, window, |this, _action_log, window, cx| { this.update_excerpts(window, cx) }), - match &thread { - AgentDiffThread::AcpThread(thread) => cx - .subscribe(thread, |this, _thread, event, cx| { - this.handle_acp_thread_event(event, cx) - }), - }, + cx.subscribe(&thread, |this, _thread, event, cx| { + this.handle_acp_thread_event(event, cx) + }), ], title: SharedString::default(), multibuffer, @@ -191,7 +124,12 @@ impl AgentDiffPane { } fn update_excerpts(&mut self, window: &mut Window, cx: &mut Context) { - let changed_buffers = self.thread.action_log(cx).read(cx).changed_buffers(cx); + let changed_buffers = self + .thread + .read(cx) + .action_log() + .read(cx) + .changed_buffers(cx); let mut paths_to_delete = self.multibuffer.read(cx).paths().collect::>(); for (buffer, diff_handle) in changed_buffers { @@ -278,7 +216,7 @@ impl AgentDiffPane { } fn update_title(&mut self, cx: &mut Context) { - let new_title = self.thread.title(cx); + let new_title = self.thread.read(cx).title(); if new_title != self.title { self.title = new_title; cx.emit(EditorEvent::TitleChanged); @@ -340,16 +278,18 @@ impl AgentDiffPane { } fn keep_all(&mut self, _: &KeepAll, _window: &mut Window, cx: &mut Context) { - self.thread - .action_log(cx) - .update(cx, |action_log, cx| action_log.keep_all_edits(cx)) + let telemetry = ActionLogTelemetry::from(self.thread.read(cx)); + let action_log = self.thread.read(cx).action_log().clone(); + action_log.update(cx, |action_log, cx| { + action_log.keep_all_edits(Some(telemetry), cx) + }); } } fn keep_edits_in_selection( editor: &mut Editor, buffer_snapshot: &MultiBufferSnapshot, - thread: &AgentDiffThread, + thread: &Entity, window: &mut Window, cx: &mut Context, ) { @@ -364,7 +304,7 @@ fn keep_edits_in_selection( fn reject_edits_in_selection( editor: &mut Editor, buffer_snapshot: &MultiBufferSnapshot, - thread: &AgentDiffThread, + thread: &Entity, window: &mut Window, cx: &mut Context, ) { @@ -378,7 +318,7 @@ fn reject_edits_in_selection( fn keep_edits_in_ranges( editor: &mut Editor, buffer_snapshot: &MultiBufferSnapshot, - thread: &AgentDiffThread, + thread: &Entity, ranges: Vec>, window: &mut Window, cx: &mut Context, @@ -393,8 +333,15 @@ fn keep_edits_in_ranges( for hunk in &diff_hunks_in_ranges { let buffer = multibuffer.read(cx).buffer(hunk.buffer_id); if let Some(buffer) = buffer { - thread.action_log(cx).update(cx, |action_log, cx| { - action_log.keep_edits_in_range(buffer, hunk.buffer_range.clone(), cx) + let action_log = thread.read(cx).action_log().clone(); + let telemetry = ActionLogTelemetry::from(thread.read(cx)); + action_log.update(cx, |action_log, cx| { + action_log.keep_edits_in_range( + buffer, + hunk.buffer_range.clone(), + Some(telemetry), + cx, + ) }); } } @@ -403,7 +350,7 @@ fn keep_edits_in_ranges( fn reject_edits_in_ranges( editor: &mut Editor, buffer_snapshot: &MultiBufferSnapshot, - thread: &AgentDiffThread, + thread: &Entity, ranges: Vec>, window: &mut Window, cx: &mut Context, @@ -427,11 +374,12 @@ fn reject_edits_in_ranges( } } + let action_log = thread.read(cx).action_log().clone(); + let telemetry = ActionLogTelemetry::from(thread.read(cx)); for (buffer, ranges) in ranges_by_buffer { - thread - .action_log(cx) + action_log .update(cx, |action_log, cx| { - action_log.reject_edits_in_ranges(buffer, ranges, cx) + action_log.reject_edits_in_ranges(buffer, ranges, Some(telemetry.clone()), cx) }) .detach_and_log_err(cx); } @@ -531,7 +479,7 @@ impl Item for AgentDiffPane { } fn tab_content(&self, params: TabContentParams, _window: &Window, cx: &App) -> AnyElement { - let title = self.thread.title(cx); + let title = self.thread.read(cx).title(); Label::new(format!("Review: {}", title)) .color(if params.selected { Color::Default @@ -712,7 +660,7 @@ impl Render for AgentDiffPane { } } -fn diff_hunk_controls(thread: &AgentDiffThread) -> editor::RenderDiffHunkControlsFn { +fn diff_hunk_controls(thread: &Entity) -> editor::RenderDiffHunkControlsFn { let thread = thread.clone(); Arc::new( @@ -739,7 +687,7 @@ fn render_diff_hunk_controls( hunk_range: Range, is_created_file: bool, line_height: Pixels, - thread: &AgentDiffThread, + thread: &Entity, editor: &Entity, cx: &mut App, ) -> AnyElement { @@ -1153,8 +1101,11 @@ impl Render for AgentDiffToolbar { return Empty.into_any(); }; - let has_pending_edit_tool_use = - agent_diff.read(cx).thread.has_pending_edit_tool_uses(cx); + let has_pending_edit_tool_use = agent_diff + .read(cx) + .thread + .read(cx) + .has_pending_edit_tool_calls(); if has_pending_edit_tool_use { return div().px_2().child(spinner_icon).into_any(); @@ -1214,7 +1165,7 @@ pub enum EditorState { } struct WorkspaceThread { - thread: WeakAgentDiffThread, + thread: WeakEntity, _thread_subscriptions: (Subscription, Subscription), singleton_editors: HashMap, HashMap, Subscription>>, _settings_subscription: Subscription, @@ -1239,23 +1190,23 @@ impl AgentDiff { pub fn set_active_thread( workspace: &WeakEntity, - thread: impl Into, + thread: Entity, window: &mut Window, cx: &mut App, ) { Self::global(cx).update(cx, |this, cx| { - this.register_active_thread_impl(workspace, thread.into(), window, cx); + this.register_active_thread_impl(workspace, thread, window, cx); }); } fn register_active_thread_impl( &mut self, workspace: &WeakEntity, - thread: AgentDiffThread, + thread: Entity, window: &mut Window, cx: &mut Context, ) { - let action_log = thread.action_log(cx); + let action_log = thread.read(cx).action_log().clone(); let action_log_subscription = cx.observe_in(&action_log, window, { let workspace = workspace.clone(); @@ -1264,14 +1215,12 @@ impl AgentDiff { } }); - let thread_subscription = match &thread { - AgentDiffThread::AcpThread(thread) => cx.subscribe_in(thread, window, { - let workspace = workspace.clone(); - move |this, thread, event, window, cx| { - this.handle_acp_thread_event(&workspace, thread, event, window, cx) - } - }), - }; + let thread_subscription = cx.subscribe_in(&thread, window, { + let workspace = workspace.clone(); + move |this, thread, event, window, cx| { + this.handle_acp_thread_event(&workspace, thread, event, window, cx) + } + }); if let Some(workspace_thread) = self.workspace_threads.get_mut(workspace) { // replace thread and action log subscription, but keep editors @@ -1348,7 +1297,7 @@ impl AgentDiff { fn register_review_action( workspace: &mut Workspace, - review: impl Fn(&Entity, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState + review: impl Fn(&Entity, &Entity, &mut Window, &mut App) -> PostReviewState + 'static, this: &Entity, ) { @@ -1508,7 +1457,7 @@ impl AgentDiff { return; }; - let action_log = thread.action_log(cx); + let action_log = thread.read(cx).action_log(); let changed_buffers = action_log.read(cx).changed_buffers(cx); let mut unaffected = self.reviewing_editors.clone(); @@ -1627,7 +1576,7 @@ impl AgentDiff { fn keep_all( editor: &Entity, - thread: &AgentDiffThread, + thread: &Entity, window: &mut Window, cx: &mut App, ) -> PostReviewState { @@ -1647,7 +1596,7 @@ impl AgentDiff { fn reject_all( editor: &Entity, - thread: &AgentDiffThread, + thread: &Entity, window: &mut Window, cx: &mut App, ) -> PostReviewState { @@ -1667,7 +1616,7 @@ impl AgentDiff { fn keep( editor: &Entity, - thread: &AgentDiffThread, + thread: &Entity, window: &mut Window, cx: &mut App, ) -> PostReviewState { @@ -1680,7 +1629,7 @@ impl AgentDiff { fn reject( editor: &Entity, - thread: &AgentDiffThread, + thread: &Entity, window: &mut Window, cx: &mut App, ) -> PostReviewState { @@ -1703,7 +1652,7 @@ impl AgentDiff { fn review_in_active_editor( &mut self, workspace: &mut Workspace, - review: impl Fn(&Entity, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState, + review: impl Fn(&Entity, &Entity, &mut Window, &mut App) -> PostReviewState, window: &mut Window, cx: &mut Context, ) -> Option>> { @@ -1725,7 +1674,7 @@ impl AgentDiff { if let PostReviewState::AllReviewed = review(&editor, &thread, window, cx) && let Some(curr_buffer) = editor.read(cx).buffer().read(cx).as_singleton() { - let changed_buffers = thread.action_log(cx).read(cx).changed_buffers(cx); + let changed_buffers = thread.read(cx).action_log().read(cx).changed_buffers(cx); let mut keys = changed_buffers.keys().cycle(); keys.find(|k| *k == &curr_buffer); @@ -1815,8 +1764,7 @@ mod tests { .await .unwrap(); - let thread = AgentDiffThread::AcpThread(thread); - let action_log = cx.read(|cx| thread.action_log(cx)); + let action_log = cx.read(|cx| thread.read(cx).action_log().clone()); let (workspace, cx) = cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx)); @@ -2004,7 +1952,6 @@ mod tests { let action_log = thread.read_with(cx, |thread, _| thread.action_log().clone()); // Set the active thread - let thread = AgentDiffThread::AcpThread(thread); cx.update(|window, cx| { AgentDiff::set_active_thread(&workspace.downgrade(), thread.clone(), window, cx) }); diff --git a/crates/telemetry/src/telemetry.rs b/crates/telemetry/src/telemetry.rs index e6c85169671cfcfb2ee798af2c9566b2eb548ac9..86be886616a53c063555f4d29a55781d7b0494f8 100644 --- a/crates/telemetry/src/telemetry.rs +++ b/crates/telemetry/src/telemetry.rs @@ -54,9 +54,10 @@ macro_rules! serialize_property { } pub fn send_event(event: Event) { - if let Some(queue) = TELEMETRY_QUEUE.get() { - queue.unbounded_send(event).ok(); - } + println!("{} - {:?}", event.event_type, event.event_properties); + // if let Some(queue) = TELEMETRY_QUEUE.get() { + // queue.unbounded_send(event).ok(); + // } } pub fn init(tx: mpsc::UnboundedSender) {