agent_ui: Route agent thread feedback comments through cloud (#49481)

Tom Houlé created

The agent thread feedback comments event was still going through the
standard telemetry pipeline. This routes it through cloud instead, as
was just done for agent thread rated and edit prediction rated, so we
can enforce organization data and privacy settings.

Part of CLO-297

Change summary

crates/agent_ui/src/acp/thread_view/active_thread.rs | 26 +++++++++----
crates/cloud_api_client/src/cloud_api_client.rs      | 28 ++++++++++++++
crates/cloud_api_types/src/cloud_api_types.rs        |  9 ++++
3 files changed, 55 insertions(+), 8 deletions(-)

Detailed changes

crates/agent_ui/src/acp/thread_view/active_thread.rs 🔗

@@ -1,4 +1,4 @@
-use cloud_api_types::SubmitAgentThreadFeedbackBody;
+use cloud_api_types::{SubmitAgentThreadFeedbackBody, SubmitAgentThreadFeedbackCommentsBody};
 use gpui::{Corner, List};
 use language_model::LanguageModelEffortLevel;
 use settings::update_settings_file;
@@ -84,18 +84,28 @@ impl ThreadFeedbackState {
 
         self.comments_editor.take();
 
+        let project = thread.read(cx).project().read(cx);
+        let client = project.client();
+        let user_store = project.user_store();
+        let organization = user_store.read(cx).current_organization();
+
         let session_id = thread.read(cx).session_id().clone();
         let agent_telemetry_id = 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_telemetry_id,
-                session_id = session_id,
-                comments = comments,
-                thread = thread
-            );
+
+            client
+                .cloud_client()
+                .submit_agent_feedback_comments(SubmitAgentThreadFeedbackCommentsBody {
+                    organization_id: organization.map(|organization| organization.id.clone()),
+                    agent: agent_telemetry_id.to_string(),
+                    session_id: session_id.to_string(),
+                    comments,
+                    thread,
+                })
+                .await?;
+
             anyhow::Ok(())
         })
         .detach_and_log_err(cx);

crates/cloud_api_client/src/cloud_api_client.rs 🔗

@@ -207,6 +207,34 @@ impl CloudApiClient {
         Ok(())
     }
 
+    pub async fn submit_agent_feedback_comments(
+        &self,
+        body: SubmitAgentThreadFeedbackCommentsBody,
+    ) -> Result<()> {
+        let request = self.build_request(
+            Request::builder().method(Method::POST).uri(
+                self.http_client
+                    .build_zed_cloud_url("/client/feedback/agent_thread_comments")?
+                    .as_ref(),
+            ),
+            AsyncBody::from(serde_json::to_string(&body)?),
+        )?;
+
+        let mut response = self.http_client.send(request).await?;
+
+        if !response.status().is_success() {
+            let mut body = String::new();
+            response.body_mut().read_to_string(&mut body).await?;
+
+            anyhow::bail!(
+                "Failed to submit agent feedback comments.\nStatus: {:?}\nBody: {body}",
+                response.status()
+            )
+        }
+
+        Ok(())
+    }
+
     pub async fn submit_edit_prediction_feedback(
         &self,
         body: SubmitEditPredictionFeedbackBody,

crates/cloud_api_types/src/cloud_api_types.rs 🔗

@@ -66,6 +66,15 @@ pub struct SubmitAgentThreadFeedbackBody {
     pub thread: serde_json::Value,
 }
 
+#[derive(Debug, PartialEq, Serialize, Deserialize)]
+pub struct SubmitAgentThreadFeedbackCommentsBody {
+    pub organization_id: Option<OrganizationId>,
+    pub agent: String,
+    pub session_id: String,
+    pub comments: String,
+    pub thread: serde_json::Value,
+}
+
 #[derive(Debug, PartialEq, Serialize, Deserialize)]
 pub struct SubmitEditPredictionFeedbackBody {
     pub organization_id: Option<OrganizationId>,