This commit is contained in:
Matthew Zeng
2026-01-22 17:51:29 -08:00
parent 2695137710
commit 81329e27d4
8 changed files with 72 additions and 110 deletions
@@ -13,7 +13,6 @@ use codex_app_server_protocol::AccountLoginCompletedNotification;
use codex_app_server_protocol::AccountUpdatedNotification;
use codex_app_server_protocol::AddConversationListenerParams;
use codex_app_server_protocol::AddConversationSubscriptionResponse;
use codex_app_server_protocol::AppInfo as ApiAppInfo;
use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::ArchiveConversationParams;
@@ -3486,18 +3485,7 @@ impl CodexMessageProcessor {
}
let end = start.saturating_add(effective_limit).min(total);
let data = connectors[start..end]
.iter()
.cloned()
.map(|connector| ApiAppInfo {
id: connector.connector_id,
name: connector.connector_name,
description: connector.connector_description,
logo_url: connector.logo_url,
install_url: connector.install_url,
is_accessible: connector.is_accessible,
})
.collect();
let data = connectors[start..end].to_vec();
let next_cursor = if end < total {
Some(end.to_string())
+17 -18
View File
@@ -20,7 +20,6 @@ use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::JSONRPCResponse;
use codex_app_server_protocol::RequestId;
use codex_core::auth::AuthCredentialsStoreMode;
use codex_core::connectors::ConnectorInfo;
use pretty_assertions::assert_eq;
use rmcp::handler::server::ServerHandler;
use rmcp::model::JsonObject;
@@ -71,18 +70,18 @@ async fn list_apps_returns_empty_when_connectors_disabled() -> Result<()> {
#[tokio::test]
async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
let connectors = vec![
ConnectorInfo {
connector_id: "alpha".to_string(),
connector_name: "Alpha".to_string(),
connector_description: Some("Alpha connector".to_string()),
AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: Some("https://example.com/alpha.png".to_string()),
install_url: None,
is_accessible: false,
},
ConnectorInfo {
connector_id: "beta".to_string(),
connector_name: "beta".to_string(),
connector_description: None,
AppInfo {
id: "beta".to_string(),
name: "beta".to_string(),
description: None,
logo_url: None,
install_url: None,
is_accessible: false,
@@ -150,18 +149,18 @@ async fn list_apps_returns_connectors_with_accessible_flags() -> Result<()> {
#[tokio::test]
async fn list_apps_paginates_results() -> Result<()> {
let connectors = vec![
ConnectorInfo {
connector_id: "alpha".to_string(),
connector_name: "Alpha".to_string(),
connector_description: Some("Alpha connector".to_string()),
AppInfo {
id: "alpha".to_string(),
name: "Alpha".to_string(),
description: Some("Alpha connector".to_string()),
logo_url: None,
install_url: None,
is_accessible: false,
},
ConnectorInfo {
connector_id: "beta".to_string(),
connector_name: "beta".to_string(),
connector_description: None,
AppInfo {
id: "beta".to_string(),
name: "beta".to_string(),
description: None,
logo_url: None,
install_url: None,
is_accessible: false,
@@ -289,7 +288,7 @@ impl ServerHandler for AppListMcpServer {
}
async fn start_apps_server(
connectors: Vec<ConnectorInfo>,
connectors: Vec<AppInfo>,
tools: Vec<Tool>,
) -> Result<(String, JoinHandle<()>)> {
let state = AppsServerState {
+10 -12
View File
@@ -7,7 +7,7 @@ use crate::chatgpt_client::chatgpt_post_request;
use crate::chatgpt_token::get_chatgpt_token_data;
use crate::chatgpt_token::init_chatgpt_token_from_auth;
pub use codex_core::connectors::ConnectorInfo;
pub use codex_core::connectors::AppInfo;
pub use codex_core::connectors::connector_display_label;
use codex_core::connectors::connector_install_url;
pub use codex_core::connectors::list_accessible_connectors_from_mcp_tools;
@@ -33,10 +33,10 @@ enum PrincipalType {
#[derive(Debug, Deserialize)]
struct ListConnectorsResponse {
connectors: Vec<ConnectorInfo>,
connectors: Vec<AppInfo>,
}
pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<ConnectorInfo>> {
pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
if !config.features.enabled(Feature::Connectors) {
return Ok(Vec::new());
}
@@ -49,7 +49,7 @@ pub async fn list_connectors(config: &Config) -> anyhow::Result<Vec<ConnectorInf
Ok(merge_connectors(connectors, accessible))
}
pub async fn list_all_connectors(config: &Config) -> anyhow::Result<Vec<ConnectorInfo>> {
pub async fn list_all_connectors(config: &Config) -> anyhow::Result<Vec<AppInfo>> {
if !config.features.enabled(Feature::Connectors) {
return Ok(Vec::new());
}
@@ -91,19 +91,17 @@ pub async fn list_all_connectors(config: &Config) -> anyhow::Result<Vec<Connecto
for connector in &mut connectors {
let install_url = match connector.install_url.take() {
Some(install_url) => install_url,
None => connector_install_url(&connector.connector_name, &connector.connector_id),
None => connector_install_url(&connector.name, &connector.id),
};
connector.connector_name =
normalize_connector_name(&connector.connector_name, &connector.connector_id);
connector.connector_description =
normalize_connector_value(connector.connector_description.as_deref());
connector.name = normalize_connector_name(&connector.name, &connector.id);
connector.description = normalize_connector_value(connector.description.as_deref());
connector.install_url = Some(install_url);
connector.is_accessible = false;
}
connectors.sort_by(|left, right| {
left.connector_name
.cmp(&right.connector_name)
.then_with(|| left.connector_id.cmp(&right.connector_id))
left.name
.cmp(&right.name)
.then_with(|| left.id.cmp(&right.id))
});
Ok(connectors)
}
+5 -5
View File
@@ -3024,9 +3024,9 @@ async fn run_auto_compact(sess: &Arc<Session>, turn_context: &Arc<TurnContext>)
}
fn filter_connectors_for_input(
connectors: Vec<connectors::ConnectorInfo>,
connectors: Vec<connectors::AppInfo>,
input: &[ResponseItem],
) -> Vec<connectors::ConnectorInfo> {
) -> Vec<connectors::AppInfo> {
let user_messages = collect_user_messages(input);
if user_messages.is_empty() {
return Vec::new();
@@ -3039,7 +3039,7 @@ fn filter_connectors_for_input(
}
fn connector_inserted_in_messages(
connector: &connectors::ConnectorInfo,
connector: &connectors::AppInfo,
user_messages: &[String],
) -> bool {
let label = connectors::connector_display_label(connector);
@@ -3053,11 +3053,11 @@ fn connector_inserted_in_messages(
fn filter_codex_apps_mcp_tools(
mut mcp_tools: HashMap<String, crate::mcp_connection_manager::ToolInfo>,
connectors: &[connectors::ConnectorInfo],
connectors: &[connectors::AppInfo],
) -> HashMap<String, crate::mcp_connection_manager::ToolInfo> {
let allowed: HashSet<&str> = connectors
.iter()
.map(|connector| connector.connector_id.as_str())
.map(|connector| connector.id.as_str())
.collect();
mcp_tools.retain(|_, tool| {
+27 -50
View File
@@ -3,9 +3,8 @@ use std::env;
use std::path::PathBuf;
use async_channel::unbounded;
pub use codex_app_server_protocol::AppInfo;
use codex_protocol::protocol::SandboxPolicy;
use serde::Deserialize;
use serde::Serialize;
use tokio_util::sync::CancellationToken;
use crate::AuthManager;
@@ -17,25 +16,9 @@ use crate::mcp::auth::compute_auth_statuses;
use crate::mcp::with_codex_apps_mcp;
use crate::mcp_connection_manager::McpConnectionManager;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectorInfo {
#[serde(rename = "id")]
pub connector_id: String,
#[serde(rename = "name")]
pub connector_name: String,
#[serde(default, rename = "description")]
pub connector_description: Option<String>,
#[serde(default, rename = "logo_url")]
pub logo_url: Option<String>,
#[serde(default, rename = "install_url")]
pub install_url: Option<String>,
#[serde(default)]
pub is_accessible: bool,
}
pub async fn list_accessible_connectors_from_mcp_tools(
config: &Config,
) -> anyhow::Result<Vec<ConnectorInfo>> {
) -> anyhow::Result<Vec<AppInfo>> {
if !config.features.enabled(Feature::Connectors) {
return Ok(Vec::new());
}
@@ -86,13 +69,13 @@ fn auth_manager_from_config(config: &Config) -> std::sync::Arc<AuthManager> {
)
}
pub fn connector_display_label(connector: &ConnectorInfo) -> String {
format_connector_label(&connector.connector_name, &connector.connector_id)
pub fn connector_display_label(connector: &AppInfo) -> String {
format_connector_label(&connector.name, &connector.id)
}
pub(crate) fn accessible_connectors_from_mcp_tools(
mcp_tools: &HashMap<String, crate::mcp_connection_manager::ToolInfo>,
) -> Vec<ConnectorInfo> {
) -> Vec<AppInfo> {
let tools = mcp_tools.values().filter_map(|tool| {
if tool.server_name != CODEX_APPS_MCP_SERVER_NAME {
return None;
@@ -105,30 +88,27 @@ pub(crate) fn accessible_connectors_from_mcp_tools(
}
pub fn merge_connectors(
connectors: Vec<ConnectorInfo>,
accessible_connectors: Vec<ConnectorInfo>,
) -> Vec<ConnectorInfo> {
let mut merged: HashMap<String, ConnectorInfo> = connectors
connectors: Vec<AppInfo>,
accessible_connectors: Vec<AppInfo>,
) -> Vec<AppInfo> {
let mut merged: HashMap<String, AppInfo> = connectors
.into_iter()
.map(|mut connector| {
connector.is_accessible = false;
(connector.connector_id.clone(), connector)
(connector.id.clone(), connector)
})
.collect();
for mut connector in accessible_connectors {
connector.is_accessible = true;
let connector_id = connector.connector_id.clone();
let connector_id = connector.id.clone();
if let Some(existing) = merged.get_mut(&connector_id) {
existing.is_accessible = true;
if existing.connector_name == existing.connector_id
&& connector.connector_name != connector.connector_id
{
existing.connector_name = connector.connector_name;
if existing.name == existing.id && connector.name != connector.id {
existing.name = connector.name;
}
if existing.connector_description.is_none() && connector.connector_description.is_some()
{
existing.connector_description = connector.connector_description;
if existing.description.is_none() && connector.description.is_some() {
existing.description = connector.description;
}
if existing.logo_url.is_none() && connector.logo_url.is_some() {
existing.logo_url = connector.logo_url;
@@ -141,23 +121,20 @@ pub fn merge_connectors(
let mut merged = merged.into_values().collect::<Vec<_>>();
for connector in &mut merged {
if connector.install_url.is_none() {
connector.install_url = Some(connector_install_url(
&connector.connector_name,
&connector.connector_id,
));
connector.install_url = Some(connector_install_url(&connector.name, &connector.id));
}
}
merged.sort_by(|left, right| {
right
.is_accessible
.cmp(&left.is_accessible)
.then_with(|| left.connector_name.cmp(&right.connector_name))
.then_with(|| left.connector_id.cmp(&right.connector_id))
.then_with(|| left.name.cmp(&right.name))
.then_with(|| left.id.cmp(&right.id))
});
merged
}
fn collect_accessible_connectors<I>(tools: I) -> Vec<ConnectorInfo>
fn collect_accessible_connectors<I>(tools: I) -> Vec<AppInfo>
where
I: IntoIterator<Item = (String, Option<String>)>,
{
@@ -172,14 +149,14 @@ where
connectors.insert(connector_id, connector_name);
}
}
let mut accessible: Vec<ConnectorInfo> = connectors
let mut accessible: Vec<AppInfo> = connectors
.into_iter()
.map(|(connector_id, connector_name)| ConnectorInfo {
install_url: Some(connector_install_url(&connector_name, &connector_id)),
connector_id,
connector_name,
connector_description: None,
.map(|(connector_id, connector_name)| AppInfo {
id: connector_id.clone(),
name: connector_name.clone(),
description: None,
logo_url: None,
install_url: Some(connector_install_url(&connector_name, &connector_id)),
is_accessible: true,
})
.collect();
@@ -187,8 +164,8 @@ where
right
.is_accessible
.cmp(&left.is_accessible)
.then_with(|| left.connector_name.cmp(&right.connector_name))
.then_with(|| left.connector_id.cmp(&right.connector_id))
.then_with(|| left.name.cmp(&right.name))
.then_with(|| left.id.cmp(&right.id))
});
accessible
}
+2 -2
View File
@@ -10,7 +10,7 @@
use std::path::PathBuf;
use codex_chatgpt::connectors::ConnectorInfo;
use codex_chatgpt::connectors::AppInfo;
use codex_common::approval_presets::ApprovalPreset;
use codex_core::protocol::Event;
use codex_core::protocol::RateLimitSnapshot;
@@ -42,7 +42,7 @@ pub(crate) enum WindowsSandboxFallbackReason {
#[derive(Debug, Clone)]
pub(crate) struct ConnectorsSnapshot {
pub(crate) connectors: Vec<ConnectorInfo>,
pub(crate) connectors: Vec<AppInfo>,
}
#[allow(clippy::large_enum_variant)]
@@ -141,7 +141,7 @@ use crate::clipboard_paste::pasted_image_format;
use crate::history_cell;
use crate::ui_consts::LIVE_PREFIX_COLS;
use codex_chatgpt::connectors;
use codex_chatgpt::connectors::ConnectorInfo;
use codex_chatgpt::connectors::AppInfo;
use codex_core::skills::model::SkillMetadata;
use codex_file_search::FileMatch;
use std::cell::RefCell;
@@ -2496,7 +2496,7 @@ impl ChatComposer {
}
let display_name = connectors::connector_display_label(connector);
let description = Some(Self::connector_brief_description(connector));
let search_terms = vec![display_name.clone(), connector.connector_id.clone()];
let search_terms = vec![display_name.clone(), connector.id.clone()];
mentions.push(MentionItem {
display_name: display_name.clone(),
description,
@@ -2509,7 +2509,7 @@ impl ChatComposer {
mentions
}
fn connector_brief_description(connector: &ConnectorInfo) -> String {
fn connector_brief_description(connector: &AppInfo) -> String {
let status_label = if connector.is_accessible {
"Connected"
} else {
@@ -2521,9 +2521,9 @@ impl ChatComposer {
}
}
fn connector_description(connector: &ConnectorInfo) -> Option<String> {
fn connector_description(connector: &AppInfo) -> Option<String> {
connector
.connector_description
.description
.as_deref()
.map(str::trim)
.filter(|description| !description.is_empty())
+5 -5
View File
@@ -4721,13 +4721,13 @@ impl ChatWidget {
self.request_redraw();
}
fn open_connectors_popup(&mut self, connectors: &[connectors::ConnectorInfo]) {
fn open_connectors_popup(&mut self, connectors: &[connectors::AppInfo]) {
let mut items: Vec<SelectionItem> = Vec::with_capacity(connectors.len());
for connector in connectors {
let connector_label = connectors::connector_display_label(connector);
let connector_title = connector_label.clone();
let description = Self::connector_brief_description(connector);
let search_value = format!("{connector_label} {}", connector.connector_id);
let search_value = format!("{connector_label} {}", connector.id);
let mut item = SelectionItem {
name: connector_label,
description: Some(description),
@@ -4793,7 +4793,7 @@ impl ChatWidget {
])
}
fn connector_brief_description(connector: &connectors::ConnectorInfo) -> String {
fn connector_brief_description(connector: &connectors::AppInfo) -> String {
let status_label = if connector.is_accessible {
"Connected"
} else {
@@ -4805,9 +4805,9 @@ impl ChatWidget {
}
}
fn connector_description(connector: &connectors::ConnectorInfo) -> Option<String> {
fn connector_description(connector: &connectors::AppInfo) -> Option<String> {
connector
.connector_description
.description
.as_deref()
.map(str::trim)
.filter(|description| !description.is_empty())