Detailed changes
@@ -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",
@@ -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
@@ -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<acp::TerminalId, acp::TerminalExitStatus>,
}
+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
}
@@ -20,6 +20,8 @@ impl UserMessageId {
}
pub trait AgentConnection {
+ fn telemetry_id(&self) -> &'static str;
+
fn new_thread(
self: Rc<Self>,
project: Entity<Project>,
@@ -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] {
&[]
}
@@ -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
@@ -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>,
buffer_range: Range<impl language::ToPoint>,
+ telemetry: Option<ActionLogTelemetry>,
cx: &mut Context<Self>,
) {
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::<String>(),
);
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>,
buffer_ranges: Vec<Range<impl language::ToPoint>>,
+ telemetry: Option<ActionLogTelemetry>,
cx: &mut Context<Self>,
) -> Task<Result<()>> {
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>) {
- self.tracked_buffers
- .retain(|_buffer, tracked_buffer| match tracked_buffer.status {
+ pub fn keep_all_edits(
+ &mut self,
+ telemetry: Option<ActionLogTelemetry>,
+ cx: &mut Context<Self>,
+ ) {
+ 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<Self>) -> Task<()> {
+ pub fn reject_all_edits(
+ &mut self,
+ telemetry: Option<ActionLogTelemetry>,
+ cx: &mut Context<Self>,
+ ) -> 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<str>,
+}
+
+struct ActionLogMetrics {
+ lines_removed: u32,
+ lines_added: u32,
+ language: Option<SharedString>,
+}
+
+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<u32>]) {
+ for edit in edits {
+ self.add_edit(edit);
+ }
+ }
+
+ fn add_edit(&mut self, edit: &Edit<u32>) {
+ 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<u32>,
edits: Vec<Edit<u32>>,
@@ -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();
@@ -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<Self>,
project: Entity<Project>,
@@ -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,
@@ -29,6 +29,7 @@ pub struct UnsupportedVersion;
pub struct AcpConnection {
server_name: SharedString,
+ telemetry_id: &'static str,
connection: Rc<acp::ClientSideConnection>,
sessions: Rc<RefCell<HashMap<acp::SessionId, AcpSession>>>,
auth_methods: Vec<acp::AuthMethod>,
@@ -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<acp::SessionModeId>,
@@ -60,6 +62,7 @@ pub async fn connect(
) -> Result<Rc<dyn AgentConnection>> {
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<acp::SessionModeId>,
@@ -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<Self>,
project: Entity<Project>,
@@ -62,6 +62,7 @@ impl AgentServer for ClaudeCode {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
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,
@@ -63,6 +63,7 @@ impl AgentServer for Codex {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
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,
@@ -67,6 +67,7 @@ impl crate::AgentServer for CustomAgentServer {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
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,
@@ -31,6 +31,7 @@ impl AgentServer for Gemini {
cx: &mut App,
) -> Task<Result<(Rc<dyn AgentConnection>, Option<task::SpawnInTerminal>)>> {
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,
@@ -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<SharedString> {
- 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()
}
}
@@ -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<AnyElement> {
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<ActionLog>,
+ telemetry: ActionLogTelemetry,
changed_buffers: &BTreeMap<Entity<Buffer>, Entity<BufferDiff>>,
pending_edits: bool,
cx: &Context<Self>,
@@ -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<Self>) {
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<String> {
+ 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::<agent::NativeAgentServer>()
- .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<Self>,
project: Entity<Project>,
@@ -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<Self>,
project: Entity<Project>,
@@ -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<MultiBuffer>,
editor: Entity<Editor>,
- thread: AgentDiffThread,
+ thread: Entity<AcpThread>,
focus_handle: FocusHandle,
workspace: WeakEntity<Workspace>,
title: SharedString,
_subscriptions: Vec<Subscription>,
}
-#[derive(PartialEq, Eq, Clone)]
-pub enum AgentDiffThread {
- AcpThread(Entity<AcpThread>),
-}
-
-impl AgentDiffThread {
- fn project(&self, cx: &App) -> Entity<Project> {
- match self {
- AgentDiffThread::AcpThread(thread) => thread.read(cx).project().clone(),
- }
- }
- fn action_log(&self, cx: &App) -> Entity<ActionLog> {
- 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<Entity<AcpThread>> for AgentDiffThread {
- fn from(entity: Entity<AcpThread>) -> Self {
- AgentDiffThread::AcpThread(entity)
- }
-}
-
-#[derive(PartialEq, Eq, Clone)]
-pub enum WeakAgentDiffThread {
- AcpThread(WeakEntity<AcpThread>),
-}
-
-impl WeakAgentDiffThread {
- pub fn upgrade(&self) -> Option<AgentDiffThread> {
- match self {
- WeakAgentDiffThread::AcpThread(weak) => weak.upgrade().map(AgentDiffThread::AcpThread),
- }
- }
-}
-
-impl From<WeakEntity<AcpThread>> for WeakAgentDiffThread {
- fn from(entity: WeakEntity<AcpThread>) -> Self {
- WeakAgentDiffThread::AcpThread(entity)
- }
-}
-
impl AgentDiffPane {
pub fn deploy(
- thread: impl Into<AgentDiffThread>,
+ thread: Entity<AcpThread>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut App,
@@ -123,12 +60,11 @@ impl AgentDiffPane {
}
pub fn deploy_in_workspace(
- thread: impl Into<AgentDiffThread>,
+ thread: Entity<AcpThread>,
workspace: &mut Workspace,
window: &mut Window,
cx: &mut Context<Workspace>,
) -> Entity<Self> {
- let thread = thread.into();
let existing_diff = workspace
.items_of_type::<AgentDiffPane>(cx)
.find(|diff| diff.read(cx).thread == thread);
@@ -145,7 +81,7 @@ impl AgentDiffPane {
}
pub fn new(
- thread: AgentDiffThread,
+ thread: Entity<AcpThread>,
workspace: WeakEntity<Workspace>,
window: &mut Window,
cx: &mut Context<Self>,
@@ -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<Self>) {
- 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::<HashSet<_>>();
for (buffer, diff_handle) in changed_buffers {
@@ -278,7 +216,7 @@ impl AgentDiffPane {
}
fn update_title(&mut self, cx: &mut Context<Self>) {
- 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>) {
- 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<AcpThread>,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -364,7 +304,7 @@ fn keep_edits_in_selection(
fn reject_edits_in_selection(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
window: &mut Window,
cx: &mut Context<Editor>,
) {
@@ -378,7 +318,7 @@ fn reject_edits_in_selection(
fn keep_edits_in_ranges(
editor: &mut Editor,
buffer_snapshot: &MultiBufferSnapshot,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
ranges: Vec<Range<editor::Anchor>>,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -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<AcpThread>,
ranges: Vec<Range<editor::Anchor>>,
window: &mut Window,
cx: &mut Context<Editor>,
@@ -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<AcpThread>) -> editor::RenderDiffHunkControlsFn {
let thread = thread.clone();
Arc::new(
@@ -739,7 +687,7 @@ fn render_diff_hunk_controls(
hunk_range: Range<editor::Anchor>,
is_created_file: bool,
line_height: Pixels,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
editor: &Entity<Editor>,
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<AcpThread>,
_thread_subscriptions: (Subscription, Subscription),
singleton_editors: HashMap<WeakEntity<Buffer>, HashMap<WeakEntity<Editor>, Subscription>>,
_settings_subscription: Subscription,
@@ -1239,23 +1190,23 @@ impl AgentDiff {
pub fn set_active_thread(
workspace: &WeakEntity<Workspace>,
- thread: impl Into<AgentDiffThread>,
+ thread: Entity<AcpThread>,
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<Workspace>,
- thread: AgentDiffThread,
+ thread: Entity<AcpThread>,
window: &mut Window,
cx: &mut Context<Self>,
) {
- 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<T: Action>(
workspace: &mut Workspace,
- review: impl Fn(&Entity<Editor>, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState
+ review: impl Fn(&Entity<Editor>, &Entity<AcpThread>, &mut Window, &mut App) -> PostReviewState
+ 'static,
this: &Entity<AgentDiff>,
) {
@@ -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<Editor>,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1647,7 +1596,7 @@ impl AgentDiff {
fn reject_all(
editor: &Entity<Editor>,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1667,7 +1616,7 @@ impl AgentDiff {
fn keep(
editor: &Entity<Editor>,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
window: &mut Window,
cx: &mut App,
) -> PostReviewState {
@@ -1680,7 +1629,7 @@ impl AgentDiff {
fn reject(
editor: &Entity<Editor>,
- thread: &AgentDiffThread,
+ thread: &Entity<AcpThread>,
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<Editor>, &AgentDiffThread, &mut Window, &mut App) -> PostReviewState,
+ review: impl Fn(&Entity<Editor>, &Entity<AcpThread>, &mut Window, &mut App) -> PostReviewState,
window: &mut Window,
cx: &mut Context<Self>,
) -> Option<Task<Result<()>>> {
@@ -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)
});
@@ -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<Event>) {