From 3444b0d60ac8e8fac194945cf085c6b05983faf4 Mon Sep 17 00:00:00 2001 From: rhan-oai Date: Thu, 7 May 2026 09:46:46 -0700 Subject: [PATCH] [codex-analytics] add tool review event schema (#18747) ## Why We want to emit terminal review analytics for tool-related approval flows, but the event contract needs to exist before the reducer can publish anything. This PR is the schema-only slice for the Codex review event family. ## What changed - add the `ReviewEvent` analytics envelope in `codex-rs/analytics/src/events.rs` - define the review subject kind, reviewer, trigger, terminal status, and post-review resolution enums - define the review event payload with thread, turn, item, lineage, tool, and timing fields that the emitter stack will populate ## Verification - stacked verification in dependent PRs: `cargo test -p codex-analytics analytics_client_tests --manifest-path codex-rs/Cargo.toml` --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/18747). * #18748 * #21434 * __->__ #18747 * #17090 * #17089 * #20514 --- .../analytics/src/analytics_client_tests.rs | 2 +- codex-rs/analytics/src/events.rs | 81 ++++++++++++++++++- codex-rs/analytics/src/reducer.rs | 2 +- 3 files changed, 82 insertions(+), 3 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index fd294385e..880adfc25 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -1012,7 +1012,7 @@ fn command_execution_event_serializes_expected_shape() { runtime_os_version: "15.3.1".to_string(), runtime_arch: "aarch64".to_string(), }, - thread_source: Some("user"), + thread_source: Some(ThreadSource::User), subagent_source: None, parent_thread_id: None, tool_name: "shell".to_string(), diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 2bcd43f38..23afd83b7 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -70,6 +70,8 @@ pub(crate) enum TrackEventRequest { CollabAgentToolCall(CodexCollabAgentToolCallEventRequest), WebSearch(CodexWebSearchEventRequest), ImageGeneration(CodexImageGenerationEventRequest), + #[allow(dead_code)] + ReviewEvent(CodexReviewEventRequest), PluginUsed(CodexPluginUsedEventRequest), PluginInstalled(CodexPluginEventRequest), PluginUninstalled(CodexPluginEventRequest), @@ -442,7 +444,7 @@ pub(crate) struct CodexToolItemEventBase { pub(crate) item_id: String, pub(crate) app_server_client: CodexAppServerClientMetadata, pub(crate) runtime: CodexRuntimeMetadata, - pub(crate) thread_source: Option<&'static str>, + pub(crate) thread_source: Option, pub(crate) subagent_source: Option, pub(crate) parent_thread_id: Option, pub(crate) tool_name: String, @@ -462,6 +464,83 @@ pub(crate) struct CodexToolItemEventBase { 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, + pub(crate) review_id: String, + 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) tool_kind: ReviewSubjectKind, + pub(crate) tool_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, +} + +#[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 { diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index 81530444d..d35fb9660 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -1447,7 +1447,7 @@ fn tool_item_base( item_id, app_server_client: context.connection_state.app_server_client.clone(), runtime: context.connection_state.runtime.clone(), - thread_source: thread_metadata.thread_source.map(ThreadSource::as_str), + thread_source: thread_metadata.thread_source, subagent_source: thread_metadata.subagent_source.clone(), parent_thread_id: thread_metadata.parent_thread_id.clone(), tool_name,