use crate::events::AppServerRpcTransport; use crate::events::CodexRuntimeMetadata; use crate::events::GuardianReviewEventParams; use codex_app_server_protocol::ClientRequest; use codex_app_server_protocol::ClientResponsePayload; use codex_app_server_protocol::InitializeParams; use codex_app_server_protocol::JSONRPCErrorError; use codex_app_server_protocol::RequestId; use codex_app_server_protocol::ServerNotification; use codex_app_server_protocol::ServerRequest; use codex_app_server_protocol::ServerResponse; use codex_plugin::PluginTelemetryMetadata; use codex_protocol::config_types::ApprovalsReviewer; use codex_protocol::config_types::ModeKind; use codex_protocol::config_types::Personality; use codex_protocol::config_types::ReasoningSummary; use codex_protocol::config_types::ServiceTier; use codex_protocol::error::CodexErr; use codex_protocol::models::PermissionProfile; use codex_protocol::openai_models::ReasoningEffort; use codex_protocol::protocol::AskForApproval; use codex_protocol::protocol::HookEventName; use codex_protocol::protocol::HookRunStatus; use codex_protocol::protocol::HookSource; use codex_protocol::protocol::SessionSource; use codex_protocol::protocol::SkillScope; use codex_protocol::protocol::SubAgentSource; use codex_protocol::protocol::TokenUsage; use codex_protocol::request_permissions::RequestPermissionsResponse; use serde::Serialize; use std::path::PathBuf; #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct AcceptedLineFingerprint { pub path_hash: String, pub line_hash: String, } #[derive(Clone)] pub struct TrackEventsContext { pub model_slug: String, pub thread_id: String, pub turn_id: String, } pub fn build_track_events_context( model_slug: String, thread_id: String, turn_id: String, ) -> TrackEventsContext { TrackEventsContext { model_slug, thread_id, turn_id, } } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum TurnSubmissionType { Default, Queued, } #[derive(Clone)] pub struct TurnResolvedConfigFact { pub turn_id: String, pub thread_id: String, pub num_input_images: usize, pub submission_type: Option, pub ephemeral: bool, pub session_source: SessionSource, pub model: String, pub model_provider: String, pub permission_profile: PermissionProfile, pub permission_profile_cwd: PathBuf, pub reasoning_effort: Option, pub reasoning_summary: Option, pub service_tier: Option, pub approval_policy: AskForApproval, pub approvals_reviewer: ApprovalsReviewer, pub sandbox_network_access: bool, pub collaboration_mode: ModeKind, pub personality: Option, pub workspace_kind: Option, pub is_first_turn: bool, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum ThreadInitializationMode { New, Forked, Resumed, } #[derive(Clone)] pub struct TurnTokenUsageFact { pub turn_id: String, pub thread_id: String, pub token_usage: TokenUsage, } #[derive(Clone, Debug, PartialEq, Eq, Serialize)] pub struct TurnProfile { pub before_first_sampling_ms: u64, pub sampling_ms: u64, pub between_sampling_overhead_ms: u64, pub tool_blocking_ms: u64, pub after_last_sampling_ms: u64, pub sampling_request_count: u32, pub sampling_retry_count: u32, } #[derive(Clone)] pub struct TurnProfileFact { pub turn_id: String, pub profile: TurnProfile, } #[derive(Clone)] pub struct TurnCodexErrorFact { pub(crate) turn_id: String, pub(crate) thread_id: String, pub(crate) error: TurnCodexError, } impl TurnCodexErrorFact { pub fn from_codex_err(thread_id: String, turn_id: String, error: &CodexErr) -> Self { Self { turn_id, thread_id, error: TurnCodexError::from_codex_err(error), } } } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CodexErrKind { TurnAborted, Stream, ContextWindowExceeded, ThreadNotFound, AgentLimitReached, SessionConfiguredNotFirstEvent, Timeout, RequestTimeout, Spawn, Interrupted, UnexpectedStatus, InvalidRequest, InvalidImageRequest, UsageLimitReached, ServerOverloaded, CyberPolicy, ResponseStreamFailed, ConnectionFailed, QuotaExceeded, UsageNotIncluded, InternalServerError, RetryLimit, InternalAgentDied, Sandbox, LandlockSandboxExecutableNotProvided, UnsupportedOperation, RefreshTokenFailed, Fatal, Io, Json, #[cfg(target_os = "linux")] LandlockRuleset, #[cfg(target_os = "linux")] LandlockPathFd, TokioJoin, EnvVar, } #[derive(Clone)] pub(crate) struct TurnCodexError { pub(crate) kind: CodexErrKind, pub(crate) http_status_code: Option, } impl TurnCodexError { fn from_codex_err(error: &CodexErr) -> Self { Self { kind: error.into(), http_status_code: error.http_status_code_value(), } } } impl From<&CodexErr> for CodexErrKind { fn from(error: &CodexErr) -> Self { match error { CodexErr::TurnAborted => CodexErrKind::TurnAborted, CodexErr::Stream(..) => CodexErrKind::Stream, CodexErr::ContextWindowExceeded => CodexErrKind::ContextWindowExceeded, CodexErr::ThreadNotFound(_) => CodexErrKind::ThreadNotFound, CodexErr::AgentLimitReached { .. } => CodexErrKind::AgentLimitReached, CodexErr::SessionConfiguredNotFirstEvent => { CodexErrKind::SessionConfiguredNotFirstEvent } CodexErr::Timeout => CodexErrKind::Timeout, CodexErr::RequestTimeout => CodexErrKind::RequestTimeout, CodexErr::Spawn => CodexErrKind::Spawn, CodexErr::Interrupted => CodexErrKind::Interrupted, CodexErr::UnexpectedStatus(_) => CodexErrKind::UnexpectedStatus, CodexErr::InvalidRequest(_) => CodexErrKind::InvalidRequest, CodexErr::InvalidImageRequest() => CodexErrKind::InvalidImageRequest, CodexErr::UsageLimitReached(_) => CodexErrKind::UsageLimitReached, CodexErr::ServerOverloaded => CodexErrKind::ServerOverloaded, CodexErr::CyberPolicy { .. } => CodexErrKind::CyberPolicy, CodexErr::ResponseStreamFailed(_) => CodexErrKind::ResponseStreamFailed, CodexErr::ConnectionFailed(_) => CodexErrKind::ConnectionFailed, CodexErr::QuotaExceeded => CodexErrKind::QuotaExceeded, CodexErr::UsageNotIncluded => CodexErrKind::UsageNotIncluded, CodexErr::InternalServerError => CodexErrKind::InternalServerError, CodexErr::RetryLimit(_) => CodexErrKind::RetryLimit, CodexErr::InternalAgentDied => CodexErrKind::InternalAgentDied, CodexErr::Sandbox(_) => CodexErrKind::Sandbox, CodexErr::LandlockSandboxExecutableNotProvided => { CodexErrKind::LandlockSandboxExecutableNotProvided } CodexErr::UnsupportedOperation(_) => CodexErrKind::UnsupportedOperation, CodexErr::RefreshTokenFailed(_) => CodexErrKind::RefreshTokenFailed, CodexErr::Fatal(_) => CodexErrKind::Fatal, CodexErr::Io(_) => CodexErrKind::Io, CodexErr::Json(_) => CodexErrKind::Json, #[cfg(target_os = "linux")] CodexErr::LandlockRuleset(_) => CodexErrKind::LandlockRuleset, #[cfg(target_os = "linux")] CodexErr::LandlockPathFd(_) => CodexErrKind::LandlockPathFd, CodexErr::TokioJoin(_) => CodexErrKind::TokioJoin, CodexErr::EnvVar(_) => CodexErrKind::EnvVar, } } } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum TurnStatus { Completed, Failed, Interrupted, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum TurnSteerResult { Accepted, Rejected, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum TurnSteerRejectionReason { NoActiveTurn, ExpectedTurnMismatch, NonSteerableReview, NonSteerableCompact, EmptyInput, InputTooLarge, } #[derive(Clone)] pub struct CodexTurnSteerEvent { pub expected_turn_id: Option, pub accepted_turn_id: Option, pub num_input_images: usize, pub result: TurnSteerResult, pub rejection_reason: Option, pub created_at: u64, } #[derive(Clone, Copy, Debug)] pub enum AnalyticsJsonRpcError { TurnSteer(TurnSteerRequestError), Input(InputError), } #[derive(Clone, Copy, Debug)] pub enum TurnSteerRequestError { NoActiveTurn, ExpectedTurnMismatch, NonSteerableReview, NonSteerableCompact, } #[derive(Clone, Copy, Debug)] pub enum InputError { Empty, TooLarge, } impl From for TurnSteerRejectionReason { fn from(error: TurnSteerRequestError) -> Self { match error { TurnSteerRequestError::NoActiveTurn => Self::NoActiveTurn, TurnSteerRequestError::ExpectedTurnMismatch => Self::ExpectedTurnMismatch, TurnSteerRequestError::NonSteerableReview => Self::NonSteerableReview, TurnSteerRequestError::NonSteerableCompact => Self::NonSteerableCompact, } } } impl From for TurnSteerRejectionReason { fn from(error: InputError) -> Self { match error { InputError::Empty => Self::EmptyInput, InputError::TooLarge => Self::InputTooLarge, } } } #[derive(Clone, Debug)] pub struct SkillInvocation { pub skill_name: String, pub skill_scope: SkillScope, pub skill_path: PathBuf, pub plugin_id: Option, pub invocation_type: InvocationType, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "lowercase")] pub enum InvocationType { Explicit, Implicit, } pub struct AppInvocation { pub connector_id: Option, pub app_name: Option, pub invocation_type: Option, } #[derive(Clone)] pub struct SubAgentThreadStartedInput { pub session_id: String, pub thread_id: String, pub parent_thread_id: Option, pub forked_from_thread_id: Option, pub product_client_id: String, pub client_name: String, pub client_version: String, pub model: String, pub ephemeral: bool, pub subagent_source: SubAgentSource, pub created_at: u64, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CompactionTrigger { Manual, Auto, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CompactionReason { UserRequested, ContextLimit, ModelDownshift, CompHashChanged, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CompactionImplementation { Responses, ResponsesCompactionV2, ResponsesCompact, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CompactionPhase { StandaloneTurn, PreTurn, MidTurn, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CompactionStrategy { Memento, PrefixCompaction, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum CompactionStatus { Completed, Failed, Interrupted, } #[derive(Clone)] pub struct CodexCompactionEvent { pub thread_id: String, pub turn_id: String, pub trigger: CompactionTrigger, pub reason: CompactionReason, pub implementation: CompactionImplementation, pub phase: CompactionPhase, pub strategy: CompactionStrategy, pub status: CompactionStatus, pub codex_error_kind: Option, pub codex_error_http_status_code: Option, pub active_context_tokens_before: i64, pub active_context_tokens_after: i64, pub retained_image_count: Option, pub compaction_summary_tokens: Option, pub cached_input_tokens: Option, pub started_at: u64, pub completed_at: u64, pub duration_ms: Option, } #[derive(Clone, Copy, Debug, Serialize)] #[serde(rename_all = "snake_case")] pub enum GoalEventKind { Created, UsageAccounted, StatusChanged, Cleared, } #[derive(Clone)] pub struct CodexGoalEvent { pub thread_id: String, pub turn_id: Option, pub goal_id: String, pub event_kind: GoalEventKind, pub goal_status: codex_state::ThreadGoalStatus, pub has_token_budget: bool, pub cumulative_tokens_accounted: Option, pub cumulative_time_accounted_seconds: Option, } #[allow(dead_code)] pub(crate) enum AnalyticsFact { Initialize { connection_id: u64, params: InitializeParams, product_client_id: String, runtime: CodexRuntimeMetadata, rpc_transport: AppServerRpcTransport, }, ClientRequest { connection_id: u64, request_id: RequestId, request: Box, }, ClientResponse { connection_id: u64, request_id: RequestId, response: Box, }, ErrorResponse { connection_id: u64, request_id: RequestId, error: JSONRPCErrorError, error_type: Option, }, ServerRequest { connection_id: u64, request: Box, }, ServerResponse { completed_at_ms: u64, response: Box, }, EffectivePermissionsApprovalResponse { completed_at_ms: u64, request_id: RequestId, response: Box, }, ServerRequestAborted { completed_at_ms: u64, request_id: RequestId, }, Notification(Box), // Facts that do not naturally exist on the app-server protocol surface, or // would require non-trivial protocol reshaping on this branch. Custom(CustomAnalyticsFact), } pub(crate) enum CustomAnalyticsFact { SubAgentThreadStarted(SubAgentThreadStartedInput), Compaction(Box), Goal(Box), GuardianReview(Box), TurnResolvedConfig(Box), TurnTokenUsage(Box), TurnProfile(Box), TurnCodexError(Box), SkillInvoked(SkillInvokedInput), AppMentioned(AppMentionedInput), AppUsed(AppUsedInput), HookRun(HookRunInput), PluginUsed(PluginUsedInput), PluginStateChanged(PluginStateChangedInput), PluginInstallFailed(PluginInstallFailedInput), ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedInput), ExternalAgentConfigImportFailure(ExternalAgentConfigImportFailureInput), } pub(crate) struct SkillInvokedInput { pub tracking: TrackEventsContext, pub invocations: Vec, } pub(crate) struct AppMentionedInput { pub tracking: TrackEventsContext, pub mentions: Vec, } pub(crate) struct AppUsedInput { pub tracking: TrackEventsContext, pub app: AppInvocation, } pub(crate) struct HookRunInput { pub tracking: TrackEventsContext, pub hook: HookRunFact, } pub struct HookRunFact { pub event_name: HookEventName, pub hook_source: HookSource, pub status: HookRunStatus, } pub(crate) struct PluginUsedInput { pub tracking: TrackEventsContext, pub plugin: PluginTelemetryMetadata, } pub(crate) struct PluginStateChangedInput { pub plugin: PluginTelemetryMetadata, pub state: PluginState, } pub(crate) struct PluginInstallFailedInput { pub plugin: PluginTelemetryMetadata, pub error_type: String, } pub struct ExternalAgentConfigImportCompletedInput { pub import_id: String, pub source: String, pub item_type: String, pub success_count: usize, pub failed_count: usize, } pub struct ExternalAgentConfigImportFailureInput { pub import_id: String, pub source: String, pub item_type: String, pub failure_stage: String, pub error_type: String, } #[derive(Clone, Copy)] pub(crate) enum PluginState { Installed, Uninstalled, Enabled, Disabled, }