Files
codex/codex-rs/tools/src/request_plugin_install_tests.rs
Adam Perry @ OpenAI df1ee09ec5 mcp: keep elicitation requests below app wire types (#29724)
## Why

Core and tools need to request MCP elicitation without constructing
app-server wire payloads. The request should remain a neutral protocol
concept until app-server serializes it for a client.

## What changed

- Switched core and tools to
`codex_protocol::approvals::ElicitationRequest`.
- Derived turn and server context inside core instead of carrying
app-server request types through lower layers.
- Kept the app-server payload unchanged through an explicit boundary
conversion.
- Removed the remaining production app-server-protocol dependency from
tools.

## Stack

This is PR 5 of 6, stacked on [PR
#29723](https://github.com/openai/codex/pull/29723). Review only the
delta from `codex/split-connector-metadata-types`. Next: [PR
#29725](https://github.com/openai/codex/pull/29725).

## Validation

- `codex-core` MCP coverage passed: 87 tests.
- Tools elicitation and app-server round-trip coverage passed.
2026-06-24 20:53:27 +00:00

203 lines
7.0 KiB
Rust

use super::*;
use crate::DiscoverablePluginInfo;
use pretty_assertions::assert_eq;
use serde_json::json;
#[test]
fn build_request_plugin_install_elicitation_request_uses_expected_shape() {
let connector = DiscoverableTool::Connector(Box::new(AppInfo {
id: "connector_2128aebfecb84f64a069897515042a44".to_string(),
name: "Google Calendar".to_string(),
description: Some("Plan events and schedules.".to_string()),
logo_url: None,
logo_url_dark: None,
icon_assets: None,
icon_dark_assets: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some(
"https://chatgpt.com/apps/google-calendar/connector_2128aebfecb84f64a069897515042a44"
.to_string(),
),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
}));
let request = build_request_plugin_install_elicitation_request(
"Plan and reference events from your calendar",
&connector,
);
assert_eq!(
request,
ElicitationRequest::Form {
meta: Some(json!(RequestPluginInstallMeta {
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
tool_type: DiscoverableToolType::Connector,
suggest_type: DiscoverableToolAction::Install,
suggest_reason: "Plan and reference events from your calendar",
tool_id: "connector_2128aebfecb84f64a069897515042a44",
tool_name: "Google Calendar",
install_url: Some(
"https://chatgpt.com/apps/google-calendar/connector_2128aebfecb84f64a069897515042a44"
),
remote_plugin_id: None,
app_connector_ids: None,
})),
message: "Plan and reference events from your calendar".to_string(),
requested_schema: json!({
"type": "object",
"properties": {},
}),
},
);
}
#[test]
fn build_request_plugin_install_elicitation_request_injects_plugin_metadata() {
let plugin = DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {
id: "sample@openai-curated-remote".to_string(),
remote_plugin_id: Some("plugins~Plugin_sample".to_string()),
name: "Sample Plugin".to_string(),
description: Some("Includes skills, MCP servers, and apps.".to_string()),
has_skills: true,
mcp_server_names: vec!["sample-docs".to_string()],
app_connector_ids: vec!["connector_calendar".to_string()],
}));
let request = build_request_plugin_install_elicitation_request(
"Use the sample plugin's skills and MCP server",
&plugin,
);
assert_eq!(
request,
ElicitationRequest::Form {
meta: Some(json!(RequestPluginInstallMeta {
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
tool_type: DiscoverableToolType::Plugin,
suggest_type: DiscoverableToolAction::Install,
suggest_reason: "Use the sample plugin's skills and MCP server",
tool_id: "sample@openai-curated-remote",
tool_name: "Sample Plugin",
install_url: None,
remote_plugin_id: Some("plugins~Plugin_sample"),
app_connector_ids: Some(&["connector_calendar".to_string()]),
})),
message: "Use the sample plugin's skills and MCP server".to_string(),
requested_schema: json!({
"type": "object",
"properties": {},
}),
},
);
}
#[test]
fn build_request_plugin_install_meta_uses_expected_shape() {
let connector = DiscoverableTool::Connector(Box::new(AppInfo {
id: "connector_68df038e0ba48191908c8434991bbac2".to_string(),
name: "Gmail".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
icon_assets: None,
icon_dark_assets: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some(
"https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2".to_string(),
),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
}));
let meta =
build_request_plugin_install_meta("Find and reference emails from your inbox", &connector);
assert_eq!(
meta,
RequestPluginInstallMeta {
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
tool_type: DiscoverableToolType::Connector,
suggest_type: DiscoverableToolAction::Install,
suggest_reason: "Find and reference emails from your inbox",
tool_id: "connector_68df038e0ba48191908c8434991bbac2",
tool_name: "Gmail",
install_url: Some(
"https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2"
),
remote_plugin_id: None,
app_connector_ids: None,
},
);
}
#[test]
fn verified_connector_install_completed_requires_accessible_connector() {
let accessible_connectors = vec![AppInfo {
id: "calendar".to_string(),
name: "Google Calendar".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
icon_assets: None,
icon_dark_assets: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: true,
is_enabled: false,
plugin_display_names: Vec::new(),
}];
assert!(verified_connector_install_completed(
"calendar",
&accessible_connectors,
));
assert!(!verified_connector_install_completed(
"gmail",
&accessible_connectors,
));
}
#[test]
fn all_requested_connectors_picked_up_requires_every_expected_connector() {
let accessible_connectors = vec![AppInfo {
id: "calendar".to_string(),
name: "Google Calendar".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
icon_assets: None,
icon_dark_assets: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: None,
is_accessible: true,
is_enabled: false,
plugin_display_names: Vec::new(),
}];
assert!(all_requested_connectors_picked_up(
&["calendar".to_string()],
&accessible_connectors,
));
assert!(!all_requested_connectors_picked_up(
&["calendar".to_string(), "gmail".to_string()],
&accessible_connectors,
));
}