mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
[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:
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::InputError;
|
||||||
use crate::facts::InvocationType;
|
use crate::facts::InvocationType;
|
||||||
use crate::facts::PluginInstallFailedInput;
|
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::PluginState;
|
||||||
use crate::facts::PluginStateChangedInput;
|
use crate::facts::PluginStateChangedInput;
|
||||||
use crate::facts::PluginUsedInput;
|
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]
|
#[tokio::test]
|
||||||
async fn reducer_ingests_plugin_install_failed_fact() {
|
async fn reducer_ingests_plugin_install_failed_fact() {
|
||||||
let mut reducer = AnalyticsReducer::default();
|
let mut reducer = AnalyticsReducer::default();
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ use crate::facts::ExternalAgentConfigImportFailureInput;
|
|||||||
use crate::facts::HookRunFact;
|
use crate::facts::HookRunFact;
|
||||||
use crate::facts::HookRunInput;
|
use crate::facts::HookRunInput;
|
||||||
use crate::facts::PluginInstallFailedInput;
|
use crate::facts::PluginInstallFailedInput;
|
||||||
|
use crate::facts::PluginInstallRequested;
|
||||||
|
use crate::facts::PluginInstallRequestedInput;
|
||||||
use crate::facts::PluginState;
|
use crate::facts::PluginState;
|
||||||
use crate::facts::PluginStateChangedInput;
|
use crate::facts::PluginStateChangedInput;
|
||||||
use crate::facts::SkillInvocation;
|
use crate::facts::SkillInvocation;
|
||||||
@@ -310,6 +312,19 @@ impl AnalyticsEventsClient {
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn track_plugin_install_requested(
|
||||||
|
&self,
|
||||||
|
tracking: TrackEventsContext,
|
||||||
|
request: PluginInstallRequested,
|
||||||
|
) {
|
||||||
|
self.record_fact(AnalyticsFact::Custom(
|
||||||
|
CustomAnalyticsFact::PluginInstallRequested(PluginInstallRequestedInput {
|
||||||
|
tracking,
|
||||||
|
request,
|
||||||
|
}),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn track_compaction(&self, event: crate::facts::CodexCompactionEvent) {
|
pub fn track_compaction(&self, event: crate::facts::CodexCompactionEvent) {
|
||||||
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::Compaction(
|
self.record_fact(AnalyticsFact::Custom(CustomAnalyticsFact::Compaction(
|
||||||
Box::new(event),
|
Box::new(event),
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ use crate::facts::CompactionTrigger;
|
|||||||
use crate::facts::GoalEventKind;
|
use crate::facts::GoalEventKind;
|
||||||
use crate::facts::HookRunFact;
|
use crate::facts::HookRunFact;
|
||||||
use crate::facts::InvocationType;
|
use crate::facts::InvocationType;
|
||||||
|
use crate::facts::PluginInstallRequested;
|
||||||
use crate::facts::PluginState;
|
use crate::facts::PluginState;
|
||||||
use crate::facts::SubAgentThreadStartedInput;
|
use crate::facts::SubAgentThreadStartedInput;
|
||||||
use crate::facts::ThreadInitializationMode;
|
use crate::facts::ThreadInitializationMode;
|
||||||
@@ -80,6 +81,7 @@ pub(crate) enum TrackEventRequest {
|
|||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
ReviewEvent(CodexReviewEventRequest),
|
ReviewEvent(CodexReviewEventRequest),
|
||||||
PluginUsed(CodexPluginUsedEventRequest),
|
PluginUsed(CodexPluginUsedEventRequest),
|
||||||
|
PluginInstallRequested(CodexPluginInstallRequestedEventRequest),
|
||||||
PluginInstalled(CodexPluginEventRequest),
|
PluginInstalled(CodexPluginEventRequest),
|
||||||
PluginUninstalled(CodexPluginEventRequest),
|
PluginUninstalled(CodexPluginEventRequest),
|
||||||
PluginEnabled(CodexPluginEventRequest),
|
PluginEnabled(CodexPluginEventRequest),
|
||||||
@@ -954,6 +956,31 @@ pub(crate) struct CodexPluginUsedMetadata {
|
|||||||
pub(crate) model_slug: Option<String>,
|
pub(crate) model_slug: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct CodexPluginInstallRequestedPluginMetadata {
|
||||||
|
pub(crate) plugin_id: String,
|
||||||
|
pub(crate) remote_plugin_id: Option<String>,
|
||||||
|
pub(crate) plugin_name: String,
|
||||||
|
pub(crate) connector_ids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct CodexPluginInstallRequestedMetadata {
|
||||||
|
pub(crate) suggestion_id: String,
|
||||||
|
pub(crate) plugins: Vec<CodexPluginInstallRequestedPluginMetadata>,
|
||||||
|
pub(crate) source: crate::facts::PluginInstallRequestSource,
|
||||||
|
pub(crate) thread_id: String,
|
||||||
|
pub(crate) turn_id: String,
|
||||||
|
pub(crate) model_slug: String,
|
||||||
|
pub(crate) product_client_id: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
pub(crate) struct CodexPluginInstallRequestedEventRequest {
|
||||||
|
pub(crate) event_type: &'static str,
|
||||||
|
pub(crate) event_params: CodexPluginInstallRequestedMetadata,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
pub(crate) struct CodexPluginEventRequest {
|
pub(crate) struct CodexPluginEventRequest {
|
||||||
pub(crate) event_type: &'static str,
|
pub(crate) event_type: &'static str,
|
||||||
@@ -1074,6 +1101,30 @@ fn codex_plugin_metadata_with_product_client_id(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn codex_plugin_install_requested_metadata(
|
||||||
|
tracking: &TrackEventsContext,
|
||||||
|
request: PluginInstallRequested,
|
||||||
|
) -> CodexPluginInstallRequestedMetadata {
|
||||||
|
CodexPluginInstallRequestedMetadata {
|
||||||
|
suggestion_id: request.suggestion_id,
|
||||||
|
plugins: request
|
||||||
|
.plugins
|
||||||
|
.into_iter()
|
||||||
|
.map(|plugin| CodexPluginInstallRequestedPluginMetadata {
|
||||||
|
plugin_id: plugin.plugin_id,
|
||||||
|
remote_plugin_id: plugin.remote_plugin_id,
|
||||||
|
plugin_name: plugin.plugin_name,
|
||||||
|
connector_ids: plugin.connector_ids,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
source: request.source,
|
||||||
|
thread_id: tracking.thread_id.clone(),
|
||||||
|
turn_id: tracking.turn_id.clone(),
|
||||||
|
model_slug: tracking.model_slug.clone(),
|
||||||
|
product_client_id: Some(originator().value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn codex_compaction_event_params(
|
pub(crate) fn codex_compaction_event_params(
|
||||||
input: CodexCompactionEvent,
|
input: CodexCompactionEvent,
|
||||||
session_id: String,
|
session_id: String,
|
||||||
|
|||||||
@@ -508,6 +508,7 @@ pub(crate) enum CustomAnalyticsFact {
|
|||||||
AppUsed(AppUsedInput),
|
AppUsed(AppUsedInput),
|
||||||
HookRun(HookRunInput),
|
HookRun(HookRunInput),
|
||||||
PluginUsed(PluginUsedInput),
|
PluginUsed(PluginUsedInput),
|
||||||
|
PluginInstallRequested(PluginInstallRequestedInput),
|
||||||
PluginStateChanged(PluginStateChangedInput),
|
PluginStateChanged(PluginStateChangedInput),
|
||||||
PluginInstallFailed(PluginInstallFailedInput),
|
PluginInstallFailed(PluginInstallFailedInput),
|
||||||
ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedInput),
|
ExternalAgentConfigImportCompleted(ExternalAgentConfigImportCompletedInput),
|
||||||
@@ -545,6 +546,33 @@ pub(crate) struct PluginUsedInput {
|
|||||||
pub plugin: PluginTelemetryMetadata,
|
pub plugin: PluginTelemetryMetadata,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum PluginInstallRequestSource {
|
||||||
|
EndpointRecommendation,
|
||||||
|
LegacyDiscovery,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct PluginInstallRequested {
|
||||||
|
pub suggestion_id: String,
|
||||||
|
pub plugins: Vec<PluginInstallRequestedPlugin>,
|
||||||
|
pub source: PluginInstallRequestSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct PluginInstallRequestedPlugin {
|
||||||
|
pub plugin_id: String,
|
||||||
|
pub remote_plugin_id: Option<String>,
|
||||||
|
pub plugin_name: String,
|
||||||
|
pub connector_ids: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct PluginInstallRequestedInput {
|
||||||
|
pub tracking: TrackEventsContext,
|
||||||
|
pub request: PluginInstallRequested,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct PluginStateChangedInput {
|
pub(crate) struct PluginStateChangedInput {
|
||||||
pub plugin: PluginTelemetryMetadata,
|
pub plugin: PluginTelemetryMetadata,
|
||||||
pub state: PluginState,
|
pub state: PluginState,
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ pub use facts::GoalEventKind;
|
|||||||
pub use facts::HookRunFact;
|
pub use facts::HookRunFact;
|
||||||
pub use facts::InputError;
|
pub use facts::InputError;
|
||||||
pub use facts::InvocationType;
|
pub use facts::InvocationType;
|
||||||
|
pub use facts::PluginInstallRequestSource;
|
||||||
|
pub use facts::PluginInstallRequested;
|
||||||
|
pub use facts::PluginInstallRequestedPlugin;
|
||||||
pub use facts::SkillInvocation;
|
pub use facts::SkillInvocation;
|
||||||
pub use facts::SubAgentThreadStartedInput;
|
pub use facts::SubAgentThreadStartedInput;
|
||||||
pub use facts::ThreadInitializationMode;
|
pub use facts::ThreadInitializationMode;
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ use crate::events::CodexOnboardingExternalAgentImportFailureMetadata;
|
|||||||
use crate::events::CodexPluginEventRequest;
|
use crate::events::CodexPluginEventRequest;
|
||||||
use crate::events::CodexPluginInstallFailedEventRequest;
|
use crate::events::CodexPluginInstallFailedEventRequest;
|
||||||
use crate::events::CodexPluginInstallFailedMetadata;
|
use crate::events::CodexPluginInstallFailedMetadata;
|
||||||
|
use crate::events::CodexPluginInstallRequestedEventRequest;
|
||||||
use crate::events::CodexPluginUsedEventRequest;
|
use crate::events::CodexPluginUsedEventRequest;
|
||||||
use crate::events::CodexReviewEventParams;
|
use crate::events::CodexReviewEventParams;
|
||||||
use crate::events::CodexReviewEventRequest;
|
use crate::events::CodexReviewEventRequest;
|
||||||
@@ -60,6 +61,7 @@ use crate::events::codex_app_metadata;
|
|||||||
use crate::events::codex_compaction_event_params;
|
use crate::events::codex_compaction_event_params;
|
||||||
use crate::events::codex_goal_event_params;
|
use crate::events::codex_goal_event_params;
|
||||||
use crate::events::codex_hook_run_metadata;
|
use crate::events::codex_hook_run_metadata;
|
||||||
|
use crate::events::codex_plugin_install_requested_metadata;
|
||||||
use crate::events::codex_plugin_metadata;
|
use crate::events::codex_plugin_metadata;
|
||||||
use crate::events::codex_plugin_used_metadata;
|
use crate::events::codex_plugin_used_metadata;
|
||||||
use crate::events::plugin_state_event_type;
|
use crate::events::plugin_state_event_type;
|
||||||
@@ -76,6 +78,7 @@ use crate::facts::ExternalAgentConfigImportCompletedInput;
|
|||||||
use crate::facts::ExternalAgentConfigImportFailureInput;
|
use crate::facts::ExternalAgentConfigImportFailureInput;
|
||||||
use crate::facts::HookRunInput;
|
use crate::facts::HookRunInput;
|
||||||
use crate::facts::PluginInstallFailedInput;
|
use crate::facts::PluginInstallFailedInput;
|
||||||
|
use crate::facts::PluginInstallRequestedInput;
|
||||||
use crate::facts::PluginState;
|
use crate::facts::PluginState;
|
||||||
use crate::facts::PluginStateChangedInput;
|
use crate::facts::PluginStateChangedInput;
|
||||||
use crate::facts::PluginUsedInput;
|
use crate::facts::PluginUsedInput;
|
||||||
@@ -517,6 +520,9 @@ impl AnalyticsReducer {
|
|||||||
CustomAnalyticsFact::PluginUsed(input) => {
|
CustomAnalyticsFact::PluginUsed(input) => {
|
||||||
self.ingest_plugin_used(input, out);
|
self.ingest_plugin_used(input, out);
|
||||||
}
|
}
|
||||||
|
CustomAnalyticsFact::PluginInstallRequested(input) => {
|
||||||
|
self.ingest_plugin_install_requested(input, out);
|
||||||
|
}
|
||||||
CustomAnalyticsFact::PluginStateChanged(input) => {
|
CustomAnalyticsFact::PluginStateChanged(input) => {
|
||||||
self.ingest_plugin_state_changed(input, out);
|
self.ingest_plugin_state_changed(input, out);
|
||||||
}
|
}
|
||||||
@@ -775,6 +781,20 @@ impl AnalyticsReducer {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn ingest_plugin_install_requested(
|
||||||
|
&mut self,
|
||||||
|
input: PluginInstallRequestedInput,
|
||||||
|
out: &mut Vec<TrackEventRequest>,
|
||||||
|
) {
|
||||||
|
let PluginInstallRequestedInput { tracking, request } = input;
|
||||||
|
out.push(TrackEventRequest::PluginInstallRequested(
|
||||||
|
CodexPluginInstallRequestedEventRequest {
|
||||||
|
event_type: "codex_plugin_install_requested",
|
||||||
|
event_params: codex_plugin_install_requested_metadata(&tracking, request),
|
||||||
|
},
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
fn ingest_plugin_state_changed(
|
fn ingest_plugin_state_changed(
|
||||||
&mut self,
|
&mut self,
|
||||||
input: PluginStateChangedInput,
|
input: PluginStateChangedInput,
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::connectors::AppInfo;
|
use codex_analytics::PluginInstallRequestSource;
|
||||||
|
use codex_analytics::PluginInstallRequested;
|
||||||
|
use codex_analytics::PluginInstallRequestedPlugin;
|
||||||
|
use codex_analytics::build_track_events_context;
|
||||||
use codex_config::types::ToolSuggestDisabledTool;
|
use codex_config::types::ToolSuggestDisabledTool;
|
||||||
use codex_core_plugins::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
|
use codex_core_plugins::remote::REMOTE_GLOBAL_MARKETPLACE_NAME;
|
||||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||||
@@ -29,6 +32,7 @@ use tracing::warn;
|
|||||||
use crate::config::edit::ConfigEdit;
|
use crate::config::edit::ConfigEdit;
|
||||||
use crate::config::edit::ConfigEditsBuilder;
|
use crate::config::edit::ConfigEditsBuilder;
|
||||||
use crate::connectors;
|
use crate::connectors;
|
||||||
|
use crate::connectors::AppInfo;
|
||||||
use crate::function_tool::FunctionCallError;
|
use crate::function_tool::FunctionCallError;
|
||||||
use crate::tools::context::FunctionToolOutput;
|
use crate::tools::context::FunctionToolOutput;
|
||||||
use crate::tools::context::ToolInvocation;
|
use crate::tools::context::ToolInvocation;
|
||||||
@@ -172,7 +176,38 @@ impl RequestPluginInstallHandler {
|
|||||||
})?;
|
})?;
|
||||||
let tool_type = tool.tool_type();
|
let tool_type = tool.tool_type();
|
||||||
|
|
||||||
let request_id = RequestId::String(format!("request_plugin_install_{call_id}").into());
|
let suggestion_id = format!("request_plugin_install_{call_id}");
|
||||||
|
if let DiscoverableTool::Plugin(plugin) = &tool {
|
||||||
|
let source = match self.presentation {
|
||||||
|
ToolSuggestPresentation::ListTool => PluginInstallRequestSource::LegacyDiscovery,
|
||||||
|
ToolSuggestPresentation::RecommendationContext => {
|
||||||
|
PluginInstallRequestSource::EndpointRecommendation
|
||||||
|
}
|
||||||
|
};
|
||||||
|
session
|
||||||
|
.services
|
||||||
|
.analytics_events_client
|
||||||
|
.track_plugin_install_requested(
|
||||||
|
build_track_events_context(
|
||||||
|
turn.model_info.slug.clone(),
|
||||||
|
session.thread_id.to_string(),
|
||||||
|
turn.sub_id.clone(),
|
||||||
|
turn.originator.clone(),
|
||||||
|
),
|
||||||
|
PluginInstallRequested {
|
||||||
|
suggestion_id: suggestion_id.clone(),
|
||||||
|
plugins: vec![PluginInstallRequestedPlugin {
|
||||||
|
plugin_id: plugin.id.clone(),
|
||||||
|
remote_plugin_id: plugin.remote_plugin_id.clone(),
|
||||||
|
plugin_name: plugin.name.clone(),
|
||||||
|
connector_ids: plugin.app_connector_ids.clone(),
|
||||||
|
}],
|
||||||
|
source,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let request_id = RequestId::String(suggestion_id.into());
|
||||||
let request = build_request_plugin_install_elicitation_request(suggest_reason, &tool);
|
let request = build_request_plugin_install_elicitation_request(suggest_reason, &tool);
|
||||||
let elicitation = session
|
let elicitation = session
|
||||||
.request_mcp_server_elicitation(
|
.request_mcp_server_elicitation(
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ use core_test_support::wait_for_event;
|
|||||||
use core_test_support::wait_for_event_match;
|
use core_test_support::wait_for_event_match;
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
use std::time::Duration;
|
||||||
|
use std::time::Instant;
|
||||||
use wiremock::Mock;
|
use wiremock::Mock;
|
||||||
use wiremock::MockGuard;
|
use wiremock::MockGuard;
|
||||||
use wiremock::ResponseTemplate;
|
use wiremock::ResponseTemplate;
|
||||||
@@ -474,6 +476,52 @@ async fn run_remote_plugin_install_metadata_case() -> Result<()> {
|
|||||||
assert_eq!(meta["remote_plugin_id"], REMOTE_PLUGIN_ID);
|
assert_eq!(meta["remote_plugin_id"], REMOTE_PLUGIN_ID);
|
||||||
assert_eq!(meta["app_connector_ids"], json!([APP_CONNECTOR_ID]));
|
assert_eq!(meta["app_connector_ids"], json!([APP_CONNECTOR_ID]));
|
||||||
|
|
||||||
|
let deadline = Instant::now() + Duration::from_secs(10);
|
||||||
|
let analytics_event = loop {
|
||||||
|
let requests = server.received_requests().await.unwrap_or_default();
|
||||||
|
if let Some(event) = requests
|
||||||
|
.into_iter()
|
||||||
|
.filter(|request| request.url.path() == "/codex/analytics-events/events")
|
||||||
|
.find_map(|request| {
|
||||||
|
let payload: Value = serde_json::from_slice(&request.body).ok()?;
|
||||||
|
payload["events"].as_array().and_then(|events| {
|
||||||
|
events
|
||||||
|
.iter()
|
||||||
|
.find(|event| event["event_type"] == "codex_plugin_install_requested")
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
{
|
||||||
|
break event;
|
||||||
|
}
|
||||||
|
if Instant::now() >= deadline {
|
||||||
|
panic!("timed out waiting for plugin install request analytics");
|
||||||
|
}
|
||||||
|
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||||
|
};
|
||||||
|
let thread_id = analytics_event["event_params"]["thread_id"].clone();
|
||||||
|
let turn_id = analytics_event["event_params"]["turn_id"].clone();
|
||||||
|
assert_eq!(
|
||||||
|
analytics_event,
|
||||||
|
json!({
|
||||||
|
"event_type": "codex_plugin_install_requested",
|
||||||
|
"event_params": {
|
||||||
|
"suggestion_id": "request_plugin_install_install-github",
|
||||||
|
"plugins": [{
|
||||||
|
"plugin_id": "github@openai-curated-remote",
|
||||||
|
"remote_plugin_id": REMOTE_PLUGIN_ID,
|
||||||
|
"plugin_name": "GitHub",
|
||||||
|
"connector_ids": [APP_CONNECTOR_ID],
|
||||||
|
}],
|
||||||
|
"source": "endpoint_recommendation",
|
||||||
|
"thread_id": thread_id,
|
||||||
|
"turn_id": turn_id,
|
||||||
|
"model_slug": "gpt-5.4",
|
||||||
|
"product_client_id": codex_login::default_client::originator().value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
resolve_install_elicitation(&test, elicitation, ElicitationAction::Decline).await?;
|
resolve_install_elicitation(&test, elicitation, ElicitationAction::Decline).await?;
|
||||||
|
|
||||||
let requests = mock.requests();
|
let requests = mock.requests();
|
||||||
|
|||||||
Reference in new issue
Block a user