From 505e703c328a03f1bf3f91622d7426e2dd490a04 Mon Sep 17 00:00:00 2001 From: Roy Han Date: Tue, 24 Mar 2026 15:26:32 -0700 Subject: [PATCH] restructure history and source --- codex-rs/core/src/analytics_client.rs | 44 ++++++++++++++++---- codex-rs/core/src/analytics_client_tests.rs | 40 ++++++++++++++++-- codex-rs/core/src/codex.rs | 39 ++++++++--------- codex-rs/core/tests/suite/thread_metadata.rs | 6 ++- 4 files changed, 95 insertions(+), 34 deletions(-) diff --git a/codex-rs/core/src/analytics_client.rs b/codex-rs/core/src/analytics_client.rs index dc5f5c556a..428c66f068 100644 --- a/codex-rs/core/src/analytics_client.rs +++ b/codex-rs/core/src/analytics_client.rs @@ -14,6 +14,7 @@ use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SkillScope; +use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::SubmissionType; use serde::Serialize; use sha1::Digest; @@ -49,8 +50,9 @@ pub(crate) struct CodexThreadStartedEvent { pub(crate) personality: Option, pub(crate) ephemeral: bool, pub(crate) session_source: SessionSource, + pub(crate) initial_history_type: InitialHistoryType, + pub(crate) subagent_source: Option, pub(crate) parent_thread_id: Option, - pub(crate) create_source: ThreadCreateSource, pub(crate) created_at: u64, } @@ -73,10 +75,10 @@ pub(crate) struct CodexTurnEvent { #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] -pub(crate) enum ThreadCreateSource { - ThreadStart, - ThreadFork, - DetachedReview, +pub(crate) enum InitialHistoryType { + New, + Forked, + Resumed, } pub(crate) fn build_track_events_context( @@ -432,9 +434,10 @@ struct CodexThreadStartedEventParams { collaboration_mode: &'static str, personality: Option, ephemeral: bool, - session_source: SessionSource, + session_source: String, + initial_history_type: InitialHistoryType, + subagent_source: Option, parent_thread_id: Option, - create_source: ThreadCreateSource, created_at: u64, } @@ -929,9 +932,10 @@ fn codex_thread_started_event_params( collaboration_mode: collaboration_mode_mode(thread_event.collaboration_mode), personality: thread_event.personality.map(|value| value.to_string()), ephemeral: thread_event.ephemeral, - session_source: thread_event.session_source, + session_source: session_source_name(&thread_event.session_source), + initial_history_type: thread_event.initial_history_type, + subagent_source: thread_event.subagent_source.map(subagent_source_name), parent_thread_id: thread_event.parent_thread_id, - create_source: thread_event.create_source, created_at: thread_event.created_at, } } @@ -989,6 +993,28 @@ fn collaboration_mode_mode(mode: ModeKind) -> &'static str { } } +fn session_source_name(session_source: &SessionSource) -> String { + match session_source { + SessionSource::Cli => "cli".to_string(), + SessionSource::VSCode => "vscode".to_string(), + SessionSource::Exec => "exec".to_string(), + SessionSource::Mcp => "mcp".to_string(), + SessionSource::Custom(source) => source.clone(), + SessionSource::SubAgent(_) => "subagent".to_string(), + SessionSource::Unknown => "unknown".to_string(), + } +} + +fn subagent_source_name(subagent_source: SubAgentSource) -> String { + match subagent_source { + SubAgentSource::Review => "review".to_string(), + SubAgentSource::Compact => "compact".to_string(), + SubAgentSource::ThreadSpawn { .. } => "thread_spawn".to_string(), + SubAgentSource::MemoryConsolidation => "memory_consolidation".to_string(), + SubAgentSource::Other(other) => other, + } +} + async fn send_track_events( auth_manager: &AuthManager, config: Arc, diff --git a/codex-rs/core/src/analytics_client_tests.rs b/codex-rs/core/src/analytics_client_tests.rs index 592c06d210..ea59269a7b 100644 --- a/codex-rs/core/src/analytics_client_tests.rs +++ b/codex-rs/core/src/analytics_client_tests.rs @@ -8,9 +8,9 @@ use super::CodexThreadStartedEvent; use super::CodexThreadStartedEventRequest; use super::CodexTurnEvent; use super::CodexTurnEventRequest; +use super::InitialHistoryType; use super::InvocationType; use super::SubmissionType; -use super::ThreadCreateSource; use super::TrackEventRequest; use super::TrackEventsContext; use super::codex_app_metadata; @@ -32,6 +32,7 @@ use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::SandboxPolicy; use codex_protocol::protocol::SessionSource; +use codex_protocol::protocol::SubAgentSource; use pretty_assertions::assert_eq; use serde_json::json; use std::collections::HashSet; @@ -279,8 +280,9 @@ fn thread_started_event_serializes_expected_shape() { personality: Some(Personality::Friendly), ephemeral: true, session_source: SessionSource::Exec, + initial_history_type: InitialHistoryType::New, + subagent_source: None, parent_thread_id: None, - create_source: ThreadCreateSource::ThreadStart, created_at: 1_716_000_000, }), }); @@ -307,14 +309,46 @@ fn thread_started_event_serializes_expected_shape() { "personality": "friendly", "ephemeral": true, "session_source": "exec", + "initial_history_type": "new", + "subagent_source": null, "parent_thread_id": null, - "create_source": "thread_start", "created_at": 1716000000 } }) ); } +#[test] +fn thread_started_event_serializes_subagent_source() { + let event = TrackEventRequest::ThreadStarted(CodexThreadStartedEventRequest { + event_type: "codex_thread_started", + event_params: codex_thread_started_event_params(CodexThreadStartedEvent { + thread_id: "thread-1".to_string(), + model: "gpt-5".to_string(), + model_provider: "openai".to_string(), + reasoning_effort: None, + reasoning_summary: None, + service_tier: None, + approval_policy: AskForApproval::OnRequest, + approvals_reviewer: ApprovalsReviewer::User, + sandbox_policy: SandboxPolicy::new_read_only_policy(), + sandbox_network_access: false, + collaboration_mode: ModeKind::Default, + personality: None, + ephemeral: false, + session_source: SessionSource::SubAgent(SubAgentSource::Review), + initial_history_type: InitialHistoryType::New, + subagent_source: Some(SubAgentSource::Review), + parent_thread_id: None, + created_at: 1, + }), + }); + + let payload = serde_json::to_value(&event).expect("serialize subagent thread started event"); + assert_eq!(payload["event_params"]["session_source"], "subagent"); + assert_eq!(payload["event_params"]["subagent_source"], "review"); +} + #[test] fn plugin_used_event_serializes_expected_shape() { let tracking = TrackEventsContext { diff --git a/codex-rs/core/src/codex.rs b/codex-rs/core/src/codex.rs index c9aa21d0af..e9f49ba5e3 100644 --- a/codex-rs/core/src/codex.rs +++ b/codex-rs/core/src/codex.rs @@ -18,8 +18,8 @@ use crate::analytics_client::AnalyticsEventsClient; use crate::analytics_client::AppInvocation; use crate::analytics_client::CodexThreadStartedEvent; use crate::analytics_client::CodexTurnEvent; +use crate::analytics_client::InitialHistoryType; use crate::analytics_client::InvocationType; -use crate::analytics_client::ThreadCreateSource; use crate::analytics_client::build_track_events_context; use crate::apps::render_apps_section; use crate::auth_env_telemetry::collect_auth_env_telemetry; @@ -634,10 +634,9 @@ impl Codex { user_shell_override, }; - let thread_create_source = classify_thread_create_source( - &conversation_history, - &session_configuration.session_source, - ); + let should_emit_thread_started = + !matches!(conversation_history, InitialHistory::Resumed(_)); + let initial_history_type = initial_history_type(&conversation_history); let thread_session_source = session_configuration.session_source.clone(); let thread_started_configuration = session_configuration.clone(); @@ -668,7 +667,7 @@ impl Codex { })?; let thread_id = session.conversation_id; - if let Some(create_source) = thread_create_source { + if should_emit_thread_started { session .services .analytics_events_client @@ -698,9 +697,10 @@ impl Codex { ephemeral: thread_started_configuration .original_config_do_not_use .ephemeral, + initial_history_type, + subagent_source: session_source_subagent_source(&thread_session_source), parent_thread_id: session_source_parent_thread_id(&thread_session_source), session_source: thread_session_source, - create_source, created_at: SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() @@ -822,21 +822,18 @@ impl Codex { } } -fn classify_thread_create_source( - conversation_history: &InitialHistory, - session_source: &SessionSource, -) -> Option { - if matches!( - session_source, - SessionSource::SubAgent(SubAgentSource::Review) - ) { - return Some(ThreadCreateSource::DetachedReview); - } - +fn initial_history_type(conversation_history: &InitialHistory) -> InitialHistoryType { match conversation_history { - InitialHistory::New => Some(ThreadCreateSource::ThreadStart), - InitialHistory::Forked(_) => Some(ThreadCreateSource::ThreadFork), - InitialHistory::Resumed(_) => None, + InitialHistory::New => InitialHistoryType::New, + InitialHistory::Forked(_) => InitialHistoryType::Forked, + InitialHistory::Resumed(_) => InitialHistoryType::Resumed, + } +} + +fn session_source_subagent_source(session_source: &SessionSource) -> Option { + match session_source { + SessionSource::SubAgent(subagent_source) => Some(subagent_source.clone()), + _ => None, } } diff --git a/codex-rs/core/tests/suite/thread_metadata.rs b/codex-rs/core/tests/suite/thread_metadata.rs index 874a486bf2..94ae3b6241 100644 --- a/codex-rs/core/tests/suite/thread_metadata.rs +++ b/codex-rs/core/tests/suite/thread_metadata.rs @@ -78,11 +78,15 @@ async fn thread_start_tracks_thread_started_analytics() -> Result<()> { assert_eq!(event["event_params"]["personality"], "friendly"); assert_eq!(event["event_params"]["ephemeral"], true); assert_eq!(event["event_params"]["session_source"], "exec"); + assert_eq!(event["event_params"]["initial_history_type"], "new"); + assert_eq!( + event["event_params"]["subagent_source"], + serde_json::Value::Null + ); assert_eq!( event["event_params"]["parent_thread_id"], serde_json::Value::Null ); - assert_eq!(event["event_params"]["create_source"], "thread_start"); assert!( event["event_params"]["created_at"] .as_u64()