diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 32cfb36aa5..a89e684aa2 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -1622,6 +1622,10 @@ async fn accepted_turn_steer_emits_expected_event() { payload["event_params"]["runtime"]["codex_rs_version"], json!("0.1.0") ); + assert_eq!(payload["event_params"]["thread_source"], json!("user")); + assert_eq!(payload["event_params"]["initialization_mode"], json!("new")); + assert_eq!(payload["event_params"]["subagent_source"], json!(null)); + assert_eq!(payload["event_params"]["parent_thread_id"], json!(null)); assert!(payload["event_params"].get("product_client_id").is_none()); } @@ -1650,6 +1654,10 @@ async fn rejected_turn_steer_uses_request_connection_metadata() { payload["event_params"]["runtime"]["codex_rs_version"], json!("0.1.0") ); + assert_eq!(payload["event_params"]["thread_source"], json!("user")); + assert_eq!(payload["event_params"]["initialization_mode"], json!("new")); + assert_eq!(payload["event_params"]["subagent_source"], json!(null)); + assert_eq!(payload["event_params"]["parent_thread_id"], json!(null)); assert_eq!(payload["event_params"]["result"], json!("rejected")); assert_eq!( payload["event_params"]["rejection_reason"], diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 4bc3ccde9d..74105a742d 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -213,6 +213,10 @@ pub(crate) struct CodexTurnSteerEventParams { pub(crate) accepted_turn_id: Option, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, + pub(crate) thread_source: Option, + pub(crate) initialization_mode: ThreadInitializationMode, + pub(crate) subagent_source: Option, + pub(crate) parent_thread_id: Option, pub(crate) num_input_images: usize, pub(crate) result: TurnSteerResult, pub(crate) rejection_reason: Option, @@ -351,6 +355,10 @@ pub(crate) fn codex_turn_steer_event_params( app_server_client: CodexAppServerClientMetadata, runtime: CodexRuntimeMetadata, tracking: &TrackEventsContext, + thread_source: Option<&'static str>, + initialization_mode: ThreadInitializationMode, + subagent_source: Option, + parent_thread_id: Option, turn_steer: CodexTurnSteerEvent, ) -> CodexTurnSteerEventParams { CodexTurnSteerEventParams { @@ -359,6 +367,10 @@ pub(crate) fn codex_turn_steer_event_params( accepted_turn_id: turn_steer.accepted_turn_id, app_server_client, runtime, + thread_source: thread_source.map(str::to_string), + initialization_mode, + subagent_source, + parent_thread_id, num_input_images: turn_steer.num_input_images, result: turn_steer.result, rejection_reason: turn_steer.rejection_reason, diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index d88a4f9bb2..00e4b7abd7 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -723,6 +723,13 @@ impl AnalyticsReducer { let Some(connection_state) = self.connections.get(&connection_id) else { return; }; + let Some(thread_metadata) = self.thread_metadata.get(&pending_request.thread_id) else { + tracing::warn!( + thread_id = %pending_request.thread_id, + "dropping turn steer analytics event: missing thread lifecycle metadata" + ); + return; + }; let tracking = TrackEventsContext { model_slug: String::new(), thread_id: pending_request.thread_id, @@ -745,6 +752,10 @@ impl AnalyticsReducer { connection_state.app_server_client.clone(), connection_state.runtime.clone(), &tracking, + thread_metadata.thread_source, + thread_metadata.initialization_mode, + thread_metadata.subagent_source.clone(), + thread_metadata.parent_thread_id.clone(), turn_steer, ), }));