Codex Handbook
analytics/src/facts.rs 551 lines
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<TurnSubmissionType>,    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<ReasoningEffort>,    pub reasoning_summary: Option<ReasoningSummary>,    pub service_tier: Option<ServiceTier>,    pub approval_policy: AskForApproval,    pub approvals_reviewer: ApprovalsReviewer,    pub sandbox_network_access: bool,    pub collaboration_mode: ModeKind,    pub personality: Option<Personality>,    pub workspace_kind: Option<String>,    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<u16>,}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<String>,    pub accepted_turn_id: Option<String>,    pub num_input_images: usize,    pub result: TurnSteerResult,    pub rejection_reason: Option<TurnSteerRejectionReason>,    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<TurnSteerRequestError> 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<InputError> 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<String>,    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<String>,    pub app_name: Option<String>,    pub invocation_type: Option<InvocationType>,}#[derive(Clone)]pub struct SubAgentThreadStartedInput {    pub session_id: String,    pub thread_id: String,    pub parent_thread_id: Option<String>,    pub forked_from_thread_id: Option<String>,    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<CodexErrKind>,    pub codex_error_http_status_code: Option<u16>,    pub active_context_tokens_before: i64,    pub active_context_tokens_after: i64,    pub retained_image_count: Option<usize>,    pub compaction_summary_tokens: Option<i64>,    pub cached_input_tokens: Option<i64>,    pub started_at: u64,    pub completed_at: u64,    pub duration_ms: Option<u64>,}#[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<String>,    pub goal_id: String,    pub event_kind: GoalEventKind,    pub goal_status: codex_state::ThreadGoalStatus,    pub has_token_budget: bool,    pub cumulative_tokens_accounted: Option<i64>,    pub cumulative_time_accounted_seconds: Option<i64>,}#[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<ClientRequest>,    },    ClientResponse {        connection_id: u64,        request_id: RequestId,        response: Box<ClientResponsePayload>,    },    ErrorResponse {        connection_id: u64,        request_id: RequestId,        error: JSONRPCErrorError,        error_type: Option<AnalyticsJsonRpcError>,    },    ServerRequest {        connection_id: u64,        request: Box<ServerRequest>,    },    ServerResponse {        completed_at_ms: u64,        response: Box<ServerResponse>,    },    EffectivePermissionsApprovalResponse {        completed_at_ms: u64,        request_id: RequestId,        response: Box<RequestPermissionsResponse>,    },    ServerRequestAborted {        completed_at_ms: u64,        request_id: RequestId,    },    Notification(Box<ServerNotification>),    // 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<CodexCompactionEvent>),    Goal(Box<CodexGoalEvent>),    GuardianReview(Box<GuardianReviewEventParams>),    TurnResolvedConfig(Box<TurnResolvedConfigFact>),    TurnTokenUsage(Box<TurnTokenUsageFact>),    TurnProfile(Box<TurnProfileFact>),    TurnCodexError(Box<TurnCodexErrorFact>),    SkillInvoked(SkillInvokedInput),    AppMentioned(AppMentionedInput),    AppUsed(AppUsedInput),    HookRun(HookRunInput),    PluginUsed(PluginUsedInput),    PluginStateChanged(PluginStateChangedInput),}pub(crate) struct SkillInvokedInput {    pub tracking: TrackEventsContext,    pub invocations: Vec<SkillInvocation>,}pub(crate) struct AppMentionedInput {    pub tracking: TrackEventsContext,    pub mentions: Vec<AppInvocation>,}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,}#[derive(Clone, Copy)]pub(crate) enum PluginState {    Installed,    Uninstalled,    Enabled,    Disabled,}