agent: Attach thread ID and prompt ID to telemetry events (#29069)

Marshall Bowers and Mikayla Maki created

This PR attaches the thread ID and the new prompt ID to telemetry events
for completions in the Agent panel.

Release Notes:

- N/A

---------

Co-authored-by: Mikayla Maki <mikayla.c.maki@gmail.com>

Change summary

Cargo.lock                                        | 13 ++++---
Cargo.toml                                        |  2 
crates/agent/src/active_thread.rs                 |  3 +
crates/agent/src/buffer_codegen.rs                |  2 +
crates/agent/src/message_editor.rs                |  3 +
crates/agent/src/terminal_inline_assistant.rs     |  2 +
crates/agent/src/thread.rs                        | 29 +++++++++++++++++
crates/assistant/src/inline_assistant.rs          |  2 +
crates/assistant/src/terminal_inline_assistant.rs |  2 +
crates/assistant_context_editor/src/context.rs    |  2 +
crates/eval/src/example.rs                        |  2 +
crates/git_ui/src/git_panel.rs                    |  2 +
crates/language_model/src/request.rs              |  2 +
crates/language_models/src/provider/cloud.rs      | 10 +++++
crates/prompt_library/src/prompt_library.rs       |  2 +
crates/semantic_index/src/summary_index.rs        |  2 +
tooling/workspace-hack/Cargo.toml                 | 14 +++++++
17 files changed, 85 insertions(+), 9 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -1926,7 +1926,7 @@ dependencies = [
  "bitflags 2.9.0",
  "cexpr",
  "clang-sys",
- "itertools 0.11.0",
+ "itertools 0.12.1",
  "lazy_static",
  "lazycell",
  "log",
@@ -6781,7 +6781,7 @@ dependencies = [
  "js-sys",
  "log",
  "wasm-bindgen",
- "windows-core 0.57.0",
+ "windows-core 0.61.0",
 ]
 
 [[package]]
@@ -11066,7 +11066,7 @@ checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4"
 dependencies = [
  "bytes 1.10.1",
  "heck 0.5.0",
- "itertools 0.11.0",
+ "itertools 0.12.1",
  "log",
  "multimap 0.10.0",
  "once_cell",
@@ -11099,7 +11099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1"
 dependencies = [
  "anyhow",
- "itertools 0.11.0",
+ "itertools 0.12.1",
  "proc-macro2",
  "quote",
  "syn 2.0.100",
@@ -17747,6 +17747,7 @@ dependencies = [
  "hyper-rustls 0.27.5",
  "indexmap",
  "inout",
+ "itertools 0.12.1",
  "itertools 0.13.0",
  "lazy_static",
  "libc",
@@ -18369,9 +18370,9 @@ dependencies = [
 
 [[package]]
 name = "zed_llm_client"
-version = "0.6.0"
+version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b91b8b05f1028157205026e525869eb860fa89bec87ea60b445efc91d05df31f"
+checksum = "ad17428120f5ca776dc5195e2411a282f5150a26d5536671f8943c622c31274f"
 dependencies = [
  "anyhow",
  "serde",

Cargo.toml 🔗

@@ -604,7 +604,7 @@ wasmtime-wasi = "29"
 which = "6.0.0"
 wit-component = "0.221"
 workspace-hack = "0.1.0"
-zed_llm_client = "0.6.0"
+zed_llm_client = "0.6.1"
 zstd = "0.11"
 metal = "0.29"
 

crates/agent/src/active_thread.rs 🔗

@@ -1212,6 +1212,8 @@ impl ActiveThread {
                 }
 
                 let request = language_model::LanguageModelRequest {
+                    thread_id: None,
+                    prompt_id: None,
                     messages: vec![LanguageModelRequestMessage {
                         role: language_model::Role::User,
                         content: vec![content.into()],
@@ -1277,6 +1279,7 @@ impl ActiveThread {
         }
 
         self.thread.update(cx, |thread, cx| {
+            thread.advance_prompt_id();
             thread.send_to_model(model.model, RequestKind::Chat, cx)
         });
         cx.notify();

crates/agent/src/buffer_codegen.rs 🔗

@@ -425,6 +425,8 @@ impl CodegenAlternative {
         request_message.content.push(prompt.into());
 
         Ok(LanguageModelRequest {
+            thread_id: None,
+            prompt_id: None,
             tools: Vec::new(),
             stop: Vec::new(),
             temperature: None,

crates/agent/src/message_editor.rs 🔗

@@ -330,6 +330,7 @@ impl MessageEditor {
             // Send to model after summaries are done
             thread
                 .update(cx, |thread, cx| {
+                    thread.advance_prompt_id();
                     thread.send_to_model(model, request_kind, cx);
                 })
                 .log_err();
@@ -1013,6 +1014,8 @@ impl MessageEditor {
                 }
 
                 let request = language_model::LanguageModelRequest {
+                    thread_id: None,
+                    prompt_id: None,
                     messages: vec![LanguageModelRequestMessage {
                         role: language_model::Role::User,
                         content: vec![content.into()],

crates/agent/src/terminal_inline_assistant.rs 🔗

@@ -261,6 +261,8 @@ impl TerminalInlineAssistant {
         request_message.content.push(prompt.into());
 
         Ok(LanguageModelRequest {
+            thread_id: None,
+            prompt_id: None,
             messages: vec![request_message],
             tools: Vec::new(),
             stop: Vec::new(),

crates/agent/src/thread.rs 🔗

@@ -70,6 +70,24 @@ impl From<&str> for ThreadId {
     }
 }
 
+/// The ID of the user prompt that initiated a request.
+///
+/// This equates to the user physically submitting a message to the model (e.g., by pressing the Enter key).
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize, Deserialize)]
+pub struct PromptId(Arc<str>);
+
+impl PromptId {
+    pub fn new() -> Self {
+        Self(Uuid::new_v4().to_string().into())
+    }
+}
+
+impl std::fmt::Display for PromptId {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Serialize, Deserialize)]
 pub struct MessageId(pub(crate) usize);
 
@@ -274,6 +292,7 @@ pub struct Thread {
     detailed_summary_state: DetailedSummaryState,
     messages: Vec<Message>,
     next_message_id: MessageId,
+    last_prompt_id: PromptId,
     context: BTreeMap<ContextId, AssistantContext>,
     context_by_message: HashMap<MessageId, Vec<ContextId>>,
     project_context: SharedProjectContext,
@@ -320,6 +339,7 @@ impl Thread {
             detailed_summary_state: DetailedSummaryState::NotGenerated,
             messages: Vec::new(),
             next_message_id: MessageId(0),
+            last_prompt_id: PromptId::new(),
             context: BTreeMap::default(),
             context_by_message: HashMap::default(),
             project_context: system_prompt,
@@ -393,6 +413,7 @@ impl Thread {
                 })
                 .collect(),
             next_message_id,
+            last_prompt_id: PromptId::new(),
             context: BTreeMap::default(),
             context_by_message: HashMap::default(),
             project_context,
@@ -432,6 +453,10 @@ impl Thread {
         self.updated_at = Utc::now();
     }
 
+    pub fn advance_prompt_id(&mut self) {
+        self.last_prompt_id = PromptId::new();
+    }
+
     pub fn summary(&self) -> Option<SharedString> {
         self.summary.clone()
     }
@@ -942,6 +967,8 @@ impl Thread {
         cx: &mut Context<Self>,
     ) -> LanguageModelRequest {
         let mut request = LanguageModelRequest {
+            thread_id: Some(self.id.to_string()),
+            prompt_id: Some(self.last_prompt_id.to_string()),
             messages: vec![],
             tools: Vec::new(),
             stop: Vec::new(),
@@ -1083,6 +1110,7 @@ impl Thread {
         cx: &mut Context<Self>,
     ) {
         let pending_completion_id = post_inc(&mut self.completion_count);
+        let prompt_id = self.last_prompt_id.clone();
         let task = cx.spawn(async move |thread, cx| {
             let stream_completion_future = model.stream_completion_with_usage(request, &cx);
             let initial_token_usage =
@@ -1273,6 +1301,7 @@ impl Thread {
                         telemetry::event!(
                             "Assistant Thread Completion",
                             thread_id = thread.id().to_string(),
+                            prompt_id = prompt_id,
                             model = model.telemetry_id(),
                             model_provider = model.provider_id().to_string(),
                             input_tokens = usage.input_tokens,

crates/assistant_context_editor/src/context.rs 🔗

@@ -2555,6 +2555,8 @@ impl AssistantContext {
         }
 
         let mut completion_request = LanguageModelRequest {
+            thread_id: None,
+            prompt_id: None,
             messages: Vec::new(),
             tools: Vec::new(),
             stop: Vec::new(),

crates/eval/src/example.rs 🔗

@@ -502,6 +502,8 @@ impl Example {
         )?;
 
         let request = LanguageModelRequest {
+            thread_id: None,
+            prompt_id: None,
             messages: vec![LanguageModelRequestMessage {
                 role: Role::User,
                 content: vec![MessageContent::Text(prompt)],

crates/git_ui/src/git_panel.rs 🔗

@@ -1744,6 +1744,8 @@ impl GitPanel {
                 const PROMPT: &str = include_str!("commit_message_prompt.txt");
 
                 let request = LanguageModelRequest {
+                    thread_id: None,
+                    prompt_id: None,
                     messages: vec![LanguageModelRequestMessage {
                         role: Role::User,
                         content: vec![content.into()],

crates/language_model/src/request.rs 🔗

@@ -238,6 +238,8 @@ pub struct LanguageModelRequestTool {
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
 pub struct LanguageModelRequest {
+    pub thread_id: Option<String>,
+    pub prompt_id: Option<String>,
     pub messages: Vec<LanguageModelRequestMessage>,
     pub tools: Vec<LanguageModelRequestTool>,
     pub stop: Vec<String>,

crates/language_models/src/provider/cloud.rs 🔗

@@ -719,7 +719,7 @@ impl LanguageModel for CloudLanguageModel {
 
     fn stream_completion_with_usage(
         &self,
-        request: LanguageModelRequest,
+        mut request: LanguageModelRequest,
         _cx: &AsyncApp,
     ) -> BoxFuture<
         'static,
@@ -728,6 +728,8 @@ impl LanguageModel for CloudLanguageModel {
             Option<RequestUsage>,
         )>,
     > {
+        let thread_id = request.prompt_id.take();
+        let prompt_id = request.prompt_id.take();
         match &self.model {
             CloudModel::Anthropic(model) => {
                 let request = into_anthropic(
@@ -744,6 +746,8 @@ impl LanguageModel for CloudLanguageModel {
                         client.clone(),
                         llm_api_token,
                         CompletionBody {
+                            thread_id,
+                            prompt_id,
                             provider: zed_llm_client::LanguageModelProvider::Anthropic,
                             model: request.model.clone(),
                             provider_request: serde_json::to_value(&request)?,
@@ -788,6 +792,8 @@ impl LanguageModel for CloudLanguageModel {
                         client.clone(),
                         llm_api_token,
                         CompletionBody {
+                            thread_id,
+                            prompt_id,
                             provider: zed_llm_client::LanguageModelProvider::OpenAi,
                             model: request.model.clone(),
                             provider_request: serde_json::to_value(&request)?,
@@ -816,6 +822,8 @@ impl LanguageModel for CloudLanguageModel {
                         client.clone(),
                         llm_api_token,
                         CompletionBody {
+                            thread_id,
+                            prompt_id,
                             provider: zed_llm_client::LanguageModelProvider::Google,
                             model: request.model.clone(),
                             provider_request: serde_json::to_value(&request)?,

crates/prompt_library/src/prompt_library.rs 🔗

@@ -924,6 +924,8 @@ impl PromptLibrary {
                         .update(|_, cx| {
                             model.count_tokens(
                                 LanguageModelRequest {
+                                    thread_id: None,
+                                    prompt_id: None,
                                     messages: vec![LanguageModelRequestMessage {
                                         role: Role::System,
                                         content: vec![body.to_string().into()],

crates/semantic_index/src/summary_index.rs 🔗

@@ -557,6 +557,8 @@ impl SummaryIndex {
         );
 
         let request = LanguageModelRequest {
+            thread_id: None,
+            prompt_id: None,
             messages: vec![LanguageModelRequestMessage {
                 role: Role::User,
                 content: vec![prompt.into()],

tooling/workspace-hack/Cargo.toml 🔗

@@ -176,7 +176,7 @@ heck = { version = "0.4", features = ["unicode"] }
 hmac = { version = "0.12", default-features = false, features = ["reset"] }
 hyper = { version = "0.14", features = ["client", "http1", "http2", "runtime", "server", "stream"] }
 indexmap = { version = "2", features = ["serde"] }
-itertools = { version = "0.13" }
+itertools-594e8ee84c453af0 = { package = "itertools", version = "0.13" }
 lazy_static = { version = "1", default-features = false, features = ["spin_no_std"] }
 libc = { version = "0.2", features = ["extra_traits"] }
 libsqlite3-sys = { version = "0.30", features = ["bundled", "unlock_notify"] }
@@ -253,6 +253,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -276,6 +277,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -299,6 +301,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -322,6 +325,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["msl-out", "wgsl-in"] }
 nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
 object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
@@ -351,6 +355,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -390,6 +395,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -427,6 +433,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -466,6 +473,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -495,6 +503,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
 ring = { version = "0.17", features = ["std"] }
 rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] }
@@ -516,6 +525,7 @@ foldhash = { version = "0.1", default-features = false, features = ["std"] }
 getrandom-468e82937335b1c9 = { package = "getrandom", version = "0.3", default-features = false, features = ["std"] }
 getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-features = false, features = ["js", "rdrand"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
 proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
 ring = { version = "0.17", features = ["std"] }
@@ -546,6 +556,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }
@@ -585,6 +596,7 @@ getrandom-6f8ce4dd05d13bba = { package = "getrandom", version = "0.2", default-f
 gimli = { version = "0.31", default-features = false, features = ["read", "std", "write"] }
 hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
 inout = { version = "0.1", default-features = false, features = ["block-padding"] }
+itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
 linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
 mio = { version = "1", features = ["net", "os-ext"] }
 naga = { version = "23", features = ["spv-out", "wgsl-in"] }