diff --git a/codex-rs/analytics/src/analytics_client.rs b/codex-rs/analytics/src/analytics_client.rs index 32b91205a8..b856118899 100644 --- a/codex-rs/analytics/src/analytics_client.rs +++ b/codex-rs/analytics/src/analytics_client.rs @@ -149,6 +149,9 @@ pub struct AnalyticsReducer { struct ConnectionState { product_client_id: String, + client_name: Option, + client_version: Option, + experimental_api_enabled: Option, } #[derive(Clone)] @@ -377,6 +380,9 @@ struct SkillInvocationEventParams { struct CodexThreadInitializedEventParams { thread_id: String, product_client_id: String, + client_name: Option, + client_version: Option, + experimental_api_enabled: Option, model: String, ephemeral: bool, session_source: Option<&'static str>, @@ -492,7 +498,12 @@ impl AnalyticsReducer { self.connections.insert( connection_id, ConnectionState { - product_client_id: params.client_info.name, + product_client_id: params.client_info.name.clone(), + client_name: Some(params.client_info.name), + client_version: Some(params.client_info.version), + experimental_api_enabled: params + .capabilities + .map(|capabilities| capabilities.experimental_api), }, ); } @@ -506,10 +517,7 @@ impl AnalyticsReducer { return; }; out.push(TrackEventRequest::CodexThreadInitialized( - codex_thread_initialized_event_request( - connection_state.product_client_id.clone(), - input, - ), + codex_thread_initialized_event_request(connection_state, input), )); } @@ -666,22 +674,25 @@ fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> Code } fn codex_thread_initialized_event_request( - product_client_id: String, + connection_state: &ConnectionState, input: ThreadInitializedInput, ) -> CodexThreadInitializedEvent { CodexThreadInitializedEvent { event_type: "codex_thread_initialized", - event_params: codex_thread_initialized_event_params(product_client_id, input), + event_params: codex_thread_initialized_event_params(connection_state, input), } } fn codex_thread_initialized_event_params( - product_client_id: String, + connection_state: &ConnectionState, input: ThreadInitializedInput, ) -> CodexThreadInitializedEventParams { CodexThreadInitializedEventParams { thread_id: input.thread_id, - product_client_id, + product_client_id: connection_state.product_client_id.clone(), + client_name: connection_state.client_name.clone(), + client_version: connection_state.client_version.clone(), + experimental_api_enabled: connection_state.experimental_api_enabled, model: input.model, ephemeral: input.ephemeral, session_source: session_source_name(&input.session_source), diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 6156015bef..431453ca51 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -20,6 +20,7 @@ use codex_app_server_protocol::ApprovalsReviewer as AppServerApprovalsReviewer; use codex_app_server_protocol::AskForApproval as AppServerAskForApproval; use codex_app_server_protocol::ClientInfo; use codex_app_server_protocol::ClientResponse; +use codex_app_server_protocol::InitializeCapabilities; use codex_app_server_protocol::InitializeParams; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::SandboxPolicy as AppServerSandboxPolicy; @@ -262,7 +263,12 @@ fn app_used_dedupe_is_keyed_by_turn_and_connector() { #[test] fn thread_initialized_event_serializes_expected_shape() { let event = TrackEventRequest::CodexThreadInitialized(codex_thread_initialized_event_request( - "codex-tui".to_string(), + &super::ConnectionState { + product_client_id: "codex-tui".to_string(), + client_name: Some("codex-tui".to_string()), + client_version: Some("1.0.0".to_string()), + experimental_api_enabled: Some(true), + }, ThreadInitializedInput { connection_id: 1, thread_id: "thread-0".to_string(), @@ -282,6 +288,9 @@ fn thread_initialized_event_serializes_expected_shape() { "event_params": { "thread_id": "thread-0", "product_client_id": "codex-tui", + "client_name": "codex-tui", + "client_version": "1.0.0", + "experimental_api_enabled": true, "model": "gpt-5", "ephemeral": true, "session_source": "user", @@ -325,7 +334,10 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize title: None, version: "1.0.0".to_string(), }, - capabilities: None, + capabilities: Some(InitializeCapabilities { + experimental_api: false, + opt_out_notification_methods: None, + }), }, }, &mut events, @@ -347,6 +359,12 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize assert_eq!(payload.as_array().expect("events array").len(), 1); assert_eq!(payload[0]["event_type"], "codex_thread_initialized"); assert_eq!(payload[0]["event_params"]["product_client_id"], "codex-tui"); + assert_eq!(payload[0]["event_params"]["client_name"], "codex-tui"); + assert_eq!(payload[0]["event_params"]["client_version"], "1.0.0"); + assert_eq!( + payload[0]["event_params"]["experimental_api_enabled"], + false + ); assert_eq!(payload[0]["event_params"]["initialization_mode"], "resumed"); assert_eq!(payload[0]["event_params"]["session_source"], "user"); assert_eq!(payload[0]["event_params"]["subagent_source"], json!(null));