136 lines · 3.6 KB · Rust
RawDownload
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<String>,    pub message_id: Option<String>,} #[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<dyn crate::LanguageModel>,    event: AnthropicEventData,    cx: &gpui::App,) {    let reporter = AnthropicEventReporter::new(model, cx);    reporter.report(event);} #[derive(Clone)]pub struct AnthropicEventReporter {    http_client: Arc<dyn HttpClient>,    executor: BackgroundExecutor,    api_key: Option<String>,    is_anthropic: bool,} impl AnthropicEventReporter {    pub fn new(model: &Arc<dyn crate::LanguageModel>, 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<dyn HttpClient>,    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()        ))    }}