From 0d107eade9684fe487135e372806665df06d4856 Mon Sep 17 00:00:00 2001 From: Alex Daley Date: Tue, 7 Jul 2026 18:03:25 -0400 Subject: [PATCH 1/2] Add plugin install suggestion outcome analytics --- .../analytics/src/analytics_client_tests.rs | 120 ++++++++++++++++++ codex-rs/analytics/src/client.rs | 14 ++ codex-rs/analytics/src/events.rs | 61 +++++++++ codex-rs/analytics/src/facts.rs | 42 ++++++ codex-rs/analytics/src/lib.rs | 4 + codex-rs/analytics/src/reducer.rs | 20 +++ codex-rs/otel/src/events/session_telemetry.rs | 11 -- codex-rs/otel/src/metrics/names.rs | 1 - codex-rs/otel/tests/suite/manager_metrics.rs | 55 -------- 9 files changed, 261 insertions(+), 67 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 0f759fd93862..204f2780d2a6 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -66,6 +66,11 @@ use crate::facts::PluginInstallRequestSource; use crate::facts::PluginInstallRequested; use crate::facts::PluginInstallRequestedInput; use crate::facts::PluginInstallRequestedPlugin; +use crate::facts::PluginInstallSuggestionOutcome; +use crate::facts::PluginInstallSuggestionOutcomeInput; +use crate::facts::PluginInstallSuggestionOutcomeTool; +use crate::facts::PluginInstallSuggestionResponseAction; +use crate::facts::PluginInstallSuggestionToolType; use crate::facts::PluginState; use crate::facts::PluginStateChangedInput; use crate::facts::PluginUsedInput; @@ -3680,6 +3685,121 @@ async fn reducer_ingests_plugin_install_requested_fact() { ); } +#[tokio::test] +async fn reducer_ingests_plugin_install_suggestion_outcome_facts() { + let mut reducer = AnalyticsReducer::default(); + let mut events = Vec::new(); + + for outcome in [ + PluginInstallSuggestionOutcome { + suggestion_id: "request_plugin_install_plugin-1".to_string(), + source: PluginInstallRequestSource::EndpointRecommendation, + tools: vec![ + PluginInstallSuggestionOutcomeTool { + tool_type: PluginInstallSuggestionToolType::Plugin, + tool_id: "calendar@openai-curated-remote".to_string(), + tool_name: "Calendar".to_string(), + remote_plugin_id: Some("plugin_calendar".to_string()), + connector_ids: vec!["connector_calendar".to_string()], + selected: true, + }, + PluginInstallSuggestionOutcomeTool { + tool_type: PluginInstallSuggestionToolType::Plugin, + tool_id: "github@openai-curated-remote".to_string(), + tool_name: "GitHub".to_string(), + remote_plugin_id: None, + connector_ids: vec!["connector_github".to_string()], + selected: false, + }, + ], + response_action: PluginInstallSuggestionResponseAction::Accept, + user_confirmed: true, + completed: true, + }, + PluginInstallSuggestionOutcome { + suggestion_id: "request_plugin_install_connector-1".to_string(), + source: PluginInstallRequestSource::LegacyDiscovery, + tools: vec![PluginInstallSuggestionOutcomeTool { + tool_type: PluginInstallSuggestionToolType::Connector, + tool_id: "connector_drive".to_string(), + tool_name: "Drive".to_string(), + remote_plugin_id: None, + connector_ids: vec!["connector_drive".to_string()], + selected: false, + }], + response_action: PluginInstallSuggestionResponseAction::Decline, + user_confirmed: false, + completed: false, + }, + ] { + reducer + .ingest( + AnalyticsFact::Custom(CustomAnalyticsFact::PluginInstallSuggestionOutcome( + PluginInstallSuggestionOutcomeInput { + tracking: test_tracking_context("thread-1", "turn-1"), + outcome, + }, + )), + &mut events, + ) + .await; + } + + assert_eq!( + serde_json::to_value(&events).expect("serialize events"), + json!([{ + "event_type": "codex_plugin_install_suggestion_outcome", + "event_params": { + "suggestion_id": "request_plugin_install_plugin-1", + "source": "endpoint_recommendation", + "tools": [{ + "tool_type": "plugin", + "tool_id": "calendar@openai-curated-remote", + "tool_name": "Calendar", + "remote_plugin_id": "plugin_calendar", + "connector_ids": ["connector_calendar"], + "selected": true, + }, { + "tool_type": "plugin", + "tool_id": "github@openai-curated-remote", + "tool_name": "GitHub", + "remote_plugin_id": null, + "connector_ids": ["connector_github"], + "selected": false, + }], + "response_action": "accept", + "user_confirmed": true, + "completed": true, + "thread_id": "thread-1", + "turn_id": "turn-1", + "model_slug": "gpt-5", + "product_client_id": TEST_PRODUCT_CLIENT_ID, + } + }, { + "event_type": "codex_plugin_install_suggestion_outcome", + "event_params": { + "suggestion_id": "request_plugin_install_connector-1", + "source": "legacy_discovery", + "tools": [{ + "tool_type": "connector", + "tool_id": "connector_drive", + "tool_name": "Drive", + "remote_plugin_id": null, + "connector_ids": ["connector_drive"], + "selected": false, + }], + "response_action": "decline", + "user_confirmed": false, + "completed": false, + "thread_id": "thread-1", + "turn_id": "turn-1", + "model_slug": "gpt-5", + "product_client_id": TEST_PRODUCT_CLIENT_ID, + } + }]) + ); +} + #[tokio::test] async fn reducer_ingests_plugin_install_failed_fact() { let mut reducer = AnalyticsReducer::default(); diff --git a/codex-rs/analytics/src/client.rs b/codex-rs/analytics/src/client.rs index f957a665a09c..c5fa8d0133fd 100644 --- a/codex-rs/analytics/src/client.rs +++ b/codex-rs/analytics/src/client.rs @@ -18,6 +18,8 @@ use crate::facts::HookRunInput; use crate::facts::PluginInstallFailedInput; use crate::facts::PluginInstallRequested; use crate::facts::PluginInstallRequestedInput; +use crate::facts::PluginInstallSuggestionOutcome; +use crate::facts::PluginInstallSuggestionOutcomeInput; use crate::facts::PluginState; use crate::facts::PluginStateChangedInput; use crate::facts::SkillInvocation; @@ -325,6 +327,18 @@ impl AnalyticsEventsClient { )); } + pub fn track_plugin_install_suggestion_outcome( + &self, + tracking: TrackEventsContext, + outcome: PluginInstallSuggestionOutcome, + ) { + self.record_fact(AnalyticsFact::Custom( + CustomAnalyticsFact::PluginInstallSuggestionOutcome( + PluginInstallSuggestionOutcomeInput { tracking, outcome }, + ), + )); + } + pub fn track_compaction(&self, event: crate::facts::CodexCompactionEvent) { self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::Compaction( Box::new(event), diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index ad8adf14cbf6..37b31db9760c 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -15,6 +15,7 @@ use crate::facts::GoalEventKind; use crate::facts::HookRunFact; use crate::facts::InvocationType; use crate::facts::PluginInstallRequested; +use crate::facts::PluginInstallSuggestionOutcome; use crate::facts::PluginState; use crate::facts::SubAgentThreadStartedInput; use crate::facts::ThreadInitializationMode; @@ -82,6 +83,7 @@ pub(crate) enum TrackEventRequest { ReviewEvent(CodexReviewEventRequest), PluginUsed(CodexPluginUsedEventRequest), PluginInstallRequested(CodexPluginInstallRequestedEventRequest), + PluginInstallSuggestionOutcome(CodexPluginInstallSuggestionOutcomeEventRequest), PluginInstalled(CodexPluginEventRequest), PluginUninstalled(CodexPluginEventRequest), PluginEnabled(CodexPluginEventRequest), @@ -981,6 +983,36 @@ pub(crate) struct CodexPluginInstallRequestedEventRequest { pub(crate) event_params: CodexPluginInstallRequestedMetadata, } +#[derive(Serialize)] +pub(crate) struct CodexPluginInstallSuggestionOutcomeToolMetadata { + pub(crate) tool_type: crate::facts::PluginInstallSuggestionToolType, + pub(crate) tool_id: String, + pub(crate) tool_name: String, + pub(crate) remote_plugin_id: Option, + pub(crate) connector_ids: Vec, + pub(crate) selected: bool, +} + +#[derive(Serialize)] +pub(crate) struct CodexPluginInstallSuggestionOutcomeMetadata { + pub(crate) suggestion_id: String, + pub(crate) source: crate::facts::PluginInstallRequestSource, + pub(crate) tools: Vec, + pub(crate) response_action: crate::facts::PluginInstallSuggestionResponseAction, + pub(crate) user_confirmed: bool, + pub(crate) completed: bool, + pub(crate) thread_id: String, + pub(crate) turn_id: String, + pub(crate) model_slug: String, + pub(crate) product_client_id: Option, +} + +#[derive(Serialize)] +pub(crate) struct CodexPluginInstallSuggestionOutcomeEventRequest { + pub(crate) event_type: &'static str, + pub(crate) event_params: CodexPluginInstallSuggestionOutcomeMetadata, +} + #[derive(Serialize)] pub(crate) struct CodexPluginEventRequest { pub(crate) event_type: &'static str, @@ -1125,6 +1157,35 @@ pub(crate) fn codex_plugin_install_requested_metadata( } } +pub(crate) fn codex_plugin_install_suggestion_outcome_metadata( + tracking: &TrackEventsContext, + outcome: PluginInstallSuggestionOutcome, +) -> CodexPluginInstallSuggestionOutcomeMetadata { + CodexPluginInstallSuggestionOutcomeMetadata { + suggestion_id: outcome.suggestion_id, + source: outcome.source, + tools: outcome + .tools + .into_iter() + .map(|tool| CodexPluginInstallSuggestionOutcomeToolMetadata { + tool_type: tool.tool_type, + tool_id: tool.tool_id, + tool_name: tool.tool_name, + remote_plugin_id: tool.remote_plugin_id, + connector_ids: tool.connector_ids, + selected: tool.selected, + }) + .collect(), + response_action: outcome.response_action, + user_confirmed: outcome.user_confirmed, + completed: outcome.completed, + thread_id: tracking.thread_id.clone(), + turn_id: tracking.turn_id.clone(), + model_slug: tracking.model_slug.clone(), + product_client_id: Some(tracking.product_client_id.clone()), + } +} + pub(crate) fn codex_compaction_event_params( input: CodexCompactionEvent, session_id: String, diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs index 3346a6e19a72..c49c23fc137c 100644 --- a/codex-rs/analytics/src/facts.rs +++ b/codex-rs/analytics/src/facts.rs @@ -510,6 +510,7 @@ pub(crate) enum CustomAnalyticsFact { HookRun(HookRunInput), PluginUsed(PluginUsedInput), PluginInstallRequested(PluginInstallRequestedInput), + PluginInstallSuggestionOutcome(PluginInstallSuggestionOutcomeInput), PluginStateChanged(PluginStateChangedInput), PluginInstallFailed(PluginInstallFailedInput), ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedInput), @@ -574,6 +575,47 @@ pub(crate) struct PluginInstallRequestedInput { pub request: PluginInstallRequested, } +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum PluginInstallSuggestionToolType { + Connector, + Plugin, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum PluginInstallSuggestionResponseAction { + Accept, + Decline, + Cancel, + Unavailable, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PluginInstallSuggestionOutcome { + pub suggestion_id: String, + pub source: PluginInstallRequestSource, + pub tools: Vec, + pub response_action: PluginInstallSuggestionResponseAction, + pub user_confirmed: bool, + pub completed: bool, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct PluginInstallSuggestionOutcomeTool { + pub tool_type: PluginInstallSuggestionToolType, + pub tool_id: String, + pub tool_name: String, + pub remote_plugin_id: Option, + pub connector_ids: Vec, + pub selected: bool, +} + +pub(crate) struct PluginInstallSuggestionOutcomeInput { + pub tracking: TrackEventsContext, + pub outcome: PluginInstallSuggestionOutcome, +} + pub(crate) struct PluginStateChangedInput { pub plugin: PluginTelemetryMetadata, pub state: PluginState, diff --git a/codex-rs/analytics/src/lib.rs b/codex-rs/analytics/src/lib.rs index 86bc9d682971..e970c141633b 100644 --- a/codex-rs/analytics/src/lib.rs +++ b/codex-rs/analytics/src/lib.rs @@ -45,6 +45,10 @@ pub use facts::InvocationType; pub use facts::PluginInstallRequestSource; pub use facts::PluginInstallRequested; pub use facts::PluginInstallRequestedPlugin; +pub use facts::PluginInstallSuggestionOutcome; +pub use facts::PluginInstallSuggestionOutcomeTool; +pub use facts::PluginInstallSuggestionResponseAction; +pub use facts::PluginInstallSuggestionToolType; pub use facts::SkillInvocation; pub use facts::SubAgentThreadStartedInput; pub use facts::ThreadInitializationMode; diff --git a/codex-rs/analytics/src/reducer.rs b/codex-rs/analytics/src/reducer.rs index 29f21bac52e6..1b8034b16a2b 100644 --- a/codex-rs/analytics/src/reducer.rs +++ b/codex-rs/analytics/src/reducer.rs @@ -29,6 +29,7 @@ use crate::events::CodexPluginEventRequest; use crate::events::CodexPluginInstallFailedEventRequest; use crate::events::CodexPluginInstallFailedMetadata; use crate::events::CodexPluginInstallRequestedEventRequest; +use crate::events::CodexPluginInstallSuggestionOutcomeEventRequest; use crate::events::CodexPluginUsedEventRequest; use crate::events::CodexReviewEventParams; use crate::events::CodexReviewEventRequest; @@ -62,6 +63,7 @@ use crate::events::codex_compaction_event_params; use crate::events::codex_goal_event_params; use crate::events::codex_hook_run_metadata; use crate::events::codex_plugin_install_requested_metadata; +use crate::events::codex_plugin_install_suggestion_outcome_metadata; use crate::events::codex_plugin_metadata; use crate::events::codex_plugin_used_metadata; use crate::events::plugin_state_event_type; @@ -79,6 +81,7 @@ use crate::facts::ExternalAgentConfigImportFailureInput; use crate::facts::HookRunInput; use crate::facts::PluginInstallFailedInput; use crate::facts::PluginInstallRequestedInput; +use crate::facts::PluginInstallSuggestionOutcomeInput; use crate::facts::PluginState; use crate::facts::PluginStateChangedInput; use crate::facts::PluginUsedInput; @@ -539,6 +542,9 @@ impl AnalyticsReducer { CustomAnalyticsFact::PluginInstallRequested(input) => { self.ingest_plugin_install_requested(input, out); } + CustomAnalyticsFact::PluginInstallSuggestionOutcome(input) => { + self.ingest_plugin_install_suggestion_outcome(input, out); + } CustomAnalyticsFact::PluginStateChanged(input) => { self.ingest_plugin_state_changed(input, out); } @@ -814,6 +820,20 @@ impl AnalyticsReducer { )); } + fn ingest_plugin_install_suggestion_outcome( + &mut self, + input: PluginInstallSuggestionOutcomeInput, + out: &mut Vec, + ) { + let PluginInstallSuggestionOutcomeInput { tracking, outcome } = input; + out.push(TrackEventRequest::PluginInstallSuggestionOutcome( + CodexPluginInstallSuggestionOutcomeEventRequest { + event_type: "codex_plugin_install_suggestion_outcome", + event_params: codex_plugin_install_suggestion_outcome_metadata(&tracking, outcome), + }, + )); + } + fn ingest_plugin_state_changed( &mut self, input: PluginStateChangedInput, diff --git a/codex-rs/otel/src/events/session_telemetry.rs b/codex-rs/otel/src/events/session_telemetry.rs index c470f8dc7cc7..c33131e6d327 100644 --- a/codex-rs/otel/src/events/session_telemetry.rs +++ b/codex-rs/otel/src/events/session_telemetry.rs @@ -9,7 +9,6 @@ use crate::metrics::MetricsClient; use crate::metrics::MetricsConfig; use crate::metrics::MetricsError; use crate::metrics::PLUGIN_INSTALL_ELICITATION_SENT_METRIC; -use crate::metrics::PLUGIN_INSTALL_SUGGESTION_METRIC; use crate::metrics::RESPONSES_API_ENGINE_IAPI_TBT_DURATION_METRIC; use crate::metrics::RESPONSES_API_ENGINE_IAPI_TTFT_DURATION_METRIC; use crate::metrics::RESPONSES_API_ENGINE_SERVICE_TBT_DURATION_METRIC; @@ -279,16 +278,6 @@ impl SessionTelemetry { user_confirmed: bool, completed: bool, ) { - let completed_tag = if completed { "true" } else { "false" }; - self.counter( - PLUGIN_INSTALL_SUGGESTION_METRIC, - /*inc*/ 1, - &[ - ("tool_type", tool_type), - ("response_action", response_action), - ("completed", completed_tag), - ], - ); log_and_trace_event!( self, common: { diff --git a/codex-rs/otel/src/metrics/names.rs b/codex-rs/otel/src/metrics/names.rs index d2093ed3f78d..a075536f46d5 100644 --- a/codex-rs/otel/src/metrics/names.rs +++ b/codex-rs/otel/src/metrics/names.rs @@ -41,7 +41,6 @@ pub const GOAL_BLOCKED_METRIC: &str = "codex.goal.blocked"; pub const GOAL_TOKEN_COUNT_METRIC: &str = "codex.goal.token_count"; pub const GOAL_DURATION_SECONDS_METRIC: &str = "codex.goal.duration_s"; pub const PLUGIN_INSTALL_ELICITATION_SENT_METRIC: &str = "codex.plugins.install_elicitation.sent"; -pub const PLUGIN_INSTALL_SUGGESTION_METRIC: &str = "codex.plugins.install_suggestion"; pub const CURATED_PLUGINS_STARTUP_SYNC_METRIC: &str = "codex.plugins.startup_sync"; pub const CURATED_PLUGINS_STARTUP_SYNC_FINAL_METRIC: &str = "codex.plugins.startup_sync.final"; pub const HOOK_RUN_METRIC: &str = "codex.hooks.run"; diff --git a/codex-rs/otel/tests/suite/manager_metrics.rs b/codex-rs/otel/tests/suite/manager_metrics.rs index e13de3f432b0..19dd8d0daa4e 100644 --- a/codex-rs/otel/tests/suite/manager_metrics.rs +++ b/codex-rs/otel/tests/suite/manager_metrics.rs @@ -3,7 +3,6 @@ use crate::harness::build_metrics_with_defaults; use crate::harness::find_metric; use crate::harness::latest_metrics; use codex_otel::PLUGIN_INSTALL_ELICITATION_SENT_METRIC; -use codex_otel::PLUGIN_INSTALL_SUGGESTION_METRIC; use codex_otel::Result; use codex_otel::SessionTelemetry; use codex_otel::TelemetryAuthMode; @@ -164,60 +163,6 @@ fn manager_attaches_optional_service_name_tag() -> Result<()> { Ok(()) } -#[test] -fn manager_records_plugin_install_suggestion_metric() -> Result<()> { - let (metrics, exporter) = build_metrics_with_defaults(&[])?; - let manager = SessionTelemetry::new( - ThreadId::new(), - "gpt-5.1", - "gpt-5.1", - Some("account-id".to_string()), - /*account_email*/ None, - Some(TelemetryAuthMode::ApiKey), - "test_originator".to_string(), - /*log_user_prompts*/ false, - "tty".to_string(), - SessionSource::Cli, - ) - .with_metrics_without_metadata_tags(metrics); - - manager.record_plugin_install_suggestion( - "connector", - "connector_calendar", - "Google Calendar", - "accept", - /*user_confirmed*/ true, - /*completed*/ false, - ); - manager.shutdown_metrics()?; - - let resource_metrics = latest_metrics(&exporter); - let metric = find_metric(&resource_metrics, PLUGIN_INSTALL_SUGGESTION_METRIC) - .expect("plugin install suggestion metric missing"); - let attrs = match metric.data() { - AggregatedMetrics::U64(data) => match data { - MetricData::Sum(sum) => { - let points: Vec<_> = sum.data_points().collect(); - assert_eq!(points.len(), 1); - attributes_to_map(points[0].attributes()) - } - _ => panic!("unexpected counter aggregation"), - }, - _ => panic!("unexpected counter data type"), - }; - - assert_eq!( - attrs, - BTreeMap::from([ - ("completed".to_string(), "false".to_string()), - ("response_action".to_string(), "accept".to_string()), - ("tool_type".to_string(), "connector".to_string()), - ]) - ); - - Ok(()) -} - #[test] fn manager_records_plugin_install_elicitation_sent_metric() -> Result<()> { let (metrics, exporter) = build_metrics_with_defaults(&[])?; From 1174a9f4c2e6a5da7757efd063182dc4bc925618 Mon Sep 17 00:00:00 2001 From: Alex Daley Date: Tue, 7 Jul 2026 18:35:52 -0400 Subject: [PATCH 2/2] Track plugin completion per suggested tool --- codex-rs/analytics/src/analytics_client_tests.rs | 12 ++++++++---- codex-rs/analytics/src/events.rs | 5 ++++- codex-rs/analytics/src/facts.rs | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/codex-rs/analytics/src/analytics_client_tests.rs b/codex-rs/analytics/src/analytics_client_tests.rs index 204f2780d2a6..94248a0aa098 100644 --- a/codex-rs/analytics/src/analytics_client_tests.rs +++ b/codex-rs/analytics/src/analytics_client_tests.rs @@ -3702,6 +3702,7 @@ async fn reducer_ingests_plugin_install_suggestion_outcome_facts() { remote_plugin_id: Some("plugin_calendar".to_string()), connector_ids: vec!["connector_calendar".to_string()], selected: true, + completed: true, }, PluginInstallSuggestionOutcomeTool { tool_type: PluginInstallSuggestionToolType::Plugin, @@ -3709,12 +3710,12 @@ async fn reducer_ingests_plugin_install_suggestion_outcome_facts() { tool_name: "GitHub".to_string(), remote_plugin_id: None, connector_ids: vec!["connector_github".to_string()], - selected: false, + selected: true, + completed: false, }, ], response_action: PluginInstallSuggestionResponseAction::Accept, user_confirmed: true, - completed: true, }, PluginInstallSuggestionOutcome { suggestion_id: "request_plugin_install_connector-1".to_string(), @@ -3726,10 +3727,10 @@ async fn reducer_ingests_plugin_install_suggestion_outcome_facts() { remote_plugin_id: None, connector_ids: vec!["connector_drive".to_string()], selected: false, + completed: false, }], response_action: PluginInstallSuggestionResponseAction::Decline, user_confirmed: false, - completed: false, }, ] { reducer @@ -3759,13 +3760,15 @@ async fn reducer_ingests_plugin_install_suggestion_outcome_facts() { "remote_plugin_id": "plugin_calendar", "connector_ids": ["connector_calendar"], "selected": true, + "completed": true, }, { "tool_type": "plugin", "tool_id": "github@openai-curated-remote", "tool_name": "GitHub", "remote_plugin_id": null, "connector_ids": ["connector_github"], - "selected": false, + "selected": true, + "completed": false, }], "response_action": "accept", "user_confirmed": true, @@ -3787,6 +3790,7 @@ async fn reducer_ingests_plugin_install_suggestion_outcome_facts() { "remote_plugin_id": null, "connector_ids": ["connector_drive"], "selected": false, + "completed": false, }], "response_action": "decline", "user_confirmed": false, diff --git a/codex-rs/analytics/src/events.rs b/codex-rs/analytics/src/events.rs index 37b31db9760c..239624257c3a 100644 --- a/codex-rs/analytics/src/events.rs +++ b/codex-rs/analytics/src/events.rs @@ -991,6 +991,7 @@ pub(crate) struct CodexPluginInstallSuggestionOutcomeToolMetadata { pub(crate) remote_plugin_id: Option, pub(crate) connector_ids: Vec, pub(crate) selected: bool, + pub(crate) completed: bool, } #[derive(Serialize)] @@ -1161,6 +1162,7 @@ pub(crate) fn codex_plugin_install_suggestion_outcome_metadata( tracking: &TrackEventsContext, outcome: PluginInstallSuggestionOutcome, ) -> CodexPluginInstallSuggestionOutcomeMetadata { + let completed = outcome.tools.iter().any(|tool| tool.completed); CodexPluginInstallSuggestionOutcomeMetadata { suggestion_id: outcome.suggestion_id, source: outcome.source, @@ -1174,11 +1176,12 @@ pub(crate) fn codex_plugin_install_suggestion_outcome_metadata( remote_plugin_id: tool.remote_plugin_id, connector_ids: tool.connector_ids, selected: tool.selected, + completed: tool.completed, }) .collect(), response_action: outcome.response_action, user_confirmed: outcome.user_confirmed, - completed: outcome.completed, + completed, thread_id: tracking.thread_id.clone(), turn_id: tracking.turn_id.clone(), model_slug: tracking.model_slug.clone(), diff --git a/codex-rs/analytics/src/facts.rs b/codex-rs/analytics/src/facts.rs index c49c23fc137c..1bc4c72b70d2 100644 --- a/codex-rs/analytics/src/facts.rs +++ b/codex-rs/analytics/src/facts.rs @@ -598,7 +598,6 @@ pub struct PluginInstallSuggestionOutcome { pub tools: Vec, pub response_action: PluginInstallSuggestionResponseAction, pub user_confirmed: bool, - pub completed: bool, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -609,6 +608,7 @@ pub struct PluginInstallSuggestionOutcomeTool { pub remote_plugin_id: Option, pub connector_ids: Vec, pub selected: bool, + pub completed: bool, } pub(crate) struct PluginInstallSuggestionOutcomeInput {