Codex Handbook
analytics/src/events.rs 1190 lines
use std::time::Instant;use crate::facts::AcceptedLineFingerprint;use crate::facts::AppInvocation;use crate::facts::CodexCompactionEvent;use crate::facts::CodexErrKind;use crate::facts::CodexGoalEvent;use crate::facts::CompactionImplementation;use crate::facts::CompactionPhase;use crate::facts::CompactionReason;use crate::facts::CompactionStatus;use crate::facts::CompactionStrategy;use crate::facts::CompactionTrigger;use crate::facts::GoalEventKind;use crate::facts::HookRunFact;use crate::facts::InvocationType;use crate::facts::PluginState;use crate::facts::SubAgentThreadStartedInput;use crate::facts::ThreadInitializationMode;use crate::facts::TrackEventsContext;use crate::facts::TurnStatus;use crate::facts::TurnSteerRejectionReason;use crate::facts::TurnSteerResult;use crate::facts::TurnSubmissionType;use crate::now_unix_millis;use codex_app_server_protocol::CodexErrorInfo;use codex_app_server_protocol::CommandExecutionSource;use codex_login::default_client::originator;use codex_plugin::PluginTelemetryMetadata;use codex_protocol::approvals::NetworkApprovalProtocol;use codex_protocol::models::AdditionalPermissionProfile;use codex_protocol::models::SandboxPermissions;use codex_protocol::protocol::GuardianAssessmentOutcome;use codex_protocol::protocol::GuardianCommandSource;use codex_protocol::protocol::GuardianRiskLevel;use codex_protocol::protocol::GuardianUserAuthorization;use codex_protocol::protocol::HookEventName;use codex_protocol::protocol::HookRunStatus;use codex_protocol::protocol::HookSource;use codex_protocol::protocol::SubAgentSource;use codex_protocol::protocol::ThreadSource;use codex_protocol::protocol::TokenUsage;use serde::Serialize;#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub enum AppServerRpcTransport {    Stdio,    Websocket,    InProcess,}#[derive(Serialize)]pub(crate) struct TrackEventsRequest {    pub(crate) events: Vec<TrackEventRequest>,}#[derive(Serialize)]#[serde(untagged)]pub(crate) enum TrackEventRequest {    SkillInvocation(SkillInvocationEventRequest),    ThreadInitialized(ThreadInitializedEvent),    GuardianReview(Box<GuardianReviewEventRequest>),    AppMentioned(CodexAppMentionedEventRequest),    AppUsed(CodexAppUsedEventRequest),    HookRun(CodexHookRunEventRequest),    Compaction(Box<CodexCompactionEventRequest>),    Goal(Box<CodexGoalEventRequest>),    TurnEvent(Box<CodexTurnEventRequest>),    TurnSteer(CodexTurnSteerEventRequest),    CommandExecution(CodexCommandExecutionEventRequest),    FileChange(CodexFileChangeEventRequest),    McpToolCall(CodexMcpToolCallEventRequest),    DynamicToolCall(CodexDynamicToolCallEventRequest),    CollabAgentToolCall(CodexCollabAgentToolCallEventRequest),    WebSearch(CodexWebSearchEventRequest),    ImageGeneration(CodexImageGenerationEventRequest),    AcceptedLineFingerprints(Box<CodexAcceptedLineFingerprintsEventRequest>),    #[allow(dead_code)]    ReviewEvent(CodexReviewEventRequest),    PluginUsed(CodexPluginUsedEventRequest),    PluginInstalled(CodexPluginEventRequest),    PluginUninstalled(CodexPluginEventRequest),    PluginEnabled(CodexPluginEventRequest),    PluginDisabled(CodexPluginEventRequest),}impl TrackEventRequest {    pub(crate) fn should_send_in_isolated_request(&self) -> bool {        matches!(self, Self::AcceptedLineFingerprints(_))    }}#[derive(Serialize)]pub(crate) struct CodexAcceptedLineFingerprintsEventParams {    pub(crate) event_type: &'static str,    pub(crate) turn_id: String,    pub(crate) thread_id: String,    pub(crate) product_surface: Option<String>,    pub(crate) model_slug: Option<String>,    pub(crate) completed_at: u64,    pub(crate) repo_hash: Option<String>,    pub(crate) accepted_added_lines: u64,    pub(crate) accepted_deleted_lines: u64,    pub(crate) line_fingerprints: Vec<AcceptedLineFingerprint>,}#[derive(Serialize)]pub(crate) struct CodexAcceptedLineFingerprintsEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexAcceptedLineFingerprintsEventParams,}#[derive(Serialize)]pub(crate) struct SkillInvocationEventRequest {    pub(crate) event_type: &'static str,    pub(crate) skill_id: String,    pub(crate) skill_name: String,    pub(crate) event_params: SkillInvocationEventParams,}#[derive(Serialize)]pub(crate) struct SkillInvocationEventParams {    pub(crate) product_client_id: Option<String>,    pub(crate) skill_scope: Option<String>,    pub(crate) plugin_id: Option<String>,    pub(crate) repo_url: Option<String>,    pub(crate) thread_id: Option<String>,    pub(crate) turn_id: Option<String>,    pub(crate) invoke_type: Option<InvocationType>,    pub(crate) model_slug: Option<String>,}#[derive(Clone, Serialize)]pub(crate) struct CodexAppServerClientMetadata {    pub(crate) product_client_id: String,    pub(crate) client_name: Option<String>,    pub(crate) client_version: Option<String>,    pub(crate) rpc_transport: AppServerRpcTransport,    pub(crate) experimental_api_enabled: Option<bool>,}#[derive(Clone, Serialize)]pub(crate) struct CodexRuntimeMetadata {    pub(crate) codex_rs_version: String,    pub(crate) runtime_os: String,    pub(crate) runtime_os_version: String,    pub(crate) runtime_arch: String,}#[derive(Serialize)]pub(crate) struct ThreadInitializedEventParams {    pub(crate) thread_id: String,    pub(crate) session_id: String,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) model: String,    pub(crate) ephemeral: bool,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) initialization_mode: ThreadInitializationMode,    pub(crate) subagent_source: Option<String>,    pub(crate) parent_thread_id: Option<String>,    pub(crate) forked_from_thread_id: Option<String>,    pub(crate) created_at: u64,}#[derive(Serialize)]pub(crate) struct ThreadInitializedEvent {    pub(crate) event_type: &'static str,    pub(crate) event_params: ThreadInitializedEventParams,}#[derive(Serialize)]pub(crate) struct GuardianReviewEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: GuardianReviewEventPayload,}#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub enum GuardianReviewDecision {    Approved,    Denied,    Aborted,}#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub enum GuardianReviewTerminalStatus {    Approved,    Denied,    Aborted,    TimedOut,    FailedClosed,}#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub enum GuardianReviewFailureReason {    Timeout,    Cancelled,    PromptBuildError,    SessionError,    ParseError,}#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub enum GuardianReviewSessionKind {    TrunkNew,    TrunkReused,    EphemeralForked,}#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub enum GuardianApprovalRequestSource {    /// Approval requested directly by the main Codex turn.    MainTurn,    /// Approval requested by a delegated subagent and routed through the parent    /// session for guardian review.    DelegatedSubagent,}#[derive(Clone, Debug, Serialize)]#[serde(tag = "type", rename_all = "snake_case")]pub enum GuardianReviewedAction {    Shell {        sandbox_permissions: SandboxPermissions,        additional_permissions: Option<AdditionalPermissionProfile>,    },    UnifiedExec {        sandbox_permissions: SandboxPermissions,        additional_permissions: Option<AdditionalPermissionProfile>,        tty: bool,    },    Execve {        source: GuardianCommandSource,        program: String,        additional_permissions: Option<AdditionalPermissionProfile>,    },    ApplyPatch {},    NetworkAccess {        protocol: NetworkApprovalProtocol,        port: u16,    },    McpToolCall {        server: String,        tool_name: String,        connector_id: Option<String>,        connector_name: Option<String>,        tool_title: Option<String>,    },    RequestPermissions {},}#[derive(Clone, Serialize)]pub struct GuardianReviewEventParams {    pub thread_id: String,    pub turn_id: String,    pub review_id: String,    pub target_item_id: Option<String>,    pub approval_request_source: GuardianApprovalRequestSource,    pub reviewed_action: GuardianReviewedAction,    pub reviewed_action_truncated: bool,    pub decision: GuardianReviewDecision,    pub terminal_status: GuardianReviewTerminalStatus,    pub failure_reason: Option<GuardianReviewFailureReason>,    pub attempt_count: i64,    pub risk_level: Option<GuardianRiskLevel>,    pub user_authorization: Option<GuardianUserAuthorization>,    pub outcome: Option<GuardianAssessmentOutcome>,    pub guardian_thread_id: Option<String>,    pub guardian_session_kind: Option<GuardianReviewSessionKind>,    pub guardian_model: Option<String>,    pub guardian_reasoning_effort: Option<String>,    pub guardian_default_review_model_id: Option<String>,    pub guardian_catalog_contains_auto_review: Option<bool>,    pub guardian_review_model_overridden: Option<bool>,    pub guardian_review_model_override: Option<String>,    pub guardian_model_provider_id: Option<String>,    pub had_prior_review_context: Option<bool>,    pub review_timeout_ms: u64,    pub tool_call_count: Option<u64>,    pub time_to_first_token_ms: Option<u64>,    pub completion_latency_ms: Option<u64>,    pub started_at: u64,    pub completed_at: Option<u64>,    pub input_tokens: Option<i64>,    pub cached_input_tokens: Option<i64>,    pub output_tokens: Option<i64>,    pub reasoning_output_tokens: Option<i64>,    pub total_tokens: Option<i64>,}pub struct GuardianReviewTrackContext {    thread_id: String,    turn_id: String,    review_id: String,    target_item_id: Option<String>,    approval_request_source: GuardianApprovalRequestSource,    reviewed_action: GuardianReviewedAction,    review_timeout_ms: u64,    pub started_at_ms: u64,    started_instant: Instant,}impl GuardianReviewTrackContext {    pub fn new(        thread_id: String,        turn_id: String,        review_id: String,        target_item_id: Option<String>,        approval_request_source: GuardianApprovalRequestSource,        reviewed_action: GuardianReviewedAction,        review_timeout_ms: u64,    ) -> Self {        Self {            thread_id,            turn_id,            review_id,            target_item_id,            approval_request_source,            reviewed_action,            review_timeout_ms,            started_at_ms: now_unix_millis(),            started_instant: Instant::now(),        }    }    pub(crate) fn event_params(        &self,        result: GuardianReviewAnalyticsResult,        completed_at_ms: u64,    ) -> GuardianReviewEventParams {        GuardianReviewEventParams {            thread_id: self.thread_id.clone(),            turn_id: self.turn_id.clone(),            review_id: self.review_id.clone(),            target_item_id: self.target_item_id.clone(),            approval_request_source: self.approval_request_source,            reviewed_action: self.reviewed_action.clone(),            reviewed_action_truncated: result.reviewed_action_truncated,            decision: result.decision,            terminal_status: result.terminal_status,            failure_reason: result.failure_reason,            attempt_count: result.attempt_count,            risk_level: result.risk_level,            user_authorization: result.user_authorization,            outcome: result.outcome,            guardian_thread_id: result.guardian_thread_id,            guardian_session_kind: result.guardian_session_kind,            guardian_model: result.guardian_model,            guardian_reasoning_effort: result.guardian_reasoning_effort,            guardian_default_review_model_id: result.guardian_default_review_model_id,            guardian_catalog_contains_auto_review: result.guardian_catalog_contains_auto_review,            guardian_review_model_overridden: result.guardian_review_model_overridden,            guardian_review_model_override: result.guardian_review_model_override,            guardian_model_provider_id: result.guardian_model_provider_id,            had_prior_review_context: result.had_prior_review_context,            review_timeout_ms: self.review_timeout_ms,            // TODO(rhan-oai): plumb nested Guardian review session tool-call counts.            tool_call_count: None,            time_to_first_token_ms: result.time_to_first_token_ms,            completion_latency_ms: Some(self.started_instant.elapsed().as_millis() as u64),            started_at: self.started_at_ms / 1_000,            completed_at: Some(completed_at_ms / 1_000),            input_tokens: result.token_usage.as_ref().map(|usage| usage.input_tokens),            cached_input_tokens: result                .token_usage                .as_ref()                .map(|usage| usage.cached_input_tokens),            output_tokens: result.token_usage.as_ref().map(|usage| usage.output_tokens),            reasoning_output_tokens: result                .token_usage                .as_ref()                .map(|usage| usage.reasoning_output_tokens),            total_tokens: result.token_usage.as_ref().map(|usage| usage.total_tokens),        }    }}#[derive(Debug)]pub struct GuardianReviewAnalyticsResult {    pub decision: GuardianReviewDecision,    pub terminal_status: GuardianReviewTerminalStatus,    pub failure_reason: Option<GuardianReviewFailureReason>,    pub attempt_count: i64,    pub risk_level: Option<GuardianRiskLevel>,    pub user_authorization: Option<GuardianUserAuthorization>,    pub outcome: Option<GuardianAssessmentOutcome>,    pub guardian_thread_id: Option<String>,    pub guardian_session_kind: Option<GuardianReviewSessionKind>,    pub guardian_model: Option<String>,    pub guardian_reasoning_effort: Option<String>,    pub guardian_default_review_model_id: Option<String>,    pub guardian_catalog_contains_auto_review: Option<bool>,    pub guardian_review_model_overridden: Option<bool>,    pub guardian_review_model_override: Option<String>,    pub guardian_model_provider_id: Option<String>,    pub had_prior_review_context: Option<bool>,    pub reviewed_action_truncated: bool,    pub token_usage: Option<TokenUsage>,    pub time_to_first_token_ms: Option<u64>,}impl GuardianReviewAnalyticsResult {    pub fn without_session() -> Self {        Self {            decision: GuardianReviewDecision::Denied,            terminal_status: GuardianReviewTerminalStatus::FailedClosed,            failure_reason: None,            attempt_count: 1,            risk_level: None,            user_authorization: None,            outcome: None,            guardian_thread_id: None,            guardian_session_kind: None,            guardian_model: None,            guardian_reasoning_effort: None,            guardian_default_review_model_id: None,            guardian_catalog_contains_auto_review: None,            guardian_review_model_overridden: None,            guardian_review_model_override: None,            guardian_model_provider_id: None,            had_prior_review_context: None,            reviewed_action_truncated: false,            token_usage: None,            time_to_first_token_ms: None,        }    }    pub fn from_session(params: GuardianReviewSessionAnalyticsParams) -> Self {        Self {            guardian_thread_id: Some(params.guardian_thread_id),            guardian_session_kind: Some(params.guardian_session_kind),            guardian_model: Some(params.guardian_model),            guardian_reasoning_effort: params.guardian_reasoning_effort,            guardian_default_review_model_id: Some(params.guardian_default_review_model_id),            guardian_catalog_contains_auto_review: Some(                params.guardian_catalog_contains_auto_review,            ),            guardian_review_model_overridden: Some(params.guardian_review_model_overridden),            guardian_review_model_override: params.guardian_review_model_override,            guardian_model_provider_id: Some(params.guardian_model_provider_id),            had_prior_review_context: Some(params.had_prior_review_context),            ..Self::without_session()        }    }}pub struct GuardianReviewSessionAnalyticsParams {    pub guardian_thread_id: String,    pub guardian_session_kind: GuardianReviewSessionKind,    pub guardian_model: String,    pub guardian_reasoning_effort: Option<String>,    pub guardian_default_review_model_id: String,    pub guardian_catalog_contains_auto_review: bool,    pub guardian_review_model_overridden: bool,    pub guardian_review_model_override: Option<String>,    pub guardian_model_provider_id: String,    pub had_prior_review_context: bool,}#[derive(Serialize)]pub(crate) struct GuardianReviewEventPayload {    pub(crate) session_id: String,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    #[serde(flatten)]    pub(crate) guardian_review: GuardianReviewEventParams,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum FinalApprovalOutcome {    Unknown,    NotNeeded,    ConfigAllowed,    PolicyForbidden,    GuardianApproved,    GuardianDenied,    GuardianAborted,    UserApproved,    UserApprovedForSession,    UserDenied,    UserAborted,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum ToolItemTerminalStatus {    Completed,    Failed,    Rejected,    Interrupted,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum ToolItemFailureKind {    ToolError,    ApprovalDenied,    ApprovalAborted,    SandboxDenied,    PolicyForbidden,}#[derive(Serialize)]pub(crate) struct CodexToolItemEventBase {    pub(crate) thread_id: String,    pub(crate) turn_id: String,    /// App-server ThreadItem.id. For tool-originated items this generally    /// corresponds to the originating core call_id.    pub(crate) item_id: String,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) subagent_source: Option<String>,    pub(crate) parent_thread_id: Option<String>,    pub(crate) tool_name: String,    pub(crate) started_at_ms: u64,    pub(crate) completed_at_ms: u64,    // Observed item lifecycle duration. This may undercount end-to-end execution    // for tools where app-server only sees part of the upstream flow.    pub(crate) duration_ms: Option<u64>,    pub(crate) execution_duration_ms: Option<u64>,    pub(crate) review_count: u64,    pub(crate) guardian_review_count: u64,    pub(crate) user_review_count: u64,    pub(crate) final_approval_outcome: FinalApprovalOutcome,    pub(crate) terminal_status: ToolItemTerminalStatus,    pub(crate) failure_kind: Option<ToolItemFailureKind>,    pub(crate) requested_additional_permissions: bool,    pub(crate) requested_network_access: bool,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum ReviewSubjectKind {    CommandExecution,    FileChange,    McpToolCall,    Permissions,    NetworkAccess,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum Reviewer {    Guardian,    User,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum ReviewTrigger {    Initial,    SandboxDenial,    NetworkPolicyDenial,    ExecveIntercept,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum ReviewStatus {    Approved,    Denied,    Aborted,    TimedOut,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum ReviewResolution {    None,    SessionApproval,    ExecPolicyAmendment,    NetworkPolicyAmendment,}#[derive(Serialize)]pub(crate) struct CodexReviewEventParams {    pub(crate) thread_id: String,    pub(crate) turn_id: String,    pub(crate) item_id: Option<String>,    pub(crate) review_id: String,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) subagent_source: Option<String>,    pub(crate) parent_thread_id: Option<String>,    pub(crate) subject_kind: ReviewSubjectKind,    pub(crate) subject_name: String,    pub(crate) reviewer: Reviewer,    pub(crate) trigger: ReviewTrigger,    pub(crate) status: ReviewStatus,    pub(crate) resolution: ReviewResolution,    pub(crate) started_at_ms: u64,    pub(crate) completed_at_ms: u64,    pub(crate) duration_ms: Option<u64>,}#[derive(Serialize)]pub(crate) struct CodexReviewEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexReviewEventParams,}#[allow(dead_code)]#[derive(Clone, Copy, Debug, Serialize)]#[serde(rename_all = "snake_case")]pub(crate) enum WebSearchActionKind {    Search,    OpenPage,    FindInPage,    Other,}#[derive(Serialize)]pub(crate) struct CodexCommandExecutionEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) command_execution_source: CommandExecutionSource,    pub(crate) exit_code: Option<i32>,    pub(crate) command_total_action_count: u64,    pub(crate) command_read_action_count: u64,    pub(crate) command_list_files_action_count: u64,    pub(crate) command_search_action_count: u64,    pub(crate) command_unknown_action_count: u64,}#[derive(Serialize)]pub(crate) struct CodexCommandExecutionEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexCommandExecutionEventParams,}#[derive(Serialize)]pub(crate) struct CodexFileChangeEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) file_change_count: u64,    pub(crate) file_add_count: u64,    pub(crate) file_update_count: u64,    pub(crate) file_delete_count: u64,    pub(crate) file_move_count: u64,}#[derive(Serialize)]pub(crate) struct CodexFileChangeEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexFileChangeEventParams,}#[derive(Serialize)]pub(crate) struct CodexMcpToolCallEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) mcp_server_name: String,    pub(crate) mcp_tool_name: String,    pub(crate) mcp_error_present: bool,    pub(crate) plugin_id: Option<String>,}#[derive(Serialize)]pub(crate) struct CodexMcpToolCallEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexMcpToolCallEventParams,}#[derive(Serialize)]pub(crate) struct CodexDynamicToolCallEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) dynamic_tool_name: String,    pub(crate) success: Option<bool>,    pub(crate) output_content_item_count: Option<u64>,    pub(crate) output_text_item_count: Option<u64>,    pub(crate) output_image_item_count: Option<u64>,}#[derive(Serialize)]pub(crate) struct CodexDynamicToolCallEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexDynamicToolCallEventParams,}#[derive(Serialize)]pub(crate) struct CodexCollabAgentToolCallEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) sender_thread_id: String,    pub(crate) receiver_thread_count: u64,    pub(crate) receiver_thread_ids: Option<Vec<String>>,    pub(crate) requested_model: Option<String>,    pub(crate) requested_reasoning_effort: Option<String>,    pub(crate) agent_state_count: Option<u64>,    pub(crate) completed_agent_count: Option<u64>,    pub(crate) failed_agent_count: Option<u64>,}#[derive(Serialize)]pub(crate) struct CodexCollabAgentToolCallEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexCollabAgentToolCallEventParams,}#[derive(Serialize)]pub(crate) struct CodexWebSearchEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) web_search_action: Option<WebSearchActionKind>,    pub(crate) query_present: bool,    pub(crate) query_count: Option<u64>,}#[derive(Serialize)]pub(crate) struct CodexWebSearchEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexWebSearchEventParams,}#[derive(Serialize)]pub(crate) struct CodexImageGenerationEventParams {    #[serde(flatten)]    pub(crate) base: CodexToolItemEventBase,    pub(crate) revised_prompt_present: bool,    pub(crate) saved_path_present: bool,}#[derive(Serialize)]pub(crate) struct CodexImageGenerationEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexImageGenerationEventParams,}#[derive(Serialize)]pub(crate) struct CodexAppMetadata {    pub(crate) connector_id: Option<String>,    pub(crate) thread_id: Option<String>,    pub(crate) turn_id: Option<String>,    pub(crate) app_name: Option<String>,    pub(crate) product_client_id: Option<String>,    pub(crate) invoke_type: Option<InvocationType>,    pub(crate) model_slug: Option<String>,}#[derive(Serialize)]pub(crate) struct CodexAppMentionedEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexAppMetadata,}#[derive(Serialize)]pub(crate) struct CodexAppUsedEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexAppMetadata,}#[derive(Serialize)]pub(crate) struct CodexHookRunMetadata {    pub(crate) thread_id: Option<String>,    pub(crate) turn_id: Option<String>,    pub(crate) model_slug: Option<String>,    pub(crate) hook_name: Option<String>,    pub(crate) hook_source: Option<&'static str>,    pub(crate) status: Option<HookRunStatus>,}#[derive(Serialize)]pub(crate) struct CodexHookRunEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexHookRunMetadata,}#[derive(Serialize)]pub(crate) struct CodexCompactionEventParams {    pub(crate) thread_id: String,    pub(crate) session_id: String,    pub(crate) turn_id: String,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) subagent_source: Option<String>,    pub(crate) parent_thread_id: Option<String>,    pub(crate) trigger: CompactionTrigger,    pub(crate) reason: CompactionReason,    pub(crate) implementation: CompactionImplementation,    pub(crate) phase: CompactionPhase,    pub(crate) strategy: CompactionStrategy,    pub(crate) status: CompactionStatus,    pub(crate) codex_error_kind: Option<CodexErrKind>,    pub(crate) codex_error_http_status_code: Option<u16>,    pub(crate) active_context_tokens_before: i64,    pub(crate) active_context_tokens_after: i64,    pub(crate) retained_image_count: Option<usize>,    pub(crate) compaction_summary_tokens: Option<i64>,    pub(crate) cached_input_tokens: Option<i64>,    pub(crate) started_at: u64,    pub(crate) completed_at: u64,    pub(crate) duration_ms: Option<u64>,}#[derive(Serialize)]pub(crate) struct CodexCompactionEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexCompactionEventParams,}#[derive(Serialize)]pub(crate) struct CodexGoalEventParams {    pub(crate) thread_id: String,    pub(crate) session_id: String,    pub(crate) turn_id: Option<String>,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) subagent_source: Option<String>,    pub(crate) parent_thread_id: Option<String>,    pub(crate) goal_id: String,    pub(crate) event_kind: GoalEventKind,    pub(crate) goal_status: codex_state::ThreadGoalStatus,    pub(crate) has_token_budget: bool,    pub(crate) cumulative_tokens_accounted: Option<i64>,    pub(crate) cumulative_time_accounted_seconds: Option<i64>,}#[derive(Serialize)]pub(crate) struct CodexGoalEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexGoalEventParams,}#[derive(Serialize)]pub(crate) struct CodexTurnEventParams {    pub(crate) thread_id: String,    pub(crate) session_id: String,    pub(crate) turn_id: String,    // TODO(rhan-oai): Populate once queued/default submission type is plumbed from    // the turn/start callsites instead of always being reported as None.    pub(crate) submission_type: Option<TurnSubmissionType>,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) ephemeral: bool,    pub(crate) thread_source: Option<ThreadSource>,    pub(crate) initialization_mode: ThreadInitializationMode,    pub(crate) subagent_source: Option<String>,    pub(crate) parent_thread_id: Option<String>,    pub(crate) model: Option<String>,    pub(crate) model_provider: String,    pub(crate) sandbox_policy: Option<&'static str>,    pub(crate) reasoning_effort: Option<String>,    pub(crate) reasoning_summary: Option<String>,    pub(crate) service_tier: String,    pub(crate) approval_policy: String,    pub(crate) approvals_reviewer: String,    pub(crate) sandbox_network_access: bool,    pub(crate) collaboration_mode: Option<&'static str>,    pub(crate) personality: Option<String>,    pub(crate) workspace_kind: Option<String>,    pub(crate) num_input_images: usize,    pub(crate) is_first_turn: bool,    pub(crate) status: Option<TurnStatus>,    pub(crate) turn_error: Option<CodexErrorInfo>,    pub(crate) codex_error_kind: Option<CodexErrKind>,    pub(crate) codex_error_http_status_code: Option<u16>,    pub(crate) steer_count: Option<usize>,    pub(crate) total_tool_call_count: Option<usize>,    pub(crate) shell_command_count: Option<usize>,    pub(crate) file_change_count: Option<usize>,    pub(crate) mcp_tool_call_count: Option<usize>,    pub(crate) dynamic_tool_call_count: Option<usize>,    pub(crate) subagent_tool_call_count: Option<usize>,    pub(crate) web_search_count: Option<usize>,    pub(crate) image_generation_count: Option<usize>,    pub(crate) input_tokens: Option<i64>,    pub(crate) cached_input_tokens: Option<i64>,    pub(crate) output_tokens: Option<i64>,    pub(crate) reasoning_output_tokens: Option<i64>,    pub(crate) total_tokens: Option<i64>,    pub(crate) before_first_sampling_ms: u64,    pub(crate) sampling_ms: u64,    pub(crate) between_sampling_overhead_ms: u64,    pub(crate) tool_blocking_ms: u64,    pub(crate) after_last_sampling_ms: u64,    pub(crate) sampling_request_count: u32,    pub(crate) sampling_retry_count: u32,    pub(crate) duration_ms: Option<u64>,    pub(crate) started_at: Option<u64>,    pub(crate) completed_at: Option<u64>,}#[derive(Serialize)]pub(crate) struct CodexTurnEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexTurnEventParams,}#[derive(Serialize)]pub(crate) struct CodexTurnSteerEventParams {    pub(crate) thread_id: String,    pub(crate) session_id: String,    pub(crate) expected_turn_id: Option<String>,    pub(crate) accepted_turn_id: Option<String>,    pub(crate) app_server_client: CodexAppServerClientMetadata,    pub(crate) runtime: CodexRuntimeMetadata,    pub(crate) thread_source: Option<ThreadSource>,    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>,    pub(crate) created_at: u64,}#[derive(Serialize)]pub(crate) struct CodexTurnSteerEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexTurnSteerEventParams,}#[derive(Serialize)]pub(crate) struct CodexPluginMetadata {    pub(crate) plugin_id: Option<String>,    pub(crate) plugin_name: Option<String>,    pub(crate) marketplace_name: Option<String>,    pub(crate) has_skills: Option<bool>,    pub(crate) mcp_server_count: Option<usize>,    pub(crate) connector_ids: Option<Vec<String>>,    pub(crate) product_client_id: Option<String>,}#[derive(Serialize)]pub(crate) struct CodexPluginUsedMetadata {    #[serde(flatten)]    pub(crate) plugin: CodexPluginMetadata,    pub(crate) mcp_server_names: Option<Vec<String>>,    pub(crate) thread_id: Option<String>,    pub(crate) turn_id: Option<String>,    pub(crate) model_slug: Option<String>,}#[derive(Serialize)]pub(crate) struct CodexPluginEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexPluginMetadata,}#[derive(Serialize)]pub(crate) struct CodexPluginUsedEventRequest {    pub(crate) event_type: &'static str,    pub(crate) event_params: CodexPluginUsedMetadata,}pub(crate) fn plugin_state_event_type(state: PluginState) -> &'static str {    match state {        PluginState::Installed => "codex_plugin_installed",        PluginState::Uninstalled => "codex_plugin_uninstalled",        PluginState::Enabled => "codex_plugin_enabled",        PluginState::Disabled => "codex_plugin_disabled",    }}pub(crate) fn codex_app_metadata(    tracking: &TrackEventsContext,    app: AppInvocation,) -> CodexAppMetadata {    CodexAppMetadata {        connector_id: app.connector_id,        thread_id: Some(tracking.thread_id.clone()),        turn_id: Some(tracking.turn_id.clone()),        app_name: app.app_name,        product_client_id: Some(originator().value),        invoke_type: app.invocation_type,        model_slug: Some(tracking.model_slug.clone()),    }}pub(crate) fn codex_plugin_metadata(plugin: PluginTelemetryMetadata) -> CodexPluginMetadata {    let PluginTelemetryMetadata {        plugin_id,        remote_plugin_id,        capability_summary,    } = plugin;    let event_plugin_id = remote_plugin_id.unwrap_or_else(|| plugin_id.as_key());    CodexPluginMetadata {        plugin_id: Some(event_plugin_id),        plugin_name: Some(plugin_id.plugin_name),        marketplace_name: Some(plugin_id.marketplace_name),        has_skills: capability_summary            .as_ref()            .map(|summary| summary.has_skills),        mcp_server_count: capability_summary            .as_ref()            .map(|summary| summary.mcp_server_names.len()),        connector_ids: capability_summary.map(|summary| {            summary                .app_connector_ids                .into_iter()                .map(|connector_id| connector_id.0)                .collect()        }),        product_client_id: Some(originator().value),    }}pub(crate) fn codex_compaction_event_params(    input: CodexCompactionEvent,    session_id: String,    app_server_client: CodexAppServerClientMetadata,    runtime: CodexRuntimeMetadata,    thread_source: Option<ThreadSource>,    subagent_source: Option<String>,    parent_thread_id: Option<String>,) -> CodexCompactionEventParams {    CodexCompactionEventParams {        thread_id: input.thread_id,        session_id,        turn_id: input.turn_id,        app_server_client,        runtime,        thread_source,        subagent_source,        parent_thread_id,        trigger: input.trigger,        reason: input.reason,        implementation: input.implementation,        phase: input.phase,        strategy: input.strategy,        status: input.status,        codex_error_kind: input.codex_error_kind,        codex_error_http_status_code: input.codex_error_http_status_code,        active_context_tokens_before: input.active_context_tokens_before,        active_context_tokens_after: input.active_context_tokens_after,        retained_image_count: input.retained_image_count,        compaction_summary_tokens: input.compaction_summary_tokens,        cached_input_tokens: input.cached_input_tokens,        started_at: input.started_at,        completed_at: input.completed_at,        duration_ms: input.duration_ms,    }}pub(crate) fn codex_goal_event_params(    input: CodexGoalEvent,    session_id: String,    app_server_client: CodexAppServerClientMetadata,    runtime: CodexRuntimeMetadata,    thread_source: Option<ThreadSource>,    subagent_source: Option<String>,    parent_thread_id: Option<String>,) -> CodexGoalEventParams {    CodexGoalEventParams {        thread_id: input.thread_id,        session_id,        turn_id: input.turn_id,        app_server_client,        runtime,        thread_source,        subagent_source,        parent_thread_id,        goal_id: input.goal_id,        event_kind: input.event_kind,        goal_status: input.goal_status,        has_token_budget: input.has_token_budget,        cumulative_tokens_accounted: input.cumulative_tokens_accounted,        cumulative_time_accounted_seconds: input.cumulative_time_accounted_seconds,    }}pub(crate) fn codex_plugin_used_metadata(    tracking: &TrackEventsContext,    plugin: PluginTelemetryMetadata,) -> CodexPluginUsedMetadata {    let mcp_server_names = plugin        .capability_summary        .as_ref()        .map(|summary| summary.mcp_server_names.clone());    CodexPluginUsedMetadata {        plugin: codex_plugin_metadata(plugin),        mcp_server_names,        thread_id: Some(tracking.thread_id.clone()),        turn_id: Some(tracking.turn_id.clone()),        model_slug: Some(tracking.model_slug.clone()),    }}pub(crate) fn codex_hook_run_metadata(    tracking: &TrackEventsContext,    hook: HookRunFact,) -> CodexHookRunMetadata {    CodexHookRunMetadata {        thread_id: Some(tracking.thread_id.clone()),        turn_id: Some(tracking.turn_id.clone()),        model_slug: Some(tracking.model_slug.clone()),        hook_name: Some(analytics_hook_event_name(hook.event_name).to_owned()),        hook_source: Some(analytics_hook_source(hook.hook_source)),        status: Some(analytics_hook_status(hook.status)),    }}fn analytics_hook_event_name(event_name: HookEventName) -> &'static str {    match event_name {        HookEventName::PreToolUse => "PreToolUse",        HookEventName::PermissionRequest => "PermissionRequest",        HookEventName::PostToolUse => "PostToolUse",        HookEventName::PreCompact => "PreCompact",        HookEventName::PostCompact => "PostCompact",        HookEventName::SessionStart => "SessionStart",        HookEventName::UserPromptSubmit => "UserPromptSubmit",        HookEventName::SubagentStart => "SubagentStart",        HookEventName::SubagentStop => "SubagentStop",        HookEventName::Stop => "Stop",    }}fn analytics_hook_source(source: HookSource) -> &'static str {    match source {        HookSource::System => "system",        HookSource::User => "user",        HookSource::Project => "project",        HookSource::Mdm => "mdm",        HookSource::SessionFlags => "session_flags",        HookSource::Plugin => "plugin",        HookSource::CloudRequirements => "cloud_requirements",        HookSource::CloudManagedConfig => "cloud_managed_config",        HookSource::LegacyManagedConfigFile => "legacy_managed_config_file",        HookSource::LegacyManagedConfigMdm => "legacy_managed_config_mdm",        HookSource::Unknown => "unknown",    }}pub(crate) fn current_runtime_metadata() -> CodexRuntimeMetadata {    let os_info = os_info::get();    CodexRuntimeMetadata {        codex_rs_version: env!("CARGO_PKG_VERSION").to_string(),        runtime_os: std::env::consts::OS.to_string(),        runtime_os_version: os_info.version().to_string(),        runtime_arch: std::env::consts::ARCH.to_string(),    }}pub(crate) fn subagent_thread_started_event_request(    input: SubAgentThreadStartedInput,) -> ThreadInitializedEvent {    let event_params = ThreadInitializedEventParams {        thread_id: input.thread_id,        session_id: input.session_id,        app_server_client: CodexAppServerClientMetadata {            product_client_id: input.product_client_id,            client_name: Some(input.client_name),            client_version: Some(input.client_version),            rpc_transport: AppServerRpcTransport::InProcess,            experimental_api_enabled: None,        },        runtime: current_runtime_metadata(),        model: input.model,        ephemeral: input.ephemeral,        thread_source: Some(ThreadSource::Subagent),        initialization_mode: ThreadInitializationMode::New,        subagent_source: Some(subagent_source_name(&input.subagent_source)),        parent_thread_id: input.parent_thread_id,        forked_from_thread_id: input.forked_from_thread_id,        created_at: input.created_at,    };    ThreadInitializedEvent {        event_type: "codex_thread_initialized",        event_params,    }}pub(crate) fn subagent_source_name(subagent_source: &SubAgentSource) -> String {    subagent_source.kind().to_string()}fn analytics_hook_status(status: HookRunStatus) -> HookRunStatus {    match status {        // Running is unexpected here and normalized defensively.        HookRunStatus::Running => HookRunStatus::Failed,        other => other,    }}