[plugins] Track plugin install requests by ID (#29684)

Summary
- Emit `codex_plugin_install_requested` when a validated plugin install
request is made, before the user accepts or declines the elicitation.
- Record the exact model-visible plugin ID, remote plugin ID, required
connector IDs, stable suggestion ID, and `endpoint_recommendation` vs
`legacy_discovery` source.
- Keep `suggest_reason` out of telemetry and leave connector-only
install requests unchanged.

Rollout
- Backend/schema dependency:
https://github.com/openai/openai/pull/1065270
- Land the backend PR before this producer starts sending the event.

Validation
- `just test -p codex-analytics` (83 passed)
- `just test -p codex-core request_plugin_install` (17 passed)
- `just fix -p codex-analytics`
- `just fix -p codex-core`
- `just fmt`
- `git diff --check`
This commit is contained in:
Alex Daley authored and GitHub committed 2026-06-24 21:29:11 +00:00
1 parent df1ee09ec5
commit 24423f5712
8 files changed
+266 -2

No files matched your search

@@ -62,6 +62,10 @@ use crate::facts::HookRunInput;
use crate::facts::InputError;
use crate::facts::InvocationType;
use crate::facts::PluginInstallFailedInput;
use crate::facts::PluginInstallRequestSource;
use crate::facts::PluginInstallRequested;
use crate::facts::PluginInstallRequestedInput;
use crate::facts::PluginInstallRequestedPlugin;
use crate::facts::PluginState;
use crate::facts::PluginStateChangedInput;
use crate::facts::PluginUsedInput;
@@ -3447,6 +3451,66 @@ async fn reducer_ingests_plugin_state_changed_fact() {
);
}
#[tokio::test]
async fn reducer_ingests_plugin_install_requested_fact() {
let mut reducer = AnalyticsReducer::default();
let mut events = Vec::new();
let tracking = test_tracking_context("thread-1", "turn-1");
let request = PluginInstallRequested {
suggestion_id: "request_plugin_install_call-1".to_string(),
plugins: vec![
PluginInstallRequestedPlugin {
plugin_id: "calendar@openai-curated-remote".to_string(),
remote_plugin_id: Some("plugin_calendar".to_string()),
plugin_name: "Calendar".to_string(),
connector_ids: vec!["connector_calendar".to_string()],
},
PluginInstallRequestedPlugin {
plugin_id: "github@openai-curated-remote".to_string(),
remote_plugin_id: None,
plugin_name: "GitHub".to_string(),
connector_ids: vec!["connector_github".to_string()],
},
],
source: PluginInstallRequestSource::EndpointRecommendation,
};
reducer
.ingest(
AnalyticsFact::Custom(CustomAnalyticsFact::PluginInstallRequested(
PluginInstallRequestedInput { tracking, request },
)),
&mut events,
)
.await;
assert_eq!(
serde_json::to_value(&events).expect("serialize events"),
json!([{
"event_type": "codex_plugin_install_requested",
"event_params": {
"suggestion_id": "request_plugin_install_call-1",
"plugins": [{
"plugin_id": "calendar@openai-curated-remote",
"remote_plugin_id": "plugin_calendar",
"plugin_name": "Calendar",
"connector_ids": ["connector_calendar"],
}, {
"plugin_id": "github@openai-curated-remote",
"remote_plugin_id": null,
"plugin_name": "GitHub",
"connector_ids": ["connector_github"],
}],
"source": "endpoint_recommendation",
"thread_id": "thread-1",
"turn_id": "turn-1",
"model_slug": "gpt-5",
"product_client_id": originator().value,
}
}])
);
}
#[tokio::test]
async fn reducer_ingests_plugin_install_failed_fact() {
let mut reducer = AnalyticsReducer::default();