Files
codex/codex-rs/core/src/guardian/analytics.rs
T

223 lines
8.7 KiB
Rust

use std::time::Instant;
use codex_analytics::GuardianApprovalRequestSource;
use codex_analytics::GuardianReviewDecision;
use codex_analytics::GuardianReviewFailureReason;
use codex_analytics::GuardianReviewSessionKind;
use codex_analytics::GuardianReviewTerminalStatus;
use codex_analytics::GuardianReviewedAction;
use codex_analytics::now_unix_seconds;
use codex_features::Feature;
use codex_protocol::protocol::GuardianRiskLevel;
use codex_protocol::protocol::GuardianUserAuthorization;
use codex_protocol::protocol::TokenUsage;
use crate::session::session::Session;
use crate::session::turn_context::TurnContext;
use super::GUARDIAN_REVIEW_TIMEOUT;
use super::GuardianApprovalRequest;
use super::GuardianAssessmentOutcome;
use super::review_session::GuardianReviewSessionMetadata;
pub(super) struct GuardianReviewAnalyticsContext {
thread_id: String,
turn_id: String,
review_id: String,
target_item_id: Option<String>,
approval_request_source: GuardianApprovalRequestSource,
reviewed_action: GuardianReviewedAction,
started_at: u64,
started_instant: Instant,
}
pub(super) struct GuardianReviewAnalyticsResult {
pub(super) decision: GuardianReviewDecision,
pub(super) terminal_status: GuardianReviewTerminalStatus,
pub(super) failure_reason: Option<GuardianReviewFailureReason>,
pub(super) risk_level: Option<GuardianRiskLevel>,
pub(super) user_authorization: Option<GuardianUserAuthorization>,
pub(super) outcome: Option<GuardianAssessmentOutcome>,
pub(super) guardian_thread_id: Option<String>,
pub(super) guardian_session_kind: Option<GuardianReviewSessionKind>,
pub(super) guardian_model: Option<String>,
pub(super) guardian_reasoning_effort: Option<String>,
pub(super) had_prior_review_context: Option<bool>,
pub(super) reviewed_action_truncated: bool,
pub(super) token_usage: Option<TokenUsage>,
pub(super) time_to_first_token_ms: Option<u64>,
pub(super) completed_at: u64,
}
impl GuardianReviewAnalyticsContext {
pub(super) fn new(
session: &Session,
turn_id: String,
review_id: String,
target_item_id: Option<String>,
approval_request_source: GuardianApprovalRequestSource,
request: &GuardianApprovalRequest,
) -> Self {
Self {
thread_id: session.conversation_id.to_string(),
turn_id,
review_id,
target_item_id,
approval_request_source,
reviewed_action: guardian_reviewed_action(request),
started_at: now_unix_seconds(),
started_instant: Instant::now(),
}
}
pub(super) fn track(
&self,
session: &Session,
turn: &TurnContext,
result: GuardianReviewAnalyticsResult,
) {
if !turn.config.features.enabled(Feature::GeneralAnalytics) {
return;
}
let completion_latency_ms = self.started_instant.elapsed().as_millis() as u64;
session
.services
.analytics_events_client
.track_guardian_review(codex_analytics::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,
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,
had_prior_review_context: result.had_prior_review_context,
review_timeout_ms: GUARDIAN_REVIEW_TIMEOUT.as_millis() as u64,
// 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(completion_latency_ms),
started_at: self.started_at,
completed_at: Some(result.completed_at),
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),
});
}
}
impl GuardianReviewAnalyticsResult {
pub(super) fn without_session_metadata(completed_at: u64) -> Self {
Self {
decision: GuardianReviewDecision::Denied,
terminal_status: GuardianReviewTerminalStatus::FailedClosed,
failure_reason: None,
risk_level: None,
user_authorization: None,
outcome: None,
guardian_thread_id: None,
guardian_session_kind: None,
guardian_model: None,
guardian_reasoning_effort: None,
had_prior_review_context: None,
reviewed_action_truncated: false,
token_usage: None,
time_to_first_token_ms: None,
completed_at,
}
}
pub(super) fn from_session_metadata(metadata: GuardianReviewSessionMetadata) -> Self {
Self {
decision: GuardianReviewDecision::Denied,
terminal_status: GuardianReviewTerminalStatus::FailedClosed,
failure_reason: None,
risk_level: None,
user_authorization: None,
outcome: None,
guardian_thread_id: Some(metadata.guardian_thread_id),
guardian_session_kind: Some(metadata.guardian_session_kind),
guardian_model: Some(metadata.guardian_model),
guardian_reasoning_effort: metadata.guardian_reasoning_effort,
had_prior_review_context: Some(metadata.had_prior_review_context),
reviewed_action_truncated: metadata.reviewed_action_truncated,
token_usage: metadata.token_usage,
time_to_first_token_ms: metadata.time_to_first_token_ms,
completed_at: metadata.completed_at,
}
}
}
fn guardian_reviewed_action(request: &GuardianApprovalRequest) -> GuardianReviewedAction {
match request {
GuardianApprovalRequest::Shell {
sandbox_permissions,
additional_permissions,
..
} => GuardianReviewedAction::Shell {
sandbox_permissions: *sandbox_permissions,
additional_permissions: additional_permissions.clone(),
},
GuardianApprovalRequest::ExecCommand {
sandbox_permissions,
additional_permissions,
tty,
..
} => GuardianReviewedAction::UnifiedExec {
sandbox_permissions: *sandbox_permissions,
additional_permissions: additional_permissions.clone(),
tty: *tty,
},
#[cfg(unix)]
GuardianApprovalRequest::Execve {
source,
program,
additional_permissions,
..
} => GuardianReviewedAction::Execve {
source: *source,
program: program.clone(),
additional_permissions: additional_permissions.clone(),
},
GuardianApprovalRequest::ApplyPatch { .. } => GuardianReviewedAction::ApplyPatch {},
GuardianApprovalRequest::NetworkAccess { protocol, port, .. } => {
GuardianReviewedAction::NetworkAccess {
protocol: *protocol,
port: *port,
}
}
GuardianApprovalRequest::McpToolCall {
server,
tool_name,
connector_id,
connector_name,
tool_title,
..
} => GuardianReviewedAction::McpToolCall {
server: server.clone(),
tool_name: tool_name.clone(),
connector_id: connector_id.clone(),
connector_name: connector_name.clone(),
tool_title: tool_title.clone(),
},
}
}