Include prediction ID on edit prediction accepted/discarded events (#24480)

Marshall Bowers created

This PR updates the edit predictions to include the prediction ID
returned from the server on the resulting telemetry events indicating
whether the prediction was accepted or discarded.

The `prediction_id` on the events can then be correlated with the
`request_id` on the server-side prediction events.

Release Notes:

- N/A

Change summary

Cargo.lock                                              |  5 +
Cargo.toml                                              |  2 
crates/copilot/src/copilot_completion_provider.rs       |  1 
crates/editor/src/editor.rs                             | 24 +++++++-
crates/editor/src/inline_completion_tests.rs            |  1 
crates/inline_completion/src/inline_completion.rs       |  4 +
crates/supermaven/src/supermaven_completion_provider.rs |  1 
crates/zeta/src/zeta.rs                                 | 29 ++++++----
8 files changed, 47 insertions(+), 20 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -16757,12 +16757,13 @@ dependencies = [
 
 [[package]]
 name = "zed_llm_client"
-version = "0.3.0"
+version = "0.4.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "656118e6b072924d28815cb892278f12c2548117794e733bd2c075ef4a0427e8"
+checksum = "614669bead4741b2fc352ae1967318be16949cf46f59013e548c6dbfdfc01252"
 dependencies = [
  "serde",
  "serde_json",
+ "uuid",
 ]
 
 [[package]]

Cargo.toml 🔗

@@ -561,7 +561,7 @@ wasmtime = { version = "24", default-features = false, features = [
 wasmtime-wasi = "24"
 which = "6.0.0"
 wit-component = "0.201"
-zed_llm_client = "0.3"
+zed_llm_client = "0.4"
 zstd = "0.11"
 metal = "0.31"
 

crates/copilot/src/copilot_completion_provider.rs 🔗

@@ -242,6 +242,7 @@ impl EditPredictionProvider for CopilotCompletionProvider {
             } else {
                 let position = cursor_position.bias_right(buffer);
                 Some(InlineCompletion {
+                    id: None,
                     edits: vec![(position..position, completion_text.into())],
                     edit_preview: None,
                 })

crates/editor/src/editor.rs 🔗

@@ -490,6 +490,7 @@ enum InlineCompletion {
 struct InlineCompletionState {
     inlay_ids: Vec<InlayId>,
     completion: InlineCompletion,
+    completion_id: Option<SharedString>,
     invalidation_range: Range<Anchor>,
 }
 
@@ -4893,7 +4894,11 @@ impl Editor {
             return;
         };
 
-        self.report_inline_completion_event(true, cx);
+        self.report_inline_completion_event(
+            active_inline_completion.completion_id.clone(),
+            true,
+            cx,
+        );
 
         match &active_inline_completion.completion {
             InlineCompletion::Move { target, .. } => {
@@ -4942,7 +4947,11 @@ impl Editor {
             return;
         }
 
-        self.report_inline_completion_event(true, cx);
+        self.report_inline_completion_event(
+            active_inline_completion.completion_id.clone(),
+            true,
+            cx,
+        );
 
         match &active_inline_completion.completion {
             InlineCompletion::Move { target, .. } => {
@@ -5000,7 +5009,12 @@ impl Editor {
         cx: &mut Context<Self>,
     ) -> bool {
         if should_report_inline_completion_event {
-            self.report_inline_completion_event(false, cx);
+            let completion_id = self
+                .active_inline_completion
+                .as_ref()
+                .and_then(|active_completion| active_completion.completion_id.clone());
+
+            self.report_inline_completion_event(completion_id, false, cx);
         }
 
         if let Some(provider) = self.edit_prediction_provider() {
@@ -5010,7 +5024,7 @@ impl Editor {
         self.take_active_inline_completion(cx)
     }
 
-    fn report_inline_completion_event(&self, accepted: bool, cx: &App) {
+    fn report_inline_completion_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
         let Some(provider) = self.edit_prediction_provider() else {
             return;
         };
@@ -5035,6 +5049,7 @@ impl Editor {
         telemetry::event!(
             event_type,
             provider = provider.name(),
+            prediction_id = id,
             suggestion_accepted = accepted,
             file_extension = extension,
         );
@@ -5250,6 +5265,7 @@ impl Editor {
         self.active_inline_completion = Some(InlineCompletionState {
             inlay_ids,
             completion,
+            completion_id: inline_completion.id,
             invalidation_range,
         });
 

crates/editor/src/inline_completion_tests.rs 🔗

@@ -333,6 +333,7 @@ fn propose_edits<T: ToOffset>(
     cx.update(|_, cx| {
         provider.update(cx, |provider, _| {
             provider.set_inline_completion(Some(inline_completion::InlineCompletion {
+                id: None,
                 edits: edits.collect(),
                 edit_preview: None,
             }))

crates/inline_completion/src/inline_completion.rs 🔗

@@ -1,4 +1,4 @@
-use gpui::{App, Context, Entity};
+use gpui::{App, Context, Entity, SharedString};
 use language::Buffer;
 use project::Project;
 use std::ops::Range;
@@ -15,6 +15,8 @@ pub enum Direction {
 
 #[derive(Clone)]
 pub struct InlineCompletion {
+    /// The ID of the completion, if it has one.
+    pub id: Option<SharedString>,
     pub edits: Vec<(Range<language::Anchor>, String)>,
     pub edit_preview: Option<language::EditPreview>,
 }

crates/zeta/src/zeta.rs 🔗

@@ -81,12 +81,6 @@ impl std::fmt::Display for InlineCompletionId {
     }
 }
 
-impl InlineCompletionId {
-    fn new() -> Self {
-        Self(Uuid::new_v4())
-    }
-}
-
 #[derive(Clone)]
 struct ZetaGlobal(Entity<Zeta>);
 
@@ -452,11 +446,10 @@ impl Zeta {
 
             let response = perform_predict_edits(client, llm_token, is_staff, body).await?;
 
-            let output_excerpt = response.output_excerpt;
-            log::debug!("completion response: {}", output_excerpt);
+            log::debug!("completion response: {}", &response.output_excerpt);
 
             Self::process_completion_response(
-                output_excerpt,
+                response,
                 buffer,
                 &snapshot,
                 values.editable_range,
@@ -495,6 +488,7 @@ impl Zeta {
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("e7861db5-0cea-4761-b1c5-ad083ac53a80").unwrap(),
                     output_excerpt: format!("{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 [here's an edit]
@@ -511,6 +505,7 @@ and then another
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("077c556a-2c49-44e2-bbc6-dafc09032a5e").unwrap(),
                     output_excerpt: format!(r#"{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 And maybe a short line
@@ -527,6 +522,7 @@ and then another
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("df8c7b23-3d1d-4f99-a306-1f6264a41277").unwrap(),
                     output_excerpt: format!(r#"{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 And maybe a short line
@@ -544,6 +540,7 @@ and then another
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("c743958d-e4d8-44a8-aa5b-eb1e305c5f5c").unwrap(),
                     output_excerpt: format!(r#"{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 And maybe a short line
@@ -561,6 +558,7 @@ and then another
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("ff5cd7ab-ad06-4808-986e-d3391e7b8355").unwrap(),
                     output_excerpt: format!(r#"{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 And maybe a short line
@@ -577,6 +575,7 @@ and then another
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("83cafa55-cdba-4b27-8474-1865ea06be94").unwrap(),
                     output_excerpt: format!(r#"{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 And maybe a short line
@@ -592,6 +591,7 @@ and then another
                 &buffer,
                 position,
                 PredictEditsResponse {
+                    request_id: Uuid::parse_str("d5bd3afd-8723-47c7-bd77-15a3a926867b").unwrap(),
                     output_excerpt: format!(r#"{EDITABLE_REGION_START_MARKER}
 a longggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg line
 And maybe a short line
@@ -703,7 +703,7 @@ and then another
 
     #[allow(clippy::too_many_arguments)]
     fn process_completion_response(
-        output_excerpt: String,
+        prediction_response: PredictEditsResponse,
         buffer: Entity<Buffer>,
         snapshot: &BufferSnapshot,
         editable_range: Range<usize>,
@@ -716,6 +716,8 @@ and then another
         cx: &AsyncApp,
     ) -> Task<Result<Option<InlineCompletion>>> {
         let snapshot = snapshot.clone();
+        let request_id = prediction_response.request_id;
+        let output_excerpt = prediction_response.output_excerpt;
         cx.spawn(|cx| async move {
             let output_excerpt: Arc<str> = output_excerpt.into();
 
@@ -746,7 +748,7 @@ and then another
             let edit_preview = edit_preview.await;
 
             Ok(Some(InlineCompletion {
-                id: InlineCompletionId::new(),
+                id: InlineCompletionId(request_id),
                 path,
                 excerpt_range: editable_range,
                 cursor_offset,
@@ -1550,6 +1552,7 @@ impl inline_completion::EditPredictionProvider for ZetaInlineCompletionProvider
         }
 
         Some(inline_completion::InlineCompletion {
+            id: Some(completion.id.to_string().into()),
             edits: edits[edit_start_ix..edit_end_ix].to_vec(),
             edit_preview: Some(completion.edit_preview.clone()),
         })
@@ -1598,7 +1601,7 @@ mod tests {
             edit_preview,
             path: Path::new("").into(),
             snapshot: cx.read(|cx| buffer.read(cx).snapshot()),
-            id: InlineCompletionId::new(),
+            id: InlineCompletionId(Uuid::new_v4()),
             excerpt_range: 0..0,
             cursor_offset: 0,
             input_outline: "".into(),
@@ -1717,6 +1720,8 @@ mod tests {
                 .status(200)
                 .body(
                     serde_json::to_string(&PredictEditsResponse {
+                        request_id: Uuid::parse_str("7e86480f-3536-4d2c-9334-8213e3445d45")
+                            .unwrap(),
                         output_excerpt: completion_response.to_string(),
                     })
                     .unwrap()