1use crate::ANTHROPIC_PROVIDER_ID;
2use anthropic::ANTHROPIC_API_URL;
3use anyhow::{Context as _, anyhow};
4use gpui::BackgroundExecutor;
5use http_client::{AsyncBody, HttpClient, Method, Request as HttpRequest};
6use std::env;
7use std::sync::Arc;
8use util::ResultExt;
9
10#[derive(Clone, Debug)]
11pub struct AnthropicEventData {
12 pub completion_type: AnthropicCompletionType,
13 pub event: AnthropicEventType,
14 pub language_name: Option<String>,
15 pub message_id: Option<String>,
16}
17
18#[derive(Clone, Debug)]
19pub enum AnthropicCompletionType {
20 Editor,
21 Terminal,
22 Panel,
23}
24
25#[derive(Clone, Debug)]
26pub enum AnthropicEventType {
27 Invoked,
28 Response,
29 Accept,
30 Reject,
31}
32
33impl AnthropicCompletionType {
34 fn as_str(&self) -> &'static str {
35 match self {
36 Self::Editor => "natural_language_completion_in_editor",
37 Self::Terminal => "natural_language_completion_in_terminal",
38 Self::Panel => "conversation_message",
39 }
40 }
41}
42
43impl AnthropicEventType {
44 fn as_str(&self) -> &'static str {
45 match self {
46 Self::Invoked => "invoke",
47 Self::Response => "response",
48 Self::Accept => "accept",
49 Self::Reject => "reject",
50 }
51 }
52}
53
54pub fn report_anthropic_event(
55 model: &Arc<dyn crate::LanguageModel>,
56 event: AnthropicEventData,
57 cx: &gpui::App,
58) {
59 let reporter = AnthropicEventReporter::new(model, cx);
60 reporter.report(event);
61}
62
63#[derive(Clone)]
64pub struct AnthropicEventReporter {
65 http_client: Arc<dyn HttpClient>,
66 executor: BackgroundExecutor,
67 api_key: Option<String>,
68 is_anthropic: bool,
69}
70
71impl AnthropicEventReporter {
72 pub fn new(model: &Arc<dyn crate::LanguageModel>, cx: &gpui::App) -> Self {
73 Self {
74 http_client: cx.http_client(),
75 executor: cx.background_executor().clone(),
76 api_key: model.api_key(cx),
77 is_anthropic: model.provider_id() == ANTHROPIC_PROVIDER_ID,
78 }
79 }
80
81 pub fn report(&self, event: AnthropicEventData) {
82 if !self.is_anthropic {
83 return;
84 }
85 let Some(api_key) = self.api_key.clone() else {
86 return;
87 };
88 let client = self.http_client.clone();
89 self.executor
90 .spawn(async move {
91 send_anthropic_event(event, client, api_key).await.log_err();
92 })
93 .detach();
94 }
95}
96
97async fn send_anthropic_event(
98 event: AnthropicEventData,
99 client: Arc<dyn HttpClient>,
100 api_key: String,
101) -> anyhow::Result<()> {
102 let uri = format!("{ANTHROPIC_API_URL}/v1/log/zed");
103 let request_builder = HttpRequest::builder()
104 .method(Method::POST)
105 .uri(uri)
106 .header("X-Api-Key", api_key)
107 .header("Content-Type", "application/json");
108
109 let serialized_event = serde_json::json!({
110 "completion_type": event.completion_type.as_str(),
111 "event": event.event.as_str(),
112 "metadata": {
113 "language_name": event.language_name,
114 "message_id": event.message_id,
115 "platform": env::consts::OS,
116 }
117 });
118
119 let request = request_builder
120 .body(AsyncBody::from(serialized_event.to_string()))
121 .context("Failed to construct Anthropic telemetry HTTP request body")?;
122
123 let response = client
124 .send(request)
125 .await
126 .context("Failed to send telemetry HTTP request to Anthropic")?;
127
128 if response.status().is_success() {
129 Ok(())
130 } else {
131 Err(anyhow!(
132 "Anthropic telemetry logging failed with HTTP status: {}",
133 response.status()
134 ))
135 }
136}