mirror of
https://github.com/openai/codex.git
synced 2026-09-10 20:26:47 +00:00
Add plugin install request model
This commit is contained in:
12
codex-rs/Cargo.lock
generated
12
codex-rs/Cargo.lock
generated
@@ -3601,6 +3601,18 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-plugin-installs-extension"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-app-server-protocol",
|
||||
"codex-extension-api",
|
||||
"codex-tools",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-process-hardening"
|
||||
version = "0.0.0"
|
||||
|
||||
@@ -53,6 +53,7 @@ members = [
|
||||
"ext/image-generation",
|
||||
"ext/memories",
|
||||
"ext/mcp",
|
||||
"ext/plugin-installs",
|
||||
"ext/skills",
|
||||
"ext/web-search",
|
||||
"external-agent-migration",
|
||||
@@ -200,6 +201,7 @@ codex-mcp = { path = "codex-mcp" }
|
||||
codex-mcp-extension = { path = "ext/mcp" }
|
||||
codex-mcp-server = { path = "mcp-server" }
|
||||
codex-model-provider-info = { path = "model-provider-info" }
|
||||
codex-plugin-installs-extension = { path = "ext/plugin-installs" }
|
||||
codex-models-manager = { path = "models-manager" }
|
||||
codex-network-proxy = { path = "network-proxy" }
|
||||
codex-ollama = { path = "ollama" }
|
||||
|
||||
6
codex-rs/ext/plugin-installs/BUILD.bazel
Normal file
6
codex-rs/ext/plugin-installs/BUILD.bazel
Normal file
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "plugin-installs",
|
||||
crate_name = "codex_plugin_installs_extension",
|
||||
)
|
||||
23
codex-rs/ext/plugin-installs/Cargo.toml
Normal file
23
codex-rs/ext/plugin-installs/Cargo.toml
Normal file
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
name = "codex-plugin-installs-extension"
|
||||
version.workspace = true
|
||||
|
||||
[lib]
|
||||
name = "codex_plugin_installs_extension"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-extension-api = { workspace = true }
|
||||
codex-tools = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
220
codex-rs/ext/plugin-installs/src/domain.rs
Normal file
220
codex-rs/ext/plugin-installs/src/domain.rs
Normal file
@@ -0,0 +1,220 @@
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use codex_app_server_protocol::AppInfo;
|
||||
use codex_app_server_protocol::McpElicitationObjectType;
|
||||
use codex_app_server_protocol::McpElicitationSchema;
|
||||
use codex_app_server_protocol::McpServerElicitationRequest;
|
||||
use codex_app_server_protocol::McpServerElicitationRequestParams;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
use codex_tools::DiscoverableTool;
|
||||
use codex_tools::DiscoverableToolAction;
|
||||
use codex_tools::DiscoverableToolType;
|
||||
|
||||
pub const REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE: &str = "tool_suggestion";
|
||||
pub const REQUEST_PLUGIN_INSTALL_PERSIST_KEY: &str = "persist";
|
||||
pub const REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE: &str = "always";
|
||||
const REQUEST_PLUGIN_INSTALL_MESSAGE: &str = "Choose integrations";
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct RequestPluginInstallsArgs {
|
||||
pub action_type: DiscoverableToolAction,
|
||||
pub entries: Option<Vec<RequestPluginInstallPickerEntry>>,
|
||||
pub categories: Option<Vec<RequestPluginInstallPickerCategory>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallPickerEntry {
|
||||
pub tool_id: String,
|
||||
pub tool_type: DiscoverableToolType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallPickerCategory {
|
||||
pub title: String,
|
||||
pub entries: Vec<RequestPluginInstallPickerEntry>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallsResult {
|
||||
pub completed: bool,
|
||||
pub user_confirmed: bool,
|
||||
pub action_type: DiscoverableToolAction,
|
||||
pub entries: Vec<RequestPluginInstallEntryResult>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallInstalledEntry {
|
||||
pub tool_id: String,
|
||||
pub tool_type: DiscoverableToolType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallEntryResult {
|
||||
pub tool_type: DiscoverableToolType,
|
||||
pub tool_id: String,
|
||||
pub tool_name: String,
|
||||
pub completed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallsMeta<'a> {
|
||||
pub codex_approval_kind: &'static str,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub persist: Option<&'static str>,
|
||||
pub suggest_type: DiscoverableToolAction,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub entries: Option<Vec<RequestPluginInstallEntryMeta<'a>>>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub categories: Option<Vec<RequestPluginInstallCategoryMeta<'a>>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RequestPluginInstallResolvedPickerEntry {
|
||||
pub category_index: Option<usize>,
|
||||
pub tool: DiscoverableTool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallEntryMeta<'a> {
|
||||
pub tool_id: &'a str,
|
||||
pub tool_name: &'a str,
|
||||
pub tool_type: DiscoverableToolType,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub description: Option<&'a str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub install_url: Option<&'a str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub remote_plugin_id: Option<&'a str>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub app_connector_ids: Option<&'a [String]>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, PartialEq, Eq)]
|
||||
pub struct RequestPluginInstallCategoryMeta<'a> {
|
||||
pub title: &'a str,
|
||||
pub entries: Vec<RequestPluginInstallEntryMeta<'a>>,
|
||||
}
|
||||
|
||||
pub fn build_request_plugin_installs_elicitation_request(
|
||||
server_name: &str,
|
||||
thread_id: String,
|
||||
turn_id: String,
|
||||
args: &RequestPluginInstallsArgs,
|
||||
resolved_entries: &[RequestPluginInstallResolvedPickerEntry],
|
||||
) -> McpServerElicitationRequestParams {
|
||||
McpServerElicitationRequestParams {
|
||||
thread_id,
|
||||
turn_id: Some(turn_id),
|
||||
server_name: server_name.to_string(),
|
||||
request: McpServerElicitationRequest::Form {
|
||||
meta: Some(json!(build_request_plugin_installs_meta(
|
||||
args,
|
||||
resolved_entries
|
||||
))),
|
||||
message: REQUEST_PLUGIN_INSTALL_MESSAGE.to_string(),
|
||||
requested_schema: empty_elicitation_schema(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_elicitation_schema() -> McpElicitationSchema {
|
||||
McpElicitationSchema {
|
||||
schema_uri: None,
|
||||
type_: McpElicitationObjectType::Object,
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn all_requested_connectors_picked_up(
|
||||
expected_connector_ids: &[String],
|
||||
accessible_connectors: &[AppInfo],
|
||||
) -> bool {
|
||||
expected_connector_ids.iter().all(|connector_id| {
|
||||
verified_connector_install_completed(connector_id, accessible_connectors)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn verified_connector_install_completed(
|
||||
tool_id: &str,
|
||||
accessible_connectors: &[AppInfo],
|
||||
) -> bool {
|
||||
accessible_connectors
|
||||
.iter()
|
||||
.find(|connector| connector.id == tool_id)
|
||||
.is_some_and(|connector| connector.is_accessible)
|
||||
}
|
||||
|
||||
fn build_request_plugin_installs_meta<'a>(
|
||||
args: &'a RequestPluginInstallsArgs,
|
||||
resolved_entries: &'a [RequestPluginInstallResolvedPickerEntry],
|
||||
) -> RequestPluginInstallsMeta<'a> {
|
||||
let entries = args.entries.as_ref().map(|_| {
|
||||
resolved_entries
|
||||
.iter()
|
||||
.map(build_request_plugin_install_entry_meta)
|
||||
.collect()
|
||||
});
|
||||
let categories = args.categories.as_ref().map(|categories| {
|
||||
categories
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(category_index, category)| {
|
||||
let entries = resolved_entries
|
||||
.iter()
|
||||
.filter(|entry| entry.category_index == Some(category_index))
|
||||
.map(build_request_plugin_install_entry_meta)
|
||||
.collect();
|
||||
RequestPluginInstallCategoryMeta {
|
||||
title: category.title.as_str(),
|
||||
entries,
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
RequestPluginInstallsMeta {
|
||||
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
|
||||
persist: Some(REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE),
|
||||
suggest_type: args.action_type,
|
||||
entries,
|
||||
categories,
|
||||
}
|
||||
}
|
||||
|
||||
fn build_request_plugin_install_entry_meta<'a>(
|
||||
entry: &'a RequestPluginInstallResolvedPickerEntry,
|
||||
) -> RequestPluginInstallEntryMeta<'a> {
|
||||
let tool = &entry.tool;
|
||||
let (remote_plugin_id, app_connector_ids) = match tool {
|
||||
DiscoverableTool::Connector(_) => (None, None),
|
||||
DiscoverableTool::Plugin(plugin) => (
|
||||
plugin.remote_plugin_id.as_deref(),
|
||||
Some(plugin.app_connector_ids.as_slice()),
|
||||
),
|
||||
};
|
||||
|
||||
RequestPluginInstallEntryMeta {
|
||||
tool_id: tool.id(),
|
||||
tool_name: tool.name(),
|
||||
tool_type: tool.tool_type(),
|
||||
description: discoverable_tool_description(tool),
|
||||
install_url: tool.install_url(),
|
||||
remote_plugin_id,
|
||||
app_connector_ids,
|
||||
}
|
||||
}
|
||||
|
||||
fn discoverable_tool_description(tool: &DiscoverableTool) -> Option<&str> {
|
||||
match tool {
|
||||
DiscoverableTool::Connector(connector) => connector.description.as_deref(),
|
||||
DiscoverableTool::Plugin(plugin) => plugin.description.as_deref(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "domain_tests.rs"]
|
||||
mod tests;
|
||||
315
codex-rs/ext/plugin-installs/src/domain_tests.rs
Normal file
315
codex-rs/ext/plugin-installs/src/domain_tests.rs
Normal file
@@ -0,0 +1,315 @@
|
||||
use super::*;
|
||||
use codex_tools::DiscoverablePluginInfo;
|
||||
use pretty_assertions::assert_eq;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn request_plugin_installs_result_keeps_model_response_minimal() {
|
||||
assert_eq!(
|
||||
json!(RequestPluginInstallsResult {
|
||||
completed: false,
|
||||
user_confirmed: false,
|
||||
action_type: DiscoverableToolAction::Install,
|
||||
entries: vec![RequestPluginInstallEntryResult {
|
||||
tool_type: DiscoverableToolType::Plugin,
|
||||
tool_id: "apollo@openai-curated-remote".to_string(),
|
||||
tool_name: "Apollo".to_string(),
|
||||
completed: false,
|
||||
}],
|
||||
}),
|
||||
json!({
|
||||
"completed": false,
|
||||
"user_confirmed": false,
|
||||
"action_type": "install",
|
||||
"entries": [
|
||||
{
|
||||
"tool_type": "plugin",
|
||||
"tool_id": "apollo@openai-curated-remote",
|
||||
"tool_name": "Apollo",
|
||||
"completed": false,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_request_plugin_installs_elicitation_request_uses_flat_entries_shape() {
|
||||
let args = RequestPluginInstallsArgs {
|
||||
action_type: DiscoverableToolAction::Install,
|
||||
entries: Some(vec![RequestPluginInstallPickerEntry {
|
||||
tool_id: "connector_2128aebfecb84f64a069897515042a44".to_string(),
|
||||
tool_type: DiscoverableToolType::Connector,
|
||||
}]),
|
||||
categories: None,
|
||||
};
|
||||
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,
|
||||
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 resolved_entries = [RequestPluginInstallResolvedPickerEntry {
|
||||
category_index: None,
|
||||
tool: connector,
|
||||
}];
|
||||
|
||||
let request = build_request_plugin_installs_elicitation_request(
|
||||
"codex-apps",
|
||||
"thread-1".to_string(),
|
||||
"turn-1".to_string(),
|
||||
&args,
|
||||
&resolved_entries,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequestParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
server_name: "codex-apps".to_string(),
|
||||
request: McpServerElicitationRequest::Form {
|
||||
meta: Some(json!(RequestPluginInstallsMeta {
|
||||
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
|
||||
persist: Some(REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE),
|
||||
suggest_type: DiscoverableToolAction::Install,
|
||||
entries: Some(vec![RequestPluginInstallEntryMeta {
|
||||
tool_id: "connector_2128aebfecb84f64a069897515042a44",
|
||||
tool_name: "Google Calendar",
|
||||
tool_type: DiscoverableToolType::Connector,
|
||||
description: Some("Plan events and schedules."),
|
||||
install_url: Some(
|
||||
"https://chatgpt.com/apps/google-calendar/connector_2128aebfecb84f64a069897515042a44"
|
||||
),
|
||||
remote_plugin_id: None,
|
||||
app_connector_ids: None,
|
||||
}]),
|
||||
categories: None,
|
||||
})),
|
||||
message: "Choose integrations".to_string(),
|
||||
requested_schema: McpElicitationSchema {
|
||||
schema_uri: None,
|
||||
type_: McpElicitationObjectType::Object,
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_request_plugin_installs_elicitation_request_injects_plugin_metadata() {
|
||||
let args = RequestPluginInstallsArgs {
|
||||
action_type: DiscoverableToolAction::Install,
|
||||
entries: Some(vec![RequestPluginInstallPickerEntry {
|
||||
tool_id: "sample@openai-curated-remote".to_string(),
|
||||
tool_type: DiscoverableToolType::Plugin,
|
||||
}]),
|
||||
categories: None,
|
||||
};
|
||||
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 resolved_entries = [RequestPluginInstallResolvedPickerEntry {
|
||||
category_index: None,
|
||||
tool: plugin,
|
||||
}];
|
||||
|
||||
let request = build_request_plugin_installs_elicitation_request(
|
||||
"codex-apps",
|
||||
"thread-1".to_string(),
|
||||
"turn-1".to_string(),
|
||||
&args,
|
||||
&resolved_entries,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequestParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
server_name: "codex-apps".to_string(),
|
||||
request: McpServerElicitationRequest::Form {
|
||||
meta: Some(json!(RequestPluginInstallsMeta {
|
||||
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
|
||||
persist: Some(REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE),
|
||||
suggest_type: DiscoverableToolAction::Install,
|
||||
entries: Some(vec![RequestPluginInstallEntryMeta {
|
||||
tool_id: "sample@openai-curated-remote",
|
||||
tool_name: "Sample Plugin",
|
||||
tool_type: DiscoverableToolType::Plugin,
|
||||
description: Some("Includes skills, MCP servers, and apps."),
|
||||
install_url: None,
|
||||
remote_plugin_id: Some("plugins~Plugin_sample"),
|
||||
app_connector_ids: Some(&["connector_calendar".to_string()]),
|
||||
}]),
|
||||
categories: None,
|
||||
})),
|
||||
message: "Choose integrations".to_string(),
|
||||
requested_schema: McpElicitationSchema {
|
||||
schema_uri: None,
|
||||
type_: McpElicitationObjectType::Object,
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_request_plugin_installs_elicitation_request_uses_categories_shape() {
|
||||
let args = RequestPluginInstallsArgs {
|
||||
action_type: DiscoverableToolAction::Install,
|
||||
entries: None,
|
||||
categories: Some(vec![RequestPluginInstallPickerCategory {
|
||||
title: "Calendar".to_string(),
|
||||
entries: vec![RequestPluginInstallPickerEntry {
|
||||
tool_id: "connector_2128aebfecb84f64a069897515042a44".to_string(),
|
||||
tool_type: DiscoverableToolType::Connector,
|
||||
}],
|
||||
}]),
|
||||
};
|
||||
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,
|
||||
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 resolved_entries = [RequestPluginInstallResolvedPickerEntry {
|
||||
category_index: Some(0),
|
||||
tool: connector,
|
||||
}];
|
||||
|
||||
let request = build_request_plugin_installs_elicitation_request(
|
||||
"codex-apps",
|
||||
"thread-1".to_string(),
|
||||
"turn-1".to_string(),
|
||||
&args,
|
||||
&resolved_entries,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequestParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
server_name: "codex-apps".to_string(),
|
||||
request: McpServerElicitationRequest::Form {
|
||||
meta: Some(json!(RequestPluginInstallsMeta {
|
||||
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
|
||||
persist: Some(REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE),
|
||||
suggest_type: DiscoverableToolAction::Install,
|
||||
entries: None,
|
||||
categories: Some(vec![RequestPluginInstallCategoryMeta {
|
||||
title: "Calendar",
|
||||
entries: vec![RequestPluginInstallEntryMeta {
|
||||
tool_id: "connector_2128aebfecb84f64a069897515042a44",
|
||||
tool_name: "Google Calendar",
|
||||
tool_type: DiscoverableToolType::Connector,
|
||||
description: Some("Plan events and schedules."),
|
||||
install_url: Some(
|
||||
"https://chatgpt.com/apps/google-calendar/connector_2128aebfecb84f64a069897515042a44"
|
||||
),
|
||||
remote_plugin_id: None,
|
||||
app_connector_ids: None,
|
||||
}],
|
||||
}]),
|
||||
})),
|
||||
message: "Choose integrations".to_string(),
|
||||
requested_schema: McpElicitationSchema {
|
||||
schema_uri: None,
|
||||
type_: McpElicitationObjectType::Object,
|
||||
properties: BTreeMap::new(),
|
||||
required: 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,
|
||||
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,
|
||||
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,
|
||||
));
|
||||
}
|
||||
16
codex-rs/ext/plugin-installs/src/lib.rs
Normal file
16
codex-rs/ext/plugin-installs/src/lib.rs
Normal file
@@ -0,0 +1,16 @@
|
||||
mod domain;
|
||||
|
||||
pub const REQUEST_PLUGIN_INSTALLS_TOOL_NAME: &str = "request_plugin_installs";
|
||||
|
||||
pub use domain::REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE;
|
||||
pub use domain::REQUEST_PLUGIN_INSTALL_PERSIST_KEY;
|
||||
pub use domain::RequestPluginInstallEntryResult;
|
||||
pub use domain::RequestPluginInstallInstalledEntry;
|
||||
pub use domain::RequestPluginInstallPickerCategory;
|
||||
pub use domain::RequestPluginInstallPickerEntry;
|
||||
pub use domain::RequestPluginInstallResolvedPickerEntry;
|
||||
pub use domain::RequestPluginInstallsArgs;
|
||||
pub use domain::RequestPluginInstallsResult;
|
||||
pub use domain::all_requested_connectors_picked_up;
|
||||
pub use domain::build_request_plugin_installs_elicitation_request;
|
||||
pub use domain::verified_connector_install_completed;
|
||||
Reference in New Issue
Block a user