diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index b9760de41..df44a7f83 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1384,6 +1384,22 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9b18233253483ce2f65329a24072ec414db782531bdbb7d0bbc4bd2ce6b7e21" +[[package]] +name = "codex-analytics" +version = "0.0.0" +dependencies = [ + "codex-git-utils", + "codex-login", + "codex-plugin", + "codex-protocol", + "pretty_assertions", + "serde", + "serde_json", + "sha1", + "tokio", + "tracing", +] + [[package]] name = "codex-ansi-escape" version = "0.0.0" @@ -1869,6 +1885,7 @@ dependencies = [ "chardetng", "chrono", "clap", + "codex-analytics", "codex-api", "codex-app-server-protocol", "codex-apply-patch", diff --git a/codex-rs/Cargo.toml b/codex-rs/Cargo.toml index 4b23f4865..10b9485d2 100644 --- a/codex-rs/Cargo.toml +++ b/codex-rs/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "analytics", "backend-client", "ansi-escape", "async-utils", @@ -98,6 +99,7 @@ license = "Apache-2.0" # Internal app_test_support = { path = "app-server/tests/common" } codex-ansi-escape = { path = "ansi-escape" } +codex-analytics = { path = "analytics" } codex-api = { path = "codex-api" } codex-artifacts = { path = "artifacts" } codex-code-mode = { path = "code-mode" } diff --git a/codex-rs/analytics/BUILD.bazel b/codex-rs/analytics/BUILD.bazel new file mode 100644 index 000000000..aec07c874 --- /dev/null +++ b/codex-rs/analytics/BUILD.bazel @@ -0,0 +1,6 @@ +load("//:defs.bzl", "codex_rust_crate") + +codex_rust_crate( + name = "analytics", + crate_name = "codex_analytics", +) diff --git a/codex-rs/analytics/Cargo.toml b/codex-rs/analytics/Cargo.toml new file mode 100644 index 000000000..9633eebb1 --- /dev/null +++ b/codex-rs/analytics/Cargo.toml @@ -0,0 +1,30 @@ +[package] +edition.workspace = true +license.workspace = true +name = "codex-analytics" +version.workspace = true + +[lib] +doctest = false +name = "codex_analytics" +path = "src/lib.rs" + +[lints] +workspace = true + +[dependencies] +codex-git-utils = { workspace = true } +codex-login = { workspace = true } +codex-plugin = { workspace = true } +codex-protocol = { workspace = true } +serde = { workspace = true, features = ["derive"] } +sha1 = { workspace = true } +tokio = { workspace = true, features = [ + "macros", + "rt-multi-thread", +] } +tracing = { workspace = true, features = ["log"] } + +[dev-dependencies] +pretty_assertions = { workspace = true } +serde_json = { workspace = true } diff --git a/codex-rs/core/src/analytics_client.rs b/codex-rs/analytics/src/analytics_client.rs similarity index 78% rename from codex-rs/core/src/analytics_client.rs rename to codex-rs/analytics/src/analytics_client.rs index 922f16de5..ac8d10b6c 100644 --- a/codex-rs/core/src/analytics_client.rs +++ b/codex-rs/analytics/src/analytics_client.rs @@ -1,9 +1,9 @@ -use crate::AuthManager; -use crate::config::Config; -use crate::default_client::create_client; -use crate::plugins::PluginTelemetryMetadata; use codex_git_utils::collect_git_info; use codex_git_utils::get_git_repo_root; +use codex_login::AuthManager; +use codex_login::default_client::create_client; +use codex_login::default_client::originator; +use codex_plugin::PluginTelemetryMetadata; use codex_protocol::protocol::SkillScope; use serde::Serialize; use sha1::Digest; @@ -17,13 +17,13 @@ use std::time::Duration; use tokio::sync::mpsc; #[derive(Clone)] -pub(crate) struct TrackEventsContext { - pub(crate) model_slug: String, - pub(crate) thread_id: String, - pub(crate) turn_id: String, +pub struct TrackEventsContext { + pub model_slug: String, + pub thread_id: String, + pub turn_id: String, } -pub(crate) fn build_track_events_context( +pub fn build_track_events_context( model_slug: String, thread_id: String, turn_id: String, @@ -36,24 +36,24 @@ pub(crate) fn build_track_events_context( } #[derive(Clone, Debug)] -pub(crate) struct SkillInvocation { - pub(crate) skill_name: String, - pub(crate) skill_scope: SkillScope, - pub(crate) skill_path: PathBuf, - pub(crate) invocation_type: InvocationType, +pub struct SkillInvocation { + pub skill_name: String, + pub skill_scope: SkillScope, + pub skill_path: PathBuf, + pub invocation_type: InvocationType, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "lowercase")] -pub(crate) enum InvocationType { +pub enum InvocationType { Explicit, Implicit, } -pub(crate) struct AppInvocation { - pub(crate) connector_id: Option, - pub(crate) app_name: Option, - pub(crate) invocation_type: Option, +pub struct AppInvocation { + pub connector_id: Option, + pub app_name: Option, + pub invocation_type: Option, } #[derive(Clone)] @@ -66,38 +66,38 @@ pub(crate) struct AnalyticsEventsQueue { #[derive(Clone)] pub struct AnalyticsEventsClient { queue: AnalyticsEventsQueue, - config: Arc, + analytics_enabled: Option, } impl AnalyticsEventsQueue { - pub(crate) fn new(auth_manager: Arc) -> Self { + pub(crate) fn new(auth_manager: Arc, base_url: String) -> Self { let (sender, mut receiver) = mpsc::channel(ANALYTICS_EVENTS_QUEUE_SIZE); tokio::spawn(async move { while let Some(job) = receiver.recv().await { match job { TrackEventsJob::SkillInvocations(job) => { - send_track_skill_invocations(&auth_manager, job).await; + send_track_skill_invocations(&auth_manager, &base_url, job).await; } TrackEventsJob::AppMentioned(job) => { - send_track_app_mentioned(&auth_manager, job).await; + send_track_app_mentioned(&auth_manager, &base_url, job).await; } TrackEventsJob::AppUsed(job) => { - send_track_app_used(&auth_manager, job).await; + send_track_app_used(&auth_manager, &base_url, job).await; } TrackEventsJob::PluginUsed(job) => { - send_track_plugin_used(&auth_manager, job).await; + send_track_plugin_used(&auth_manager, &base_url, job).await; } TrackEventsJob::PluginInstalled(job) => { - send_track_plugin_installed(&auth_manager, job).await; + send_track_plugin_installed(&auth_manager, &base_url, job).await; } TrackEventsJob::PluginUninstalled(job) => { - send_track_plugin_uninstalled(&auth_manager, job).await; + send_track_plugin_uninstalled(&auth_manager, &base_url, job).await; } TrackEventsJob::PluginEnabled(job) => { - send_track_plugin_enabled(&auth_manager, job).await; + send_track_plugin_enabled(&auth_manager, &base_url, job).await; } TrackEventsJob::PluginDisabled(job) => { - send_track_plugin_disabled(&auth_manager, job).await; + send_track_plugin_disabled(&auth_manager, &base_url, job).await; } } } @@ -147,60 +147,51 @@ impl AnalyticsEventsQueue { } impl AnalyticsEventsClient { - pub fn new(config: Arc, auth_manager: Arc) -> Self { + pub fn new( + auth_manager: Arc, + base_url: String, + analytics_enabled: Option, + ) -> Self { Self { - queue: AnalyticsEventsQueue::new(Arc::clone(&auth_manager)), - config, + queue: AnalyticsEventsQueue::new(Arc::clone(&auth_manager), base_url), + analytics_enabled, } } - pub(crate) fn track_skill_invocations( + pub fn track_skill_invocations( &self, tracking: TrackEventsContext, invocations: Vec, ) { track_skill_invocations( &self.queue, - Arc::clone(&self.config), + self.analytics_enabled, Some(tracking), invocations, ); } - pub(crate) fn track_app_mentioned( - &self, - tracking: TrackEventsContext, - mentions: Vec, - ) { + pub fn track_app_mentioned(&self, tracking: TrackEventsContext, mentions: Vec) { track_app_mentioned( &self.queue, - Arc::clone(&self.config), + self.analytics_enabled, Some(tracking), mentions, ); } - pub(crate) fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) { - track_app_used(&self.queue, Arc::clone(&self.config), Some(tracking), app); + pub fn track_app_used(&self, tracking: TrackEventsContext, app: AppInvocation) { + track_app_used(&self.queue, self.analytics_enabled, Some(tracking), app); } - pub(crate) fn track_plugin_used( - &self, - tracking: TrackEventsContext, - plugin: PluginTelemetryMetadata, - ) { - track_plugin_used( - &self.queue, - Arc::clone(&self.config), - Some(tracking), - plugin, - ); + pub fn track_plugin_used(&self, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata) { + track_plugin_used(&self.queue, self.analytics_enabled, Some(tracking), plugin); } pub fn track_plugin_installed(&self, plugin: PluginTelemetryMetadata) { track_plugin_management( &self.queue, - Arc::clone(&self.config), + self.analytics_enabled, PluginManagementEventType::Installed, plugin, ); @@ -209,7 +200,7 @@ impl AnalyticsEventsClient { pub fn track_plugin_uninstalled(&self, plugin: PluginTelemetryMetadata) { track_plugin_management( &self.queue, - Arc::clone(&self.config), + self.analytics_enabled, PluginManagementEventType::Uninstalled, plugin, ); @@ -218,7 +209,7 @@ impl AnalyticsEventsClient { pub fn track_plugin_enabled(&self, plugin: PluginTelemetryMetadata) { track_plugin_management( &self.queue, - Arc::clone(&self.config), + self.analytics_enabled, PluginManagementEventType::Enabled, plugin, ); @@ -227,7 +218,7 @@ impl AnalyticsEventsClient { pub fn track_plugin_disabled(&self, plugin: PluginTelemetryMetadata) { track_plugin_management( &self.queue, - Arc::clone(&self.config), + self.analytics_enabled, PluginManagementEventType::Disabled, plugin, ); @@ -246,31 +237,31 @@ enum TrackEventsJob { } struct TrackSkillInvocationsJob { - config: Arc, + analytics_enabled: Option, tracking: TrackEventsContext, invocations: Vec, } struct TrackAppMentionedJob { - config: Arc, + analytics_enabled: Option, tracking: TrackEventsContext, mentions: Vec, } struct TrackAppUsedJob { - config: Arc, + analytics_enabled: Option, tracking: TrackEventsContext, app: AppInvocation, } struct TrackPluginUsedJob { - config: Arc, + analytics_enabled: Option, tracking: TrackEventsContext, plugin: PluginTelemetryMetadata, } struct TrackPluginManagementJob { - config: Arc, + analytics_enabled: Option, plugin: PluginTelemetryMetadata, } @@ -379,11 +370,11 @@ struct CodexPluginUsedEventRequest { pub(crate) fn track_skill_invocations( queue: &AnalyticsEventsQueue, - config: Arc, + analytics_enabled: Option, tracking: Option, invocations: Vec, ) { - if config.analytics_enabled == Some(false) { + if analytics_enabled == Some(false) { return; } let Some(tracking) = tracking else { @@ -393,7 +384,7 @@ pub(crate) fn track_skill_invocations( return; } let job = TrackEventsJob::SkillInvocations(TrackSkillInvocationsJob { - config, + analytics_enabled, tracking, invocations, }); @@ -402,11 +393,11 @@ pub(crate) fn track_skill_invocations( pub(crate) fn track_app_mentioned( queue: &AnalyticsEventsQueue, - config: Arc, + analytics_enabled: Option, tracking: Option, mentions: Vec, ) { - if config.analytics_enabled == Some(false) { + if analytics_enabled == Some(false) { return; } let Some(tracking) = tracking else { @@ -416,7 +407,7 @@ pub(crate) fn track_app_mentioned( return; } let job = TrackEventsJob::AppMentioned(TrackAppMentionedJob { - config, + analytics_enabled, tracking, mentions, }); @@ -425,11 +416,11 @@ pub(crate) fn track_app_mentioned( pub(crate) fn track_app_used( queue: &AnalyticsEventsQueue, - config: Arc, + analytics_enabled: Option, tracking: Option, app: AppInvocation, ) { - if config.analytics_enabled == Some(false) { + if analytics_enabled == Some(false) { return; } let Some(tracking) = tracking else { @@ -439,7 +430,7 @@ pub(crate) fn track_app_used( return; } let job = TrackEventsJob::AppUsed(TrackAppUsedJob { - config, + analytics_enabled, tracking, app, }); @@ -448,11 +439,11 @@ pub(crate) fn track_app_used( pub(crate) fn track_plugin_used( queue: &AnalyticsEventsQueue, - config: Arc, + analytics_enabled: Option, tracking: Option, plugin: PluginTelemetryMetadata, ) { - if config.analytics_enabled == Some(false) { + if analytics_enabled == Some(false) { return; } let Some(tracking) = tracking else { @@ -462,7 +453,7 @@ pub(crate) fn track_plugin_used( return; } let job = TrackEventsJob::PluginUsed(TrackPluginUsedJob { - config, + analytics_enabled, tracking, plugin, }); @@ -471,14 +462,17 @@ pub(crate) fn track_plugin_used( fn track_plugin_management( queue: &AnalyticsEventsQueue, - config: Arc, + analytics_enabled: Option, event_type: PluginManagementEventType, plugin: PluginTelemetryMetadata, ) { - if config.analytics_enabled == Some(false) { + if analytics_enabled == Some(false) { return; } - let job = TrackPluginManagementJob { config, plugin }; + let job = TrackPluginManagementJob { + analytics_enabled, + plugin, + }; let job = match event_type { PluginManagementEventType::Installed => TrackEventsJob::PluginInstalled(job), PluginManagementEventType::Uninstalled => TrackEventsJob::PluginUninstalled(job), @@ -488,9 +482,13 @@ fn track_plugin_management( queue.try_send(job); } -async fn send_track_skill_invocations(auth_manager: &AuthManager, job: TrackSkillInvocationsJob) { +async fn send_track_skill_invocations( + auth_manager: &AuthManager, + base_url: &str, + job: TrackSkillInvocationsJob, +) { let TrackSkillInvocationsJob { - config, + analytics_enabled, tracking, invocations, } = job; @@ -525,7 +523,7 @@ async fn send_track_skill_invocations(auth_manager: &AuthManager, job: TrackSkil thread_id: Some(tracking.thread_id.clone()), invoke_type: Some(invocation.invocation_type), model_slug: Some(tracking.model_slug.clone()), - product_client_id: Some(crate::default_client::originator().value), + product_client_id: Some(originator().value), repo_url, skill_scope: Some(skill_scope.to_string()), }, @@ -533,12 +531,16 @@ async fn send_track_skill_invocations(auth_manager: &AuthManager, job: TrackSkil )); } - send_track_events(auth_manager, config, events).await; + send_track_events(auth_manager, analytics_enabled, base_url, events).await; } -async fn send_track_app_mentioned(auth_manager: &AuthManager, job: TrackAppMentionedJob) { +async fn send_track_app_mentioned( + auth_manager: &AuthManager, + base_url: &str, + job: TrackAppMentionedJob, +) { let TrackAppMentionedJob { - config, + analytics_enabled, tracking, mentions, } = job; @@ -553,12 +555,12 @@ async fn send_track_app_mentioned(auth_manager: &AuthManager, job: TrackAppMenti }) .collect::>(); - send_track_events(auth_manager, config, events).await; + send_track_events(auth_manager, analytics_enabled, base_url, events).await; } -async fn send_track_app_used(auth_manager: &AuthManager, job: TrackAppUsedJob) { +async fn send_track_app_used(auth_manager: &AuthManager, base_url: &str, job: TrackAppUsedJob) { let TrackAppUsedJob { - config, + analytics_enabled, tracking, app, } = job; @@ -568,12 +570,16 @@ async fn send_track_app_used(auth_manager: &AuthManager, job: TrackAppUsedJob) { event_params, })]; - send_track_events(auth_manager, config, events).await; + send_track_events(auth_manager, analytics_enabled, base_url, events).await; } -async fn send_track_plugin_used(auth_manager: &AuthManager, job: TrackPluginUsedJob) { +async fn send_track_plugin_used( + auth_manager: &AuthManager, + base_url: &str, + job: TrackPluginUsedJob, +) { let TrackPluginUsedJob { - config, + analytics_enabled, tracking, plugin, } = job; @@ -582,31 +588,52 @@ async fn send_track_plugin_used(auth_manager: &AuthManager, job: TrackPluginUsed event_params: codex_plugin_used_metadata(&tracking, plugin), })]; - send_track_events(auth_manager, config, events).await; + send_track_events(auth_manager, analytics_enabled, base_url, events).await; } -async fn send_track_plugin_installed(auth_manager: &AuthManager, job: TrackPluginManagementJob) { - send_track_plugin_management_event(auth_manager, job, "codex_plugin_installed").await; +async fn send_track_plugin_installed( + auth_manager: &AuthManager, + base_url: &str, + job: TrackPluginManagementJob, +) { + send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_installed").await; } -async fn send_track_plugin_uninstalled(auth_manager: &AuthManager, job: TrackPluginManagementJob) { - send_track_plugin_management_event(auth_manager, job, "codex_plugin_uninstalled").await; +async fn send_track_plugin_uninstalled( + auth_manager: &AuthManager, + base_url: &str, + job: TrackPluginManagementJob, +) { + send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_uninstalled") + .await; } -async fn send_track_plugin_enabled(auth_manager: &AuthManager, job: TrackPluginManagementJob) { - send_track_plugin_management_event(auth_manager, job, "codex_plugin_enabled").await; +async fn send_track_plugin_enabled( + auth_manager: &AuthManager, + base_url: &str, + job: TrackPluginManagementJob, +) { + send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_enabled").await; } -async fn send_track_plugin_disabled(auth_manager: &AuthManager, job: TrackPluginManagementJob) { - send_track_plugin_management_event(auth_manager, job, "codex_plugin_disabled").await; +async fn send_track_plugin_disabled( + auth_manager: &AuthManager, + base_url: &str, + job: TrackPluginManagementJob, +) { + send_track_plugin_management_event(auth_manager, base_url, job, "codex_plugin_disabled").await; } async fn send_track_plugin_management_event( auth_manager: &AuthManager, + base_url: &str, job: TrackPluginManagementJob, event_type: &'static str, ) { - let TrackPluginManagementJob { config, plugin } = job; + let TrackPluginManagementJob { + analytics_enabled, + plugin, + } = job; let event_params = codex_plugin_metadata(plugin); let event = CodexPluginEventRequest { event_type, @@ -620,7 +647,7 @@ async fn send_track_plugin_management_event( _ => unreachable!("unknown plugin management event type"), }]; - send_track_events(auth_manager, config, events).await; + send_track_events(auth_manager, analytics_enabled, base_url, events).await; } fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> CodexAppMetadata { @@ -629,7 +656,7 @@ fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> Code thread_id: Some(tracking.thread_id.clone()), turn_id: Some(tracking.turn_id.clone()), app_name: app.app_name, - product_client_id: Some(crate::default_client::originator().value), + product_client_id: Some(originator().value), invoke_type: app.invocation_type, model_slug: Some(tracking.model_slug.clone()), } @@ -654,7 +681,7 @@ fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPluginMetadata .map(|connector_id| connector_id.0) .collect() }), - product_client_id: Some(crate::default_client::originator().value), + product_client_id: Some(originator().value), } } @@ -672,9 +699,13 @@ fn codex_plugin_used_metadata( async fn send_track_events( auth_manager: &AuthManager, - config: Arc, + analytics_enabled: Option, + base_url: &str, events: Vec, ) { + if analytics_enabled == Some(false) { + return; + } if events.is_empty() { return; } @@ -692,7 +723,7 @@ async fn send_track_events( return; }; - let base_url = config.chatgpt_base_url.trim_end_matches('/'); + let base_url = base_url.trim_end_matches('/'); let url = format!("{base_url}/codex/analytics-events/events"); let payload = TrackEventsRequest { events }; diff --git a/codex-rs/core/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs similarity index 95% rename from codex-rs/core/src/analytics_client_tests.rs rename to codex-rs/analytics/src/analytics_client_tests.rs index a038aae04..f64da8f58 100644 --- a/codex-rs/core/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -11,10 +11,11 @@ use super::codex_app_metadata; use super::codex_plugin_metadata; use super::codex_plugin_used_metadata; use super::normalize_path_for_skill_id; -use crate::plugins::AppConnectorId; -use crate::plugins::PluginCapabilitySummary; -use crate::plugins::PluginId; -use crate::plugins::PluginTelemetryMetadata; +use codex_login::default_client::originator; +use codex_plugin::AppConnectorId; +use codex_plugin::PluginCapabilitySummary; +use codex_plugin::PluginId; +use codex_plugin::PluginTelemetryMetadata; use pretty_assertions::assert_eq; use serde_json::json; use std::collections::HashSet; @@ -109,7 +110,7 @@ fn app_mentioned_event_serializes_expected_shape() { "thread_id": "thread-1", "turn_id": "turn-1", "app_name": "Calendar", - "product_client_id": crate::default_client::originator().value, + "product_client_id": originator().value, "invoke_type": "explicit", "model_slug": "gpt-5" } @@ -147,7 +148,7 @@ fn app_used_event_serializes_expected_shape() { "thread_id": "thread-2", "turn_id": "turn-2", "app_name": "Google Drive", - "product_client_id": crate::default_client::originator().value, + "product_client_id": originator().value, "invoke_type": "implicit", "model_slug": "gpt-5" } @@ -210,7 +211,7 @@ fn plugin_used_event_serializes_expected_shape() { "has_skills": true, "mcp_server_count": 2, "connector_ids": ["calendar", "drive"], - "product_client_id": crate::default_client::originator().value, + "product_client_id": originator().value, "thread_id": "thread-3", "turn_id": "turn-3", "model_slug": "gpt-5" @@ -239,7 +240,7 @@ fn plugin_management_event_serializes_expected_shape() { "has_skills": true, "mcp_server_count": 2, "connector_ids": ["calendar", "drive"], - "product_client_id": crate::default_client::originator().value + "product_client_id": originator().value } }) ); diff --git a/codex-rs/analytics/src/lib.rs b/codex-rs/analytics/src/lib.rs new file mode 100644 index 000000000..e36f5d71b --- /dev/null +++ b/codex-rs/analytics/src/lib.rs @@ -0,0 +1,8 @@ +mod analytics_client; + +pub use analytics_client::AnalyticsEventsClient; +pub use analytics_client::AppInvocation; +pub use analytics_client::InvocationType; +pub use analytics_client::SkillInvocation; +pub use analytics_client::TrackEventsContext; +pub use analytics_client::build_track_events_context; diff --git a/codex-rs/app-server/src/config_api.rs b/codex-rs/app-server/src/config_api.rs index 4aa2982a8..2be0bb859 100644 --- a/codex-rs/app-server/src/config_api.rs +++ b/codex-rs/app-server/src/config_api.rs @@ -450,6 +450,8 @@ fn config_write_error(code: ConfigWriteErrorCode, message: impl Into) -> mod tests { use super::*; use codex_core::AnalyticsEventsClient; + use codex_core::AuthManager; + use codex_core::CodexAuth; use codex_core::config_loader::NetworkRequirementsToml as CoreNetworkRequirementsToml; use codex_features::Feature; use codex_protocol::protocol::AskForApproval as CoreAskForApproval; @@ -651,6 +653,7 @@ mod tests { .await .expect("load analytics config"), ); + let auth_manager = AuthManager::from_auth_for_testing(CodexAuth::from_api_key("test")); let config_api = ConfigApi::new( codex_home.path().to_path_buf(), Arc::new(RwLock::new(Vec::new())), @@ -659,10 +662,12 @@ mod tests { Arc::new(RwLock::new(CloudRequirementsLoader::default())), reloader.clone(), AnalyticsEventsClient::new( - analytics_config, - codex_core::test_support::auth_manager_from_auth( - codex_core::CodexAuth::from_api_key("test"), - ), + auth_manager, + analytics_config + .chatgpt_base_url + .trim_end_matches('/') + .to_string(), + analytics_config.analytics_enabled, ), ); diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index d070738fb..feca70951 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -222,8 +222,11 @@ impl MessageProcessor { auth_manager.set_external_auth_refresher(Arc::new(ExternalAuthRefreshBridge { outgoing: outgoing.clone(), })); - let analytics_events_client = - AnalyticsEventsClient::new(Arc::clone(&config), Arc::clone(&auth_manager)); + let analytics_events_client = AnalyticsEventsClient::new( + Arc::clone(&auth_manager), + config.chatgpt_base_url.trim_end_matches('/').to_string(), + config.analytics_enabled, + ); thread_manager .plugins_manager() .set_analytics_events_client(analytics_events_client.clone()); diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml index 9e01e6662..42ca739fa 100644 --- a/codex-rs/core/Cargo.toml +++ b/codex-rs/core/Cargo.toml @@ -27,6 +27,7 @@ bm25 = { workspace = true } chardetng = { workspace = true } chrono = { workspace = true, features = ["serde"] } clap = { workspace = true, features = ["derive"] } +codex-analytics = { workspace = true } codex-api = { workspace = true } codex-app-server-protocol = { workspace = true } codex-apply-patch = { workspace = true } diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index 1e874b4d6..2684cc90b 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -12,10 +12,6 @@ use crate::SandboxState; use crate::agent::AgentControl; use crate::agent::AgentStatus; use crate::agent::agent_status_from_event; -use crate::analytics_client::AnalyticsEventsClient; -use crate::analytics_client::AppInvocation; -use crate::analytics_client::InvocationType; -use crate::analytics_client::build_track_events_context; use crate::apps::render_apps_section; use crate::auth_env_telemetry::collect_auth_env_telemetry; use crate::commit_attribution::commit_message_trailer_instruction; @@ -349,6 +345,10 @@ use crate::turn_timing::record_turn_ttft_metric; use crate::unified_exec::UnifiedExecProcessManager; use crate::util::backoff; use crate::windows_sandbox::WindowsSandboxLevelExt; +use codex_analytics::AnalyticsEventsClient; +use codex_analytics::AppInvocation; +use codex_analytics::InvocationType; +use codex_analytics::build_track_events_context; use codex_async_utils::OrCancelExt; use codex_git_utils::get_git_repo_root; use codex_otel::SessionTelemetry; @@ -1844,8 +1844,9 @@ impl Session { shell_zsh_path: config.zsh_path.clone(), main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(), analytics_events_client: AnalyticsEventsClient::new( - Arc::clone(&config), Arc::clone(&auth_manager), + config.chatgpt_base_url.trim_end_matches('/').to_string(), + config.analytics_enabled, ), hooks, rollout: Mutex::new(rollout_recorder), diff --git a/codex-rs/core/src/codex_tests.rs b/codex-rs/core/src/codex_tests.rs index 34399a000..82371f5be 100644 --- a/codex-rs/core/src/codex_tests.rs +++ b/codex-rs/core/src/codex_tests.rs @@ -2668,8 +2668,9 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) { shell_zsh_path: None, main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(), analytics_events_client: AnalyticsEventsClient::new( - Arc::clone(&config), Arc::clone(&auth_manager), + config.chatgpt_base_url.trim_end_matches('/').to_string(), + config.analytics_enabled, ), hooks: Hooks::new(HooksConfig { legacy_notify_argv: config.notify.clone(), @@ -3503,8 +3504,9 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx( shell_zsh_path: None, main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(), analytics_events_client: AnalyticsEventsClient::new( - Arc::clone(&config), Arc::clone(&auth_manager), + config.chatgpt_base_url.trim_end_matches('/').to_string(), + config.analytics_enabled, ), hooks: Hooks::new(HooksConfig { legacy_notify_argv: config.notify.clone(), diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index 71f4328ae..b4426c9cd 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -5,7 +5,6 @@ // the TUI or the tracing stack). #![deny(clippy::print_stdout, clippy::print_stderr)] -mod analytics_client; pub mod api_bridge; mod apply_patch; mod apps; @@ -108,9 +107,9 @@ pub type NewConversation = NewThread; #[deprecated(note = "use CodexThread")] pub type CodexConversation = CodexThread; // Re-export common auth types for workspace consumers -pub use analytics_client::AnalyticsEventsClient; pub use auth::AuthManager; pub use auth::CodexAuth; +pub use codex_analytics::AnalyticsEventsClient; mod default_client_forwarding; /// Default Codex HTTP client headers and reqwest construction. diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index 18a484357..d31696db5 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -8,9 +8,6 @@ use codex_app_server_protocol::McpServerElicitationRequest; use codex_app_server_protocol::McpServerElicitationRequestParams; use tracing::error; -use crate::analytics_client::AppInvocation; -use crate::analytics_client::InvocationType; -use crate::analytics_client::build_track_events_context; use crate::arc_monitor::ArcMonitorOutcome; use crate::arc_monitor::monitor_action; use crate::codex::Session; @@ -32,6 +29,9 @@ use crate::protocol::McpInvocation; use crate::protocol::McpToolCallBeginEvent; use crate::protocol::McpToolCallEndEvent; use crate::state_db; +use codex_analytics::AppInvocation; +use codex_analytics::InvocationType; +use codex_analytics::build_track_events_context; use codex_features::Feature; use codex_protocol::mcp::CallToolResult; use codex_protocol::openai_models::InputModality; diff --git a/codex-rs/core/src/skills/injection.rs b/codex-rs/core/src/skills/injection.rs index b83be2322..dac05f836 100644 --- a/codex-rs/core/src/skills/injection.rs +++ b/codex-rs/core/src/skills/injection.rs @@ -2,14 +2,14 @@ use std::collections::HashMap; use std::collections::HashSet; use std::path::PathBuf; -use crate::analytics_client::AnalyticsEventsClient; -use crate::analytics_client::InvocationType; -use crate::analytics_client::SkillInvocation; -use crate::analytics_client::TrackEventsContext; use crate::instructions::SkillInstructions; use crate::mention_syntax::TOOL_MENTION_SIGIL; use crate::mentions::build_skill_name_counts; use crate::skills::SkillMetadata; +use codex_analytics::AnalyticsEventsClient; +use codex_analytics::InvocationType; +use codex_analytics::SkillInvocation; +use codex_analytics::TrackEventsContext; use codex_otel::SessionTelemetry; use codex_protocol::models::ResponseItem; use codex_protocol::user_input::UserInput; diff --git a/codex-rs/core/src/skills/invocation_utils.rs b/codex-rs/core/src/skills/invocation_utils.rs index 122158bda..084670fa8 100644 --- a/codex-rs/core/src/skills/invocation_utils.rs +++ b/codex-rs/core/src/skills/invocation_utils.rs @@ -2,13 +2,13 @@ use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; -use crate::analytics_client::InvocationType; -use crate::analytics_client::SkillInvocation; -use crate::analytics_client::build_track_events_context; use crate::codex::Session; use crate::codex::TurnContext; use crate::skills::SkillLoadOutcome; use crate::skills::SkillMetadata; +use codex_analytics::InvocationType; +use codex_analytics::SkillInvocation; +use codex_analytics::build_track_events_context; pub(crate) fn build_implicit_skill_path_indexes( skills: Vec, diff --git a/codex-rs/core/src/state/service.rs b/codex-rs/core/src/state/service.rs index f12f7ef97..ad980db1a 100644 --- a/codex-rs/core/src/state/service.rs +++ b/codex-rs/core/src/state/service.rs @@ -4,7 +4,6 @@ use std::sync::Arc; use crate::AuthManager; use crate::RolloutRecorder; use crate::agent::AgentControl; -use crate::analytics_client::AnalyticsEventsClient; use crate::client::ModelClient; use crate::config::StartedNetworkProxy; use crate::exec_policy::ExecPolicyManager; @@ -20,6 +19,7 @@ use crate::tools::network_approval::NetworkApprovalService; use crate::tools::runtimes::ExecveSessionApproval; use crate::tools::sandboxing::ApprovalStore; use crate::unified_exec::UnifiedExecProcessManager; +use codex_analytics::AnalyticsEventsClient; use codex_exec_server::Environment; use codex_hooks::Hooks; use codex_otel::SessionTelemetry;