use crate::facts::AppInvocation; use crate::facts::CodexCompactionEvent; 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::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 codex_app_server_protocol::CodexErrorInfo; use codex_login::default_client::originator; use codex_plugin::PluginTelemetryMetadata; use codex_protocol::approvals::NetworkApprovalProtocol; use codex_protocol::models::PermissionProfile; 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 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, } #[derive(Serialize)] #[serde(untagged)] pub(crate) enum TrackEventRequest { SkillInvocation(SkillInvocationEventRequest), ThreadInitialized(ThreadInitializedEvent), GuardianReview(Box), AppMentioned(CodexAppMentionedEventRequest), AppUsed(CodexAppUsedEventRequest), HookRun(CodexHookRunEventRequest), Compaction(Box), TurnEvent(Box), TurnSteer(CodexTurnSteerEventRequest), PluginUsed(CodexPluginUsedEventRequest), PluginInstalled(CodexPluginEventRequest), PluginUninstalled(CodexPluginEventRequest), PluginEnabled(CodexPluginEventRequest), PluginDisabled(CodexPluginEventRequest), } #[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, pub(crate) skill_scope: Option, pub(crate) repo_url: Option, pub(crate) thread_id: Option, pub(crate) invoke_type: Option, pub(crate) model_slug: Option, } #[derive(Clone, Serialize)] pub(crate) struct CodexAppServerClientMetadata { pub(crate) product_client_id: String, pub(crate) client_name: Option, pub(crate) client_version: Option, pub(crate) rpc_transport: AppServerRpcTransport, pub(crate) experimental_api_enabled: Option, } #[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) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, pub(crate) model: String, pub(crate) ephemeral: bool, pub(crate) thread_source: Option<&'static str>, pub(crate) initialization_mode: ThreadInitializationMode, pub(crate) subagent_source: Option, pub(crate) parent_thread_id: Option, 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, }, UnifiedExec { sandbox_permissions: SandboxPermissions, additional_permissions: Option, tty: bool, }, Execve { source: GuardianCommandSource, program: String, additional_permissions: Option, }, ApplyPatch {}, NetworkAccess { protocol: NetworkApprovalProtocol, port: u16, }, McpToolCall { server: String, tool_name: String, connector_id: Option, connector_name: Option, tool_title: Option, }, } #[derive(Clone, Serialize)] pub struct GuardianReviewEventParams { pub thread_id: String, pub turn_id: String, pub review_id: String, pub target_item_id: Option, 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, pub risk_level: Option, pub user_authorization: Option, pub outcome: Option, pub guardian_thread_id: Option, pub guardian_session_kind: Option, pub guardian_model: Option, pub guardian_reasoning_effort: Option, pub had_prior_review_context: Option, pub review_timeout_ms: u64, pub tool_call_count: Option, pub time_to_first_token_ms: Option, pub completion_latency_ms: Option, pub started_at: u64, pub completed_at: Option, pub input_tokens: Option, pub cached_input_tokens: Option, pub output_tokens: Option, pub reasoning_output_tokens: Option, pub total_tokens: Option, } #[derive(Serialize)] pub(crate) struct GuardianReviewEventPayload { pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, #[serde(flatten)] pub(crate) guardian_review: GuardianReviewEventParams, } #[derive(Serialize)] pub(crate) struct CodexAppMetadata { pub(crate) connector_id: Option, pub(crate) thread_id: Option, pub(crate) turn_id: Option, pub(crate) app_name: Option, pub(crate) product_client_id: Option, pub(crate) invoke_type: Option, pub(crate) model_slug: Option, } #[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, pub(crate) turn_id: Option, pub(crate) model_slug: Option, pub(crate) hook_name: Option, pub(crate) hook_source: Option<&'static str>, pub(crate) status: Option, } #[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) turn_id: String, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, pub(crate) thread_source: Option<&'static str>, pub(crate) subagent_source: Option, pub(crate) parent_thread_id: Option, 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) error: Option, pub(crate) active_context_tokens_before: i64, pub(crate) active_context_tokens_after: i64, pub(crate) started_at: u64, pub(crate) completed_at: u64, pub(crate) duration_ms: Option, } #[derive(Serialize)] pub(crate) struct CodexCompactionEventRequest { pub(crate) event_type: &'static str, pub(crate) event_params: CodexCompactionEventParams, } #[derive(Serialize)] pub(crate) struct CodexTurnEventParams { pub(crate) thread_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, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, pub(crate) ephemeral: bool, pub(crate) thread_source: Option, pub(crate) initialization_mode: ThreadInitializationMode, pub(crate) subagent_source: Option, pub(crate) parent_thread_id: Option, pub(crate) model: Option, pub(crate) model_provider: String, pub(crate) sandbox_policy: Option<&'static str>, pub(crate) reasoning_effort: Option, pub(crate) reasoning_summary: Option, 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, pub(crate) num_input_images: usize, pub(crate) is_first_turn: bool, pub(crate) status: Option, pub(crate) turn_error: Option, pub(crate) steer_count: Option, // TODO(rhan-oai): Populate these once tool-call accounting is emitted from // core; the schema is reserved but these fields are currently always None. pub(crate) total_tool_call_count: Option, pub(crate) shell_command_count: Option, pub(crate) file_change_count: Option, pub(crate) mcp_tool_call_count: Option, pub(crate) dynamic_tool_call_count: Option, pub(crate) subagent_tool_call_count: Option, pub(crate) web_search_count: Option, pub(crate) image_generation_count: Option, pub(crate) input_tokens: Option, pub(crate) cached_input_tokens: Option, pub(crate) output_tokens: Option, pub(crate) reasoning_output_tokens: Option, pub(crate) total_tokens: Option, pub(crate) duration_ms: Option, pub(crate) started_at: Option, pub(crate) completed_at: Option, } #[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) expected_turn_id: Option, pub(crate) accepted_turn_id: Option, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, pub(crate) thread_source: Option, 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, 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, pub(crate) plugin_name: Option, pub(crate) marketplace_name: Option, pub(crate) has_skills: Option, pub(crate) mcp_server_count: Option, pub(crate) connector_ids: Option>, pub(crate) product_client_id: Option, } #[derive(Serialize)] pub(crate) struct CodexPluginUsedMetadata { #[serde(flatten)] pub(crate) plugin: CodexPluginMetadata, pub(crate) thread_id: Option, pub(crate) turn_id: Option, pub(crate) model_slug: Option, } #[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 capability_summary = plugin.capability_summary; CodexPluginMetadata { plugin_id: Some(plugin.plugin_id.as_key()), plugin_name: Some(plugin.plugin_id.plugin_name), marketplace_name: Some(plugin.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, app_server_client: CodexAppServerClientMetadata, runtime: CodexRuntimeMetadata, thread_source: Option<&'static str>, subagent_source: Option, parent_thread_id: Option, ) -> CodexCompactionEventParams { CodexCompactionEventParams { thread_id: input.thread_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, error: input.error, active_context_tokens_before: input.active_context_tokens_before, active_context_tokens_after: input.active_context_tokens_after, started_at: input.started_at, completed_at: input.completed_at, duration_ms: input.duration_ms, } } pub(crate) fn codex_plugin_used_metadata( tracking: &TrackEventsContext, plugin: PluginTelemetryMetadata, ) -> CodexPluginUsedMetadata { CodexPluginUsedMetadata { plugin: codex_plugin_metadata(plugin), 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::SessionStart => "SessionStart", HookEventName::UserPromptSubmit => "UserPromptSubmit", 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::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, 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("subagent"), initialization_mode: ThreadInitializationMode::New, subagent_source: Some(subagent_source_name(&input.subagent_source)), parent_thread_id: input .parent_thread_id .or_else(|| subagent_parent_thread_id(&input.subagent_source)), created_at: input.created_at, }; ThreadInitializedEvent { event_type: "codex_thread_initialized", event_params, } } pub(crate) 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.clone(), } } pub(crate) fn subagent_parent_thread_id(subagent_source: &SubAgentSource) -> Option { match subagent_source { SubAgentSource::ThreadSpawn { parent_thread_id, .. } => Some(parent_thread_id.to_string()), _ => None, } } fn analytics_hook_status(status: HookRunStatus) -> HookRunStatus { match status { // Running is unexpected here and normalized defensively. HookRunStatus::Running => HookRunStatus::Failed, other => other, } }