[codex-analytics] add compaction analytics event

This commit is contained in:
rhan-oai
2026-04-08 13:10:12 -07:00
committed by Roy Han
parent 598d6ff056
commit d9757c306e
9 changed files with 565 additions and 10 deletions

View File

@@ -3,6 +3,7 @@ use crate::events::AppServerRpcTransport;
use crate::events::CodexAppMentionedEventRequest;
use crate::events::CodexAppServerClientMetadata;
use crate::events::CodexAppUsedEventRequest;
use crate::events::CodexCompactionEventRequest;
use crate::events::CodexPluginEventRequest;
use crate::events::CodexPluginUsedEventRequest;
use crate::events::CodexRuntimeMetadata;
@@ -18,6 +19,10 @@ use crate::facts::AnalyticsFact;
use crate::facts::AppInvocation;
use crate::facts::AppMentionedInput;
use crate::facts::AppUsedInput;
use crate::facts::CodexCompactionEvent;
use crate::facts::CompactionMode;
use crate::facts::CompactionStatus;
use crate::facts::CompactionTrigger;
use crate::facts::CustomAnalyticsFact;
use crate::facts::InvocationType;
use crate::facts::PluginState;
@@ -254,6 +259,86 @@ fn app_used_event_serializes_expected_shape() {
);
}
#[test]
fn compaction_event_serializes_expected_shape() {
let event = TrackEventRequest::Compaction(Box::new(CodexCompactionEventRequest {
event_type: "codex_compaction_event",
event_params: crate::events::codex_compaction_event_params(
CodexAppServerClientMetadata {
product_client_id: "CODEX_CLI".to_string(),
client_name: Some("codex-tui".to_string()),
client_version: Some("1.2.3".to_string()),
rpc_transport: AppServerRpcTransport::InProcess,
experimental_api_enabled: Some(true),
},
CodexRuntimeMetadata {
codex_rs_version: "0.1.0".to_string(),
runtime_os: "macos".to_string(),
runtime_os_version: "15.3.1".to_string(),
runtime_arch: "aarch64".to_string(),
},
CodexCompactionEvent {
thread_id: "thread-1".to_string(),
turn_id: "turn-1".to_string(),
trigger: CompactionTrigger::AutoMidTurn,
mode: CompactionMode::Remote,
status: CompactionStatus::Completed,
error: None,
started_at: 100,
completed_at: 106,
duration_ms: Some(6543),
input_tokens_before: Some(1000),
input_tokens_after: Some(600),
estimated_tokens_before: Some(1500),
estimated_tokens_after: Some(900),
history_items_before: 50,
history_items_after: 12,
deleted_items_before_remote_compact: Some(3),
},
),
}));
let payload = serde_json::to_value(&event).expect("serialize compaction event");
assert_eq!(
payload,
json!({
"event_type": "codex_compaction_event",
"event_params": {
"thread_id": "thread-1",
"turn_id": "turn-1",
"app_server_client": {
"product_client_id": "CODEX_CLI",
"client_name": "codex-tui",
"client_version": "1.2.3",
"rpc_transport": "in_process",
"experimental_api_enabled": true
},
"runtime": {
"codex_rs_version": "0.1.0",
"runtime_os": "macos",
"runtime_os_version": "15.3.1",
"runtime_arch": "aarch64"
},
"trigger": "auto_mid_turn",
"mode": "remote",
"status": "completed",
"error": null,
"started_at": 100,
"completed_at": 106,
"duration_ms": 6543,
"input_tokens_before": 1000,
"input_tokens_after": 600,
"estimated_tokens_before": 1500,
"estimated_tokens_after": 900,
"history_items_before": 50,
"history_items_after": 12,
"deleted_items_before_remote_compact": 3
}
})
);
}
#[test]
fn app_used_dedupe_is_keyed_by_turn_and_connector() {
let (sender, _receiver) = mpsc::channel(1);
@@ -449,6 +534,99 @@ async fn initialize_caches_client_and_thread_lifecycle_publishes_once_initialize
assert_eq!(payload[0]["event_params"]["parent_thread_id"], json!(null));
}
#[tokio::test]
async fn compaction_event_waits_for_thread_connection_metadata() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::Compaction(Box::new(
CodexCompactionEvent {
thread_id: "thread-1".to_string(),
turn_id: "turn-compact".to_string(),
trigger: CompactionTrigger::Manual,
mode: CompactionMode::Local,
status: CompactionStatus::Failed,
error: Some("context limit exceeded".to_string()),
started_at: 100,
completed_at: 101,
duration_ms: Some(1200),
input_tokens_before: Some(2000),
input_tokens_after: Some(2000),
estimated_tokens_before: Some(2500),
estimated_tokens_after: Some(2500),
history_items_before: 20,
history_items_after: 20,
deleted_items_before_remote_compact: None,
},
))),
&mut events,
)
.await;
assert!(
events.is_empty(),
"compaction events should wait for client/runtime metadata"
);
reducer
.ingest(
AnalyticsFact::Initialize {
connection_id: 7,
params: InitializeParams {
client_info: ClientInfo {
name: "codex-tui".to_string(),
title: None,
version: "1.0.0".to_string(),
},
capabilities: Some(InitializeCapabilities {
experimental_api: false,
opt_out_notification_methods: None,
}),
},
product_client_id: DEFAULT_ORIGINATOR.to_string(),
runtime: CodexRuntimeMetadata {
codex_rs_version: "0.99.0".to_string(),
runtime_os: "linux".to_string(),
runtime_os_version: "24.04".to_string(),
runtime_arch: "x86_64".to_string(),
},
rpc_transport: AppServerRpcTransport::Websocket,
},
&mut events,
)
.await;
reducer
.ingest(
AnalyticsFact::Response {
connection_id: 7,
response: Box::new(sample_thread_start_response(
"thread-1", /*ephemeral*/ false, "gpt-5",
)),
},
&mut events,
)
.await;
let payload = serde_json::to_value(&events).expect("serialize events");
assert_eq!(payload.as_array().expect("events array").len(), 2);
assert_eq!(payload[0]["event_type"], "codex_thread_initialized");
assert_eq!(payload[1]["event_type"], "codex_compaction_event");
assert_eq!(payload[1]["event_params"]["thread_id"], "thread-1");
assert_eq!(payload[1]["event_params"]["turn_id"], "turn-compact");
assert_eq!(payload[1]["event_params"]["trigger"], "manual");
assert_eq!(payload[1]["event_params"]["mode"], "local");
assert_eq!(payload[1]["event_params"]["status"], "failed");
assert_eq!(
payload[1]["event_params"]["app_server_client"]["product_client_id"],
DEFAULT_ORIGINATOR
);
assert_eq!(
payload[1]["event_params"]["runtime"]["codex_rs_version"],
"0.99.0"
);
}
#[test]
fn subagent_thread_started_review_serializes_expected_shape() {
let event = TrackEventRequest::ThreadInitialized(subagent_thread_started_event_request(