mirror of
https://github.com/openai/codex.git
synced 2026-11-10 21:00:47 +00:00
## What changed Include `turn_trigger` from turn metadata in tool, artifact, skill, app, plugin usage, and hook analytics events. Omit the field when no trigger is available. ## Testing Extend app serialization and reducer tests to check trigger propagation, and verify that MCP tool events retain the trigger after turn completion. GitOrigin-RevId: 975780983d0761eb34fecf5e8b4abe98a432d0c3
182 lines
5.9 KiB
Rust
182 lines
5.9 KiB
Rust
//! App mention and usage events.
|
|
|
|
use crate::events::CodexAppMentionedEventRequest;
|
|
use crate::events::CodexAppUsedEventRequest;
|
|
use crate::events::CodexAppUsedMetadata;
|
|
use crate::events::TrackEventRequest;
|
|
use crate::events::codex_app_metadata;
|
|
use crate::facts::AnalyticsFact;
|
|
use crate::facts::AppInvocation;
|
|
use crate::facts::AppMentionedInput;
|
|
use crate::facts::AppUsedInput;
|
|
use crate::facts::CustomAnalyticsFact;
|
|
use crate::facts::ElicitationType;
|
|
use crate::facts::InvocationType;
|
|
use crate::reducer::AnalyticsReducer;
|
|
use crate::tests::support::TEST_PRODUCT_CLIENT_ID;
|
|
use crate::tests::support::test_tracking_context;
|
|
use crate::tests::support::test_turn_metadata;
|
|
use pretty_assertions::assert_eq;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn app_mentioned_event_serializes_expected_shape() {
|
|
let tracking = test_tracking_context("thread-1", "turn-1");
|
|
let event = TrackEventRequest::AppMentioned(CodexAppMentionedEventRequest {
|
|
event_type: "codex_app_mentioned",
|
|
event_params: codex_app_metadata(
|
|
&tracking,
|
|
AppInvocation {
|
|
connector_id: Some("calendar".to_string()),
|
|
app_name: Some("Calendar".to_string()),
|
|
invocation_type: Some(InvocationType::Explicit),
|
|
},
|
|
),
|
|
});
|
|
|
|
let payload = serde_json::to_value(&event).expect("serialize app mentioned event");
|
|
|
|
assert_eq!(
|
|
payload,
|
|
json!({
|
|
"event_type": "codex_app_mentioned",
|
|
"event_params": {
|
|
"connector_id": "calendar",
|
|
"thread_id": "thread-1",
|
|
"turn_id": "turn-1",
|
|
"app_name": "Calendar",
|
|
"product_client_id": TEST_PRODUCT_CLIENT_ID,
|
|
"invoke_type": "explicit",
|
|
"model_slug": "gpt-5"
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn app_used_event_serializes_expected_shape() {
|
|
let mut tracking = test_tracking_context("thread-2", "turn-2");
|
|
let mut metadata = test_turn_metadata(/*root_turn_id*/ None);
|
|
std::sync::Arc::get_mut(&mut metadata)
|
|
.expect("unique metadata")
|
|
.turn_trigger = Some("appServer-goal".to_string());
|
|
tracking.turn_metadata = Some(metadata);
|
|
let event = TrackEventRequest::AppUsed(CodexAppUsedEventRequest {
|
|
event_type: "codex_app_used",
|
|
event_params: CodexAppUsedMetadata {
|
|
app: codex_app_metadata(
|
|
&tracking,
|
|
AppInvocation {
|
|
connector_id: Some("drive".to_string()),
|
|
app_name: Some("Google Drive".to_string()),
|
|
invocation_type: Some(InvocationType::Implicit),
|
|
},
|
|
),
|
|
voice_session_id: None,
|
|
elicitation_type: None,
|
|
},
|
|
});
|
|
|
|
let payload = serde_json::to_value(&event).expect("serialize app used event");
|
|
|
|
assert_eq!(
|
|
payload,
|
|
json!({
|
|
"event_type": "codex_app_used",
|
|
"event_params": {
|
|
"turn_trigger": "appServer-goal",
|
|
"connector_id": "drive",
|
|
"thread_id": "thread-2",
|
|
"turn_id": "turn-2",
|
|
"app_name": "Google Drive",
|
|
"product_client_id": TEST_PRODUCT_CLIENT_ID,
|
|
"invoke_type": "implicit",
|
|
"model_slug": "gpt-5",
|
|
"voice_session_id": null,
|
|
"elicitation_type": null
|
|
}
|
|
})
|
|
);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn reducer_ingests_app_facts() {
|
|
let mut reducer = AnalyticsReducer::default();
|
|
let mut events = Vec::new();
|
|
let tracking = test_tracking_context("thread-1", "turn-1");
|
|
let mut metadata = test_turn_metadata(/*root_turn_id*/ None);
|
|
std::sync::Arc::get_mut(&mut metadata)
|
|
.expect("unique metadata")
|
|
.turn_trigger = Some("automation_cron_run_now".to_string());
|
|
let tracking = crate::facts::TrackEventsContext {
|
|
turn_metadata: Some(metadata),
|
|
..tracking
|
|
};
|
|
|
|
reducer
|
|
.ingest(
|
|
AnalyticsFact::Custom(CustomAnalyticsFact::AppMentioned(AppMentionedInput {
|
|
tracking: tracking.clone(),
|
|
mentions: vec![AppInvocation {
|
|
connector_id: Some("calendar".to_string()),
|
|
app_name: Some("Calendar".to_string()),
|
|
invocation_type: Some(InvocationType::Explicit),
|
|
}],
|
|
})),
|
|
&mut events,
|
|
)
|
|
.await;
|
|
reducer
|
|
.ingest(
|
|
AnalyticsFact::Custom(CustomAnalyticsFact::AppUsed(AppUsedInput {
|
|
tracking,
|
|
app: AppInvocation {
|
|
connector_id: Some("drive".to_string()),
|
|
app_name: Some("Drive".to_string()),
|
|
invocation_type: Some(InvocationType::Implicit),
|
|
},
|
|
elicitation_type: Some(ElicitationType::AuthOrLink),
|
|
})),
|
|
&mut events,
|
|
)
|
|
.await;
|
|
|
|
let [
|
|
TrackEventRequest::AppMentioned(mention),
|
|
TrackEventRequest::AppUsed(usage),
|
|
] = events.as_slice()
|
|
else {
|
|
panic!("expected app mention and usage events");
|
|
};
|
|
assert_eq!(
|
|
[
|
|
(
|
|
mention.event_type,
|
|
mention.event_params.product_client_id.as_deref()
|
|
),
|
|
(
|
|
usage.event_type,
|
|
usage.event_params.app.product_client_id.as_deref()
|
|
),
|
|
],
|
|
[
|
|
("codex_app_mentioned", Some(TEST_PRODUCT_CLIENT_ID)),
|
|
("codex_app_used", Some(TEST_PRODUCT_CLIENT_ID)),
|
|
]
|
|
);
|
|
assert_eq!(
|
|
(
|
|
mention.event_params.turn_trigger.as_deref(),
|
|
usage.event_params.app.turn_trigger.as_deref()
|
|
),
|
|
(
|
|
Some("automation_cron_run_now"),
|
|
Some("automation_cron_run_now")
|
|
)
|
|
);
|
|
assert_eq!(
|
|
usage.event_params.elicitation_type,
|
|
Some(ElicitationType::AuthOrLink)
|
|
);
|
|
}
|