mirror of
https://github.com/openai/codex.git
synced 2026-10-24 18:18:41 +00:00
[codex-analytics] denormalize thread metadata onto turn events
This commit is contained in:
@@ -287,7 +287,6 @@ fn sample_turn_resolved_config(turn_id: &str) -> TurnResolvedConfigFact {
|
||||
submission_type: None,
|
||||
ephemeral: false,
|
||||
session_source: SessionSource::Exec,
|
||||
initialization_mode: ThreadInitializationMode::New,
|
||||
model: "gpt-5".to_string(),
|
||||
model_provider: "openai".to_string(),
|
||||
sandbox_policy: SandboxPolicy::new_read_only_policy(),
|
||||
@@ -456,6 +455,18 @@ async fn ingest_turn_prerequisites(
|
||||
) {
|
||||
if include_initialize {
|
||||
ingest_initialize(reducer, out).await;
|
||||
reducer
|
||||
.ingest(
|
||||
AnalyticsFact::Response {
|
||||
connection_id: 7,
|
||||
response: Box::new(sample_thread_start_response(
|
||||
"thread-2", /*ephemeral*/ false, "gpt-5",
|
||||
)),
|
||||
},
|
||||
out,
|
||||
)
|
||||
.await;
|
||||
out.clear();
|
||||
}
|
||||
|
||||
reducer
|
||||
@@ -1611,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());
|
||||
}
|
||||
|
||||
@@ -1639,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"],
|
||||
|
||||
@@ -213,6 +213,10 @@ pub(crate) struct CodexTurnSteerEventParams {
|
||||
pub(crate) accepted_turn_id: Option<String>,
|
||||
pub(crate) app_server_client: CodexAppServerClientMetadata,
|
||||
pub(crate) runtime: CodexRuntimeMetadata,
|
||||
pub(crate) thread_source: Option<String>,
|
||||
pub(crate) initialization_mode: ThreadInitializationMode,
|
||||
pub(crate) subagent_source: Option<String>,
|
||||
pub(crate) parent_thread_id: Option<String>,
|
||||
pub(crate) num_input_images: usize,
|
||||
pub(crate) result: TurnSteerResult,
|
||||
pub(crate) rejection_reason: Option<TurnSteerRejectionReason>,
|
||||
@@ -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<String>,
|
||||
parent_thread_id: Option<String>,
|
||||
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,
|
||||
|
||||
@@ -56,7 +56,6 @@ pub struct TurnResolvedConfigFact {
|
||||
pub submission_type: Option<TurnSubmissionType>,
|
||||
pub ephemeral: bool,
|
||||
pub session_source: SessionSource,
|
||||
pub initialization_mode: ThreadInitializationMode,
|
||||
pub model: String,
|
||||
pub model_provider: String,
|
||||
pub sandbox_policy: SandboxPolicy,
|
||||
|
||||
@@ -83,12 +83,16 @@ struct ConnectionState {
|
||||
#[derive(Clone)]
|
||||
struct ThreadMetadataState {
|
||||
thread_source: Option<&'static str>,
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
subagent_source: Option<String>,
|
||||
parent_thread_id: Option<String>,
|
||||
}
|
||||
|
||||
impl ThreadMetadataState {
|
||||
fn from_session_source(session_source: &SessionSource) -> Self {
|
||||
fn from_thread_lifecycle(
|
||||
session_source: &SessionSource,
|
||||
initialization_mode: ThreadInitializationMode,
|
||||
) -> Self {
|
||||
let (subagent_source, parent_thread_id) = match session_source {
|
||||
SessionSource::SubAgent(subagent_source) => (
|
||||
Some(subagent_source_name(subagent_source)),
|
||||
@@ -103,6 +107,7 @@ impl ThreadMetadataState {
|
||||
};
|
||||
Self {
|
||||
thread_source: thread_source_name(session_source),
|
||||
initialization_mode,
|
||||
subagent_source,
|
||||
parent_thread_id,
|
||||
}
|
||||
@@ -615,7 +620,8 @@ impl AnalyticsReducer {
|
||||
let Some(connection_state) = self.connections.get(&connection_id) else {
|
||||
return;
|
||||
};
|
||||
let thread_metadata = ThreadMetadataState::from_session_source(&thread_source);
|
||||
let thread_metadata =
|
||||
ThreadMetadataState::from_thread_lifecycle(&thread_source, initialization_mode);
|
||||
self.thread_connections
|
||||
.insert(thread_id.clone(), connection_id);
|
||||
self.thread_metadata
|
||||
@@ -717,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,
|
||||
@@ -739,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,
|
||||
),
|
||||
}));
|
||||
@@ -765,6 +782,24 @@ impl AnalyticsReducer {
|
||||
)
|
||||
});
|
||||
let Some((app_server_client, runtime)) = connection_metadata else {
|
||||
if let Some(connection_id) = turn_state.connection_id {
|
||||
tracing::warn!(
|
||||
turn_id,
|
||||
connection_id,
|
||||
"dropping turn analytics event: missing connection metadata"
|
||||
);
|
||||
}
|
||||
return;
|
||||
};
|
||||
let Some(thread_id) = turn_state.thread_id.as_ref() else {
|
||||
return;
|
||||
};
|
||||
let Some(thread_metadata) = self.thread_metadata.get(thread_id) else {
|
||||
tracing::warn!(
|
||||
thread_id,
|
||||
turn_id,
|
||||
"dropping turn analytics event: missing thread lifecycle metadata"
|
||||
);
|
||||
return;
|
||||
};
|
||||
out.push(TrackEventRequest::TurnEvent(Box::new(
|
||||
@@ -775,6 +810,7 @@ impl AnalyticsReducer {
|
||||
runtime,
|
||||
turn_id.to_string(),
|
||||
turn_state,
|
||||
thread_metadata,
|
||||
),
|
||||
},
|
||||
)));
|
||||
@@ -787,6 +823,7 @@ fn codex_turn_event_params(
|
||||
runtime: CodexRuntimeMetadata,
|
||||
turn_id: String,
|
||||
turn_state: &TurnState,
|
||||
thread_metadata: &ThreadMetadataState,
|
||||
) -> CodexTurnEventParams {
|
||||
let (Some(thread_id), Some(num_input_images), Some(resolved_config), Some(completed)) = (
|
||||
turn_state.thread_id.clone(),
|
||||
@@ -803,8 +840,7 @@ fn codex_turn_event_params(
|
||||
num_input_images: _resolved_num_input_images,
|
||||
submission_type,
|
||||
ephemeral,
|
||||
session_source,
|
||||
initialization_mode,
|
||||
session_source: _session_source,
|
||||
model,
|
||||
model_provider,
|
||||
sandbox_policy,
|
||||
@@ -826,10 +862,10 @@ fn codex_turn_event_params(
|
||||
runtime,
|
||||
submission_type,
|
||||
ephemeral,
|
||||
thread_source: thread_source_name(&session_source).map(str::to_string),
|
||||
initialization_mode,
|
||||
subagent_source: None,
|
||||
parent_thread_id: None,
|
||||
thread_source: thread_metadata.thread_source.map(str::to_string),
|
||||
initialization_mode: thread_metadata.initialization_mode,
|
||||
subagent_source: thread_metadata.subagent_source.clone(),
|
||||
parent_thread_id: thread_metadata.parent_thread_id.clone(),
|
||||
model: Some(model),
|
||||
model_provider,
|
||||
sandbox_policy: Some(sandbox_policy_mode(&sandbox_policy)),
|
||||
|
||||
@@ -395,6 +395,17 @@ async fn turn_start_tracks_turn_event_analytics() -> Result<()> {
|
||||
assert_eq!(event["event_params"]["model"], "mock-model");
|
||||
assert_eq!(event["event_params"]["model_provider"], "mock_provider");
|
||||
assert_eq!(event["event_params"]["sandbox_policy"], "read_only");
|
||||
assert_eq!(event["event_params"]["ephemeral"], false);
|
||||
assert_eq!(event["event_params"]["thread_source"], "user");
|
||||
assert_eq!(event["event_params"]["initialization_mode"], "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"]["num_input_images"], 1);
|
||||
assert_eq!(event["event_params"]["status"], "completed");
|
||||
assert!(event["event_params"]["started_at"].as_u64().is_some());
|
||||
|
||||
@@ -55,7 +55,6 @@ use codex_analytics::CompactionPhase;
|
||||
use codex_analytics::CompactionReason;
|
||||
use codex_analytics::InvocationType;
|
||||
use codex_analytics::SubAgentThreadStartedInput;
|
||||
use codex_analytics::ThreadInitializationMode;
|
||||
use codex_analytics::TurnResolvedConfigFact;
|
||||
use codex_analytics::build_track_events_context;
|
||||
use codex_app_server_protocol::AuthMode;
|
||||
@@ -657,7 +656,6 @@ impl Codex {
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools,
|
||||
persist_extended_history,
|
||||
inherited_shell_snapshot,
|
||||
@@ -1175,7 +1173,6 @@ pub(crate) struct SessionConfiguration {
|
||||
app_server_client_version: Option<String>,
|
||||
/// Source of the session (cli, vscode, exec, mcp, ...)
|
||||
session_source: SessionSource,
|
||||
thread_initialization_mode: ThreadInitializationMode,
|
||||
dynamic_tools: Vec<DynamicToolSpec>,
|
||||
persist_extended_history: bool,
|
||||
inherited_shell_snapshot: Option<Arc<ShellSnapshot>>,
|
||||
@@ -1200,7 +1197,6 @@ impl SessionConfiguration {
|
||||
reasoning_effort: self.collaboration_mode.reasoning_effort(),
|
||||
personality: self.personality,
|
||||
session_source: self.session_source.clone(),
|
||||
initialization_mode: self.thread_initialization_mode,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6534,7 +6530,6 @@ async fn track_turn_resolved_config_analytics(
|
||||
submission_type: None,
|
||||
ephemeral: thread_config.ephemeral,
|
||||
session_source: thread_config.session_source,
|
||||
initialization_mode: thread_config.initialization_mode,
|
||||
model: turn_context.model_info.slug.clone(),
|
||||
model_provider: turn_context.config.model_provider_id.clone(),
|
||||
sandbox_policy: turn_context.sandbox_policy.get().clone(),
|
||||
|
||||
@@ -15,8 +15,6 @@ use crate::mcp_tool_exposure::build_mcp_tool_exposure;
|
||||
use crate::shell::default_user_shell;
|
||||
use crate::tools::format_exec_output_str;
|
||||
|
||||
use codex_analytics::ThreadInitializationMode;
|
||||
|
||||
use codex_features::Features;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
@@ -1993,7 +1991,6 @@ async fn set_rate_limits_retains_previous_credits() {
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source: SessionSource::Exec,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools: Vec::new(),
|
||||
persist_extended_history: false,
|
||||
inherited_shell_snapshot: None,
|
||||
@@ -2096,7 +2093,6 @@ async fn set_rate_limits_updates_plan_type_when_present() {
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source: SessionSource::Exec,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools: Vec::new(),
|
||||
persist_extended_history: false,
|
||||
inherited_shell_snapshot: None,
|
||||
@@ -2449,7 +2445,6 @@ pub(crate) async fn make_session_configuration_for_tests() -> SessionConfigurati
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source: SessionSource::Exec,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools: Vec::new(),
|
||||
persist_extended_history: false,
|
||||
inherited_shell_snapshot: None,
|
||||
@@ -2713,7 +2708,6 @@ async fn session_new_fails_when_zsh_fork_enabled_without_zsh_path() {
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source: SessionSource::Exec,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools: Vec::new(),
|
||||
persist_extended_history: false,
|
||||
inherited_shell_snapshot: None,
|
||||
@@ -2818,7 +2812,6 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source: SessionSource::Exec,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools: Vec::new(),
|
||||
persist_extended_history: false,
|
||||
inherited_shell_snapshot: None,
|
||||
@@ -3664,7 +3657,6 @@ pub(crate) async fn make_session_and_context_with_dynamic_tools_and_rx(
|
||||
app_server_client_name: None,
|
||||
app_server_client_version: None,
|
||||
session_source: SessionSource::Exec,
|
||||
thread_initialization_mode: ThreadInitializationMode::New,
|
||||
dynamic_tools,
|
||||
persist_extended_history: false,
|
||||
inherited_shell_snapshot: None,
|
||||
|
||||
@@ -3,7 +3,6 @@ use crate::codex::Codex;
|
||||
use crate::codex::SteerInputError;
|
||||
use crate::config::ConstraintResult;
|
||||
use crate::file_watcher::WatchRegistration;
|
||||
use codex_analytics::ThreadInitializationMode;
|
||||
use codex_features::Feature;
|
||||
use codex_protocol::config_types::ApprovalsReviewer;
|
||||
use codex_protocol::config_types::Personality;
|
||||
@@ -44,7 +43,6 @@ pub struct ThreadConfigSnapshot {
|
||||
pub reasoning_effort: Option<ReasoningEffort>,
|
||||
pub personality: Option<Personality>,
|
||||
pub session_source: SessionSource,
|
||||
pub initialization_mode: ThreadInitializationMode,
|
||||
}
|
||||
|
||||
pub struct CodexThread {
|
||||
|
||||
Reference in New Issue
Block a user