use crate::ANTHROPIC_PROVIDER_ID; use anthropic::ANTHROPIC_API_URL; use anyhow::{Context as _, anyhow}; use gpui::BackgroundExecutor; use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest}; use std::env; use std::sync::Arc; use util::ResultExt; #[derive(Clone, Debug)] pub struct AnthropicEventData { pub completion_type: AnthropicCompletionType, pub event: AnthropicEventType, pub language_name: Option, pub message_id: Option, } #[derive(Clone, Debug)] pub enum AnthropicCompletionType { Editor, Terminal, Panel, } #[derive(Clone, Debug)] pub enum AnthropicEventType { Invoked, Response, Accept, Reject, } impl AnthropicCompletionType { fn as_str(&self) -> &'static str { match self { Self::Editor => "natural_language_completion_in_editor", Self::Terminal => "natural_language_completion_in_terminal", Self::Panel => "conversation_message", } } } impl AnthropicEventType { fn as_str(&self) -> &'static str { match self { Self::Invoked => "invoke", Self::Response => "response", Self::Accept => "accept", Self::Reject => "reject", } } } pub fn report_anthropic_event( model: &Arc, event: AnthropicEventData, cx: &gpui::App, ) { let reporter = AnthropicEventReporter::new(model, cx); reporter.report(event); } #[derive(Clone)] pub struct AnthropicEventReporter { http_client: Arc, executor: BackgroundExecutor, api_key: Option, is_anthropic: bool, } impl AnthropicEventReporter { pub fn new(model: &Arc, cx: &gpui::App) -> Self { Self { http_client: cx.http_client(), executor: cx.background_executor().clone(), api_key: model.api_key(cx), is_anthropic: model.provider_id() == ANTHROPIC_PROVIDER_ID, } } pub fn report(&self, event: AnthropicEventData) { if !self.is_anthropic { return; } let Some(api_key) = self.api_key.clone() else { return; }; let client = self.http_client.clone(); self.executor .spawn(async move { send_anthropic_event(event, client, api_key).await.log_err(); }) .detach(); } } async fn send_anthropic_event( event: AnthropicEventData, client: Arc, api_key: String, ) -> anyhow::Result<()> { let uri = format!("{ANTHROPIC_API_URL}/v1/log/zed"); let request_builder = HttpRequest::builder() .method(Method::POST) .uri(uri) .header("X-Api-Key", api_key) .header("Content-Type", "application/json"); let serialized_event = serde_json::json!({ "completion_type": event.completion_type.as_str(), "event": event.event.as_str(), "metadata": { "language_name": event.language_name, "message_id": event.message_id, "platform": env::consts::OS, } }); let request = request_builder .body(AsyncBody::from(serialized_event.to_string())) .context("Failed to construct Anthropic telemetry HTTP request body")?; let response = client .send(request) .await .context("Failed to send telemetry HTTP request to Anthropic")?; if response.status().is_success() { Ok(()) } else { Err(anyhow!( "Anthropic telemetry logging failed with HTTP status: {}", response.status() )) } }