diff --git a/codex-rs/analytics/src/analytics_client.rs b/codex-rs/analytics/src/analytics_client.rs index 3dd4d14f80..6b50f0f341 100644 --- a/codex-rs/analytics/src/analytics_client.rs +++ b/codex-rs/analytics/src/analytics_client.rs @@ -39,6 +39,7 @@ pub struct ThreadInitializeInput { pub model: String, pub ephemeral: bool, pub session_source: SessionSource, + pub initialization_mode: InitializationMode, } #[derive(Clone, Copy, Debug, Serialize)] @@ -84,9 +85,7 @@ pub struct AppInvocation { pub enum AnalyticsInput { Initialize(InitializeInput), - ThreadStart(ThreadInitializeInput), - ThreadFork(ThreadInitializeInput), - ThreadResume(ThreadInitializeInput), + ThreadInitialized(ThreadInitializeInput), SkillInvoked(SkillInvokedInput), AppMentioned(AppMentionedInput), AppUsed(AppUsedInput), @@ -234,16 +233,8 @@ impl AnalyticsEventsClient { self.record(AnalyticsInput::Initialize(input)); } - pub fn track_thread_start(&self, input: ThreadInitializeInput) { - self.record(AnalyticsInput::ThreadStart(input)); - } - - pub fn track_thread_fork(&self, input: ThreadInitializeInput) { - self.record(AnalyticsInput::ThreadFork(input)); - } - - pub fn track_thread_resume(&self, input: ThreadInitializeInput) { - self.record(AnalyticsInput::ThreadResume(input)); + pub fn track_thread_initialized(&self, input: ThreadInitializeInput) { + self.record(AnalyticsInput::ThreadInitialized(input)); } pub fn track_app_mentioned(&self, tracking: TrackEventsContext, mentions: Vec) { @@ -438,14 +429,8 @@ impl AnalyticsReducer { AnalyticsInput::Initialize(input) => { self.ingest_initialize(input); } - AnalyticsInput::ThreadStart(input) => { - self.ingest_thread_initialized(input, InitializationMode::New, out); - } - AnalyticsInput::ThreadFork(input) => { - self.ingest_thread_initialized(input, InitializationMode::Forked, out); - } - AnalyticsInput::ThreadResume(input) => { - self.ingest_thread_initialized(input, InitializationMode::Resumed, out); + AnalyticsInput::ThreadInitialized(input) => { + self.ingest_thread_initialized(input, out); } AnalyticsInput::SkillInvoked(input) => { self.ingest_skill_invoked(input, out).await; @@ -477,18 +462,13 @@ impl AnalyticsReducer { fn ingest_thread_initialized( &mut self, input: ThreadInitializeInput, - initialization_mode: InitializationMode, out: &mut Vec, ) { let Some(client_state) = self.clients.get(&input.connection_id) else { return; }; out.push(TrackEventRequest::CodexThreadInitialized( - codex_thread_initialized_event_request( - client_state.product_client_id.clone(), - input, - initialization_mode, - ), + codex_thread_initialized_event_request(client_state.product_client_id.clone(), input), )); } @@ -611,22 +591,16 @@ fn codex_app_metadata(tracking: &TrackEventsContext, app: AppInvocation) -> Code fn codex_thread_initialized_event_request( product_client_id: String, input: ThreadInitializeInput, - initialization_mode: InitializationMode, ) -> CodexThreadInitializedEvent { CodexThreadInitializedEvent { event_type: "codex_thread_initialized", - event_params: codex_thread_initialized_event_params( - product_client_id, - input, - initialization_mode, - ), + event_params: codex_thread_initialized_event_params(product_client_id, input), } } fn codex_thread_initialized_event_params( product_client_id: String, input: ThreadInitializeInput, - initialization_mode: InitializationMode, ) -> CodexThreadInitializedEventParams { CodexThreadInitializedEventParams { thread_id: input.thread_id, @@ -634,7 +608,7 @@ fn codex_thread_initialized_event_params( model: input.model, ephemeral: input.ephemeral, session_source: session_source_name(&input.session_source), - initialization_mode, + initialization_mode: input.initialization_mode, subagent_source: session_source_subagent_source(&input.session_source) .map(subagent_source_name), parent_thread_id: session_source_parent_thread_id(&input.session_source), diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 28e3860098..7b6bb6c1e2 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -204,8 +204,8 @@ fn thread_initialized_event_serializes_expected_shape() { model: "gpt-5".to_string(), ephemeral: true, session_source: SessionSource::Exec, + initialization_mode: InitializationMode::New, }, - InitializationMode::New, )); let payload = serde_json::to_value(&event).expect("serialize thread initialized event"); @@ -240,8 +240,8 @@ fn thread_initialized_event_serializes_subagent_source() { model: "gpt-5".to_string(), ephemeral: false, session_source: SessionSource::SubAgent(SubAgentSource::Review), + initialization_mode: InitializationMode::New, }, - InitializationMode::New, )); let payload = @@ -250,27 +250,6 @@ fn thread_initialized_event_serializes_subagent_source() { assert_eq!(payload["event_params"]["subagent_source"], "review"); } -#[test] -fn thread_initialized_event_omits_non_user_non_subagent_session_source() { - let event = TrackEventRequest::CodexThreadInitialized(codex_thread_initialized_event_request( - originator().value, - ThreadInitializeInput { - connection_id: 1, - thread_id: "thread-2".to_string(), - model: "gpt-5".to_string(), - ephemeral: false, - session_source: SessionSource::Mcp, - }, - InitializationMode::New, - )); - - let payload = serde_json::to_value(&event).expect("serialize mcp thread initialized event"); - assert_eq!( - payload["event_params"]["session_source"], - serde_json::Value::Null - ); -} - #[tokio::test] async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialized() { let mut reducer = AnalyticsReducer::default(); @@ -278,12 +257,13 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize reducer .ingest( - AnalyticsInput::ThreadStart(ThreadInitializeInput { + AnalyticsInput::ThreadInitialized(ThreadInitializeInput { connection_id: 7, thread_id: "thread-no-client".to_string(), model: "gpt-5".to_string(), ephemeral: false, session_source: SessionSource::Exec, + initialization_mode: InitializationMode::New, }), &mut events, ) @@ -303,7 +283,7 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize reducer .ingest( - AnalyticsInput::ThreadResume(ThreadInitializeInput { + AnalyticsInput::ThreadInitialized(ThreadInitializeInput { connection_id: 7, thread_id: "thread-1".to_string(), model: "gpt-5".to_string(), @@ -318,6 +298,7 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize agent_nickname: None, agent_role: None, }), + initialization_mode: InitializationMode::Resumed, }), &mut events, ) diff --git a/codex-rs/app-server/src/codex_message_processor.rs b/codex-rs/app-server/src/codex_message_processor.rs index 553e32bc95..9c3ebf5400 100644 --- a/codex-rs/app-server/src/codex_message_processor.rs +++ b/codex-rs/app-server/src/codex_message_processor.rs @@ -185,6 +185,7 @@ use codex_core::CodexAuth; use codex_core::CodexThread; use codex_core::Cursor as RolloutCursor; use codex_core::ForkSnapshot; +use codex_core::InitializationMode; use codex_core::NewThread; use codex_core::RolloutRecorder; use codex_core::SessionMeta; @@ -2163,10 +2164,11 @@ impl CodexMessageProcessor { }; listener_task_context .analytics_events_client - .track_thread_start(thread_lifecycle_input( + .track_thread_initialized(thread_initialize_input( request_id.connection_id, &thread, response.model.clone(), + InitializationMode::New, )); listener_task_context @@ -3647,10 +3649,11 @@ impl CodexMessageProcessor { reasoning_effort: session_configured.reasoning_effort, }; self.analytics_events_client - .track_thread_resume(thread_lifecycle_input( + .track_thread_initialized(thread_initialize_input( request_id.connection_id, &response.thread, response.model.clone(), + InitializationMode::Resumed, )); self.outgoing.send_response(request_id, response).await; @@ -4260,10 +4263,11 @@ impl CodexMessageProcessor { reasoning_effort: session_configured.reasoning_effort, }; self.analytics_events_client - .track_thread_fork(thread_lifecycle_input( + .track_thread_initialized(thread_initialize_input( request_id.connection_id, &thread, response.model.clone(), + InitializationMode::Forked, )); self.outgoing.send_response(request_id, response).await; @@ -7884,10 +7888,11 @@ fn cloud_requirements_load_error(err: &std::io::Error) -> Option<&CloudRequireme None } -fn thread_lifecycle_input( +fn thread_initialize_input( connection_id: ConnectionId, thread: &Thread, model: String, + initialization_mode: InitializationMode, ) -> ThreadInitializeInput { ThreadInitializeInput { connection_id: connection_id.0, @@ -7895,6 +7900,7 @@ fn thread_lifecycle_input( model, ephemeral: thread.ephemeral, session_source: thread.source.clone().into(), + initialization_mode, } } diff --git a/codex-rs/core/src/lib.rs b/codex-rs/core/src/lib.rs index b5b094c5a4..6bad79a676 100644 --- a/codex-rs/core/src/lib.rs +++ b/codex-rs/core/src/lib.rs @@ -136,6 +136,7 @@ pub type CodexConversation = CodexThread; pub use auth::AuthManager; pub use auth::CodexAuth; pub use codex_analytics::AnalyticsEventsClient; +pub use codex_analytics::InitializationMode; pub use codex_analytics::InitializeInput; pub use codex_analytics::ThreadInitializeInput; mod default_client_forwarding;