diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock index 1e4c204a0..3324fbd32 100644 --- a/codex-rs/Cargo.lock +++ b/codex-rs/Cargo.lock @@ -1976,6 +1976,7 @@ dependencies = [ "codex-chatgpt", "codex-cloud-config", "codex-config", + "codex-connectors", "codex-core", "codex-core-plugins", "codex-exec-server", @@ -2293,7 +2294,6 @@ version = "0.0.0" dependencies = [ "anyhow", "clap", - "codex-app-server-protocol", "codex-connectors", "codex-core", "codex-git-utils", @@ -2583,7 +2583,6 @@ name = "codex-connectors" version = "0.0.0" dependencies = [ "anyhow", - "codex-app-server-protocol", "codex-config", "pretty_assertions", "serde", @@ -4016,6 +4015,7 @@ version = "0.0.0" dependencies = [ "codex-app-server-protocol", "codex-code-mode", + "codex-connectors", "codex-features", "codex-file-system", "codex-protocol", diff --git a/codex-rs/app-server/Cargo.toml b/codex-rs/app-server/Cargo.toml index 3d08059a6..bde8048e7 100644 --- a/codex-rs/app-server/Cargo.toml +++ b/codex-rs/app-server/Cargo.toml @@ -33,6 +33,7 @@ codex-analytics = { workspace = true } codex-arg0 = { workspace = true } codex-cloud-config = { workspace = true } codex-config = { workspace = true } +codex-connectors = { workspace = true } codex-core = { workspace = true } codex-core-plugins = { workspace = true } codex-home = { workspace = true } diff --git a/codex-rs/app-server/src/app_info.rs b/codex-rs/app-server/src/app_info.rs new file mode 100644 index 000000000..5c8686172 --- /dev/null +++ b/codex-rs/app-server/src/app_info.rs @@ -0,0 +1,118 @@ +use codex_app_server_protocol::AppBranding as ApiAppBranding; +use codex_app_server_protocol::AppInfo as ApiAppInfo; +use codex_app_server_protocol::AppMetadata as ApiAppMetadata; +use codex_app_server_protocol::AppReview as ApiAppReview; +use codex_app_server_protocol::AppScreenshot as ApiAppScreenshot; +use codex_connectors::AppBranding; +use codex_connectors::AppInfo; +use codex_connectors::AppMetadata; +use codex_connectors::AppReview; +use codex_connectors::AppScreenshot; + +/// Converts connector-domain app metadata owned by `codex-connectors` into the app-server wire +/// type owned by `codex-app-server-protocol`. +/// +/// The types stay separate so app-server protocol ownership does not leak into the connector +/// domain crate. Because this crate owns neither type, Rust's orphan rules require an explicit +/// conversion function instead of a `From` implementation. +pub(crate) fn app_info_to_api(app: AppInfo) -> ApiAppInfo { + let AppInfo { + id, + name, + description, + logo_url, + logo_url_dark, + distribution_channel, + branding, + app_metadata, + labels, + install_url, + is_accessible, + is_enabled, + plugin_display_names, + } = app; + ApiAppInfo { + id, + name, + description, + logo_url, + logo_url_dark, + distribution_channel, + branding: branding.map(app_branding_to_api), + app_metadata: app_metadata.map(app_metadata_to_api), + labels, + install_url, + is_accessible, + is_enabled, + plugin_display_names, + } +} + +fn app_branding_to_api(branding: AppBranding) -> ApiAppBranding { + let AppBranding { + category, + developer, + website, + privacy_policy, + terms_of_service, + is_discoverable_app, + } = branding; + ApiAppBranding { + category, + developer, + website, + privacy_policy, + terms_of_service, + is_discoverable_app, + } +} + +fn app_review_to_api(review: AppReview) -> ApiAppReview { + let AppReview { status } = review; + ApiAppReview { status } +} + +fn app_screenshot_to_api(screenshot: AppScreenshot) -> ApiAppScreenshot { + let AppScreenshot { + url, + file_id, + user_prompt, + } = screenshot; + ApiAppScreenshot { + url, + file_id, + user_prompt, + } +} + +fn app_metadata_to_api(metadata: AppMetadata) -> ApiAppMetadata { + let AppMetadata { + review, + categories, + sub_categories, + seo_description, + screenshots, + developer, + version, + version_id, + version_notes, + first_party_type, + first_party_requires_install, + show_in_composer_when_unlinked, + } = metadata; + ApiAppMetadata { + review: review.map(app_review_to_api), + categories, + sub_categories, + seo_description, + screenshots: screenshots + .map(|screenshots| screenshots.into_iter().map(app_screenshot_to_api).collect()), + developer, + version, + version_id, + version_notes, + first_party_type, + first_party_requires_install, + show_in_composer_when_unlinked, + } +} diff --git a/codex-rs/app-server/src/lib.rs b/codex-rs/app-server/src/lib.rs index 40cda6d42..beed35848 100644 --- a/codex-rs/app-server/src/lib.rs +++ b/codex-rs/app-server/src/lib.rs @@ -80,6 +80,7 @@ use tracing_subscriber::util::SubscriberInitExt; const SQLITE_RECOVERY_CONFIG_WARNING_SUMMARY: &str = "Codex rebuilt its local database."; mod analytics_utils; +mod app_info; mod app_server_tracing; mod attestation; mod auth_mode; diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index 509cbeb2e..500ecc473 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -29,7 +29,6 @@ use codex_app_server_protocol::AddCreditsNudgeCreditType; use codex_app_server_protocol::AddCreditsNudgeEmailStatus; use codex_app_server_protocol::AdditionalContextEntry; use codex_app_server_protocol::AdditionalContextKind; -use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AppListUpdatedNotification; use codex_app_server_protocol::AppSummary; use codex_app_server_protocol::AppTemplateSummary; @@ -302,6 +301,7 @@ use codex_config::CloudConfigBundleLoadErrorCode; use codex_config::ConfigLayerStack; use codex_config::loader::project_trust_key; use codex_config::types::McpServerTransportConfig; +use codex_connectors::AppInfo; use codex_core::CodexThread; use codex_core::CodexThreadSettingsOverrides; use codex_core::ForkSnapshot; diff --git a/codex-rs/app-server/src/request_processors/apps_processor.rs b/codex-rs/app-server/src/request_processors/apps_processor.rs index a7097052b..ce25387da 100644 --- a/codex-rs/app-server/src/request_processors/apps_processor.rs +++ b/codex-rs/app-server/src/request_processors/apps_processor.rs @@ -1,4 +1,5 @@ use super::*; +use crate::app_info::app_info_to_api; pub(crate) struct AppsRequestProcessor { auth_manager: Arc, @@ -396,7 +397,11 @@ fn paginate_apps( let effective_limit = limit.unwrap_or(total as u32).max(1) as usize; let end = start.saturating_add(effective_limit).min(total); - let data = connectors[start..end].to_vec(); + let data = connectors[start..end] + .iter() + .cloned() + .map(app_info_to_api) + .collect(); let next_cursor = if end < total { Some(end.to_string()) } else { @@ -410,6 +415,7 @@ async fn send_app_list_updated_notification( outgoing: &Arc, data: Vec, ) { + let data = data.into_iter().map(app_info_to_api).collect(); outgoing .send_server_notification(ServerNotification::AppListUpdated( AppListUpdatedNotification { data }, diff --git a/codex-rs/chatgpt/Cargo.toml b/codex-rs/chatgpt/Cargo.toml index c2cbb0283..3cb15ad1a 100644 --- a/codex-rs/chatgpt/Cargo.toml +++ b/codex-rs/chatgpt/Cargo.toml @@ -10,7 +10,6 @@ workspace = true [dependencies] anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } -codex-app-server-protocol = { workspace = true } codex-connectors = { workspace = true } codex-core = { workspace = true } codex-git-utils = { workspace = true } diff --git a/codex-rs/chatgpt/src/connectors.rs b/codex-rs/chatgpt/src/connectors.rs index fa1a894cc..7089acda5 100644 --- a/codex-rs/chatgpt/src/connectors.rs +++ b/codex-rs/chatgpt/src/connectors.rs @@ -4,7 +4,7 @@ use std::time::Duration; use crate::chatgpt_client::chatgpt_get_request_with_timeout; -use codex_app_server_protocol::AppInfo; +use codex_connectors::AppInfo; use codex_connectors::ConnectorDirectoryCacheContext; use codex_connectors::ConnectorDirectoryCacheKey; use codex_connectors::DirectoryListResponse; diff --git a/codex-rs/connectors/Cargo.toml b/codex-rs/connectors/Cargo.toml index 417b3e48a..c010effe8 100644 --- a/codex-rs/connectors/Cargo.toml +++ b/codex-rs/connectors/Cargo.toml @@ -9,7 +9,6 @@ workspace = true [dependencies] anyhow = { workspace = true } -codex-app-server-protocol = { workspace = true } codex-config = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/codex-rs/connectors/src/accessible.rs b/codex-rs/connectors/src/accessible.rs index c44f8d8a3..96eb38550 100644 --- a/codex-rs/connectors/src/accessible.rs +++ b/codex-rs/connectors/src/accessible.rs @@ -1,9 +1,9 @@ use std::collections::BTreeSet; use std::collections::HashMap; +use crate::AppInfo; use crate::metadata::connector_install_url; use crate::normalize_connector_value; -use codex_app_server_protocol::AppInfo; pub struct AccessibleConnectorTool { pub connector_id: String, diff --git a/codex-rs/connectors/src/app_info.rs b/codex-rs/connectors/src/app_info.rs new file mode 100644 index 000000000..d31204d7a --- /dev/null +++ b/codex-rs/connectors/src/app_info.rs @@ -0,0 +1,110 @@ +//! Connector-domain app metadata used by directory discovery, caching, and tool selection. +//! +//! The Serde implementations decode connector-directory response metadata and persist normalized +//! app information in the connector-directory disk cache. They do not define the app-server wire +//! format; `codex-app-server-protocol` owns separate API types for that boundary. + +use serde::Deserialize; +use serde::Serialize; +use std::collections::HashMap; + +/// Branding supplied by the connector directory for an app. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AppBranding { + pub category: Option, + pub developer: Option, + pub website: Option, + pub privacy_policy: Option, + pub terms_of_service: Option, + pub is_discoverable_app: bool, +} + +/// Review state supplied by the connector directory for an app. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AppReview { + pub status: String, +} + +/// Screenshot metadata supplied by the connector directory for an app. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AppScreenshot { + pub url: Option, + #[serde(alias = "file_id")] + pub file_id: Option, + #[serde(alias = "user_prompt")] + pub user_prompt: String, +} + +/// Extended metadata supplied by the connector directory for an app. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AppMetadata { + pub review: Option, + pub categories: Option>, + pub sub_categories: Option>, + pub seo_description: Option, + pub screenshots: Option>, + pub developer: Option, + pub version: Option, + pub version_id: Option, + pub version_notes: Option, + pub first_party_type: Option, + pub first_party_requires_install: Option, + pub show_in_composer_when_unlinked: Option, +} + +/// Connector metadata used by connector discovery, caching, and tool selection. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct AppInfo { + pub id: String, + pub name: String, + pub description: Option, + pub logo_url: Option, + pub logo_url_dark: Option, + pub distribution_channel: Option, + pub branding: Option, + pub app_metadata: Option, + pub labels: Option>, + pub install_url: Option, + #[serde(default)] + pub is_accessible: bool, + #[serde(default = "default_enabled")] + pub is_enabled: bool, + #[serde(default)] + pub plugin_display_names: Vec, +} + +impl AppInfo { + pub fn category(&self) -> Option { + self.branding + .as_ref() + .and_then(|branding| non_empty_category(branding.category.as_deref())) + .or_else(|| { + self.app_metadata + .as_ref() + .and_then(|metadata| metadata.categories.as_ref()) + .and_then(|categories| { + categories + .iter() + .find_map(|category| non_empty_category(Some(category.as_str()))) + }) + }) + } +} + +const fn default_enabled() -> bool { + true +} + +fn non_empty_category(category: Option<&str>) -> Option { + let category = category?.trim(); + if category.is_empty() { + None + } else { + Some(category.to_string()) + } +} diff --git a/codex-rs/connectors/src/directory_cache.rs b/codex-rs/connectors/src/directory_cache.rs index 581193b87..beaa21e14 100644 --- a/codex-rs/connectors/src/directory_cache.rs +++ b/codex-rs/connectors/src/directory_cache.rs @@ -1,12 +1,12 @@ use std::path::PathBuf; -use codex_app_server_protocol::AppInfo; use serde::Deserialize; use serde::Serialize; use sha1::Digest; use sha1::Sha1; use tracing::warn; +use crate::AppInfo; use crate::ConnectorDirectoryCacheKey; pub(crate) const CONNECTOR_DIRECTORY_DISK_CACHE_SCHEMA_VERSION: u8 = 1; diff --git a/codex-rs/connectors/src/filter.rs b/codex-rs/connectors/src/filter.rs index 6d60c89ad..2f876f91d 100644 --- a/codex-rs/connectors/src/filter.rs +++ b/codex-rs/connectors/src/filter.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use codex_app_server_protocol::AppInfo; +use crate::AppInfo; pub fn filter_tool_suggest_discoverable_connectors( directory_connectors: Vec, diff --git a/codex-rs/connectors/src/lib.rs b/codex-rs/connectors/src/lib.rs index a75120c0a..f2db9d0d2 100644 --- a/codex-rs/connectors/src/lib.rs +++ b/codex-rs/connectors/src/lib.rs @@ -5,19 +5,22 @@ use std::sync::Mutex as StdMutex; use std::time::Duration; use std::time::Instant; -use codex_app_server_protocol::AppBranding; -use codex_app_server_protocol::AppInfo; -use codex_app_server_protocol::AppMetadata; use serde::Deserialize; use serde::Serialize; pub mod accessible; +mod app_info; mod app_tool_policy; mod directory_cache; pub mod filter; pub mod merge; pub mod metadata; +pub use app_info::AppBranding; +pub use app_info::AppInfo; +pub use app_info::AppMetadata; +pub use app_info::AppReview; +pub use app_info::AppScreenshot; pub use app_tool_policy::AppToolPolicy; pub use app_tool_policy::AppToolPolicyEvaluator; pub use app_tool_policy::AppToolPolicyInput; diff --git a/codex-rs/connectors/src/merge.rs b/codex-rs/connectors/src/merge.rs index a8bee1873..cbaf0723c 100644 --- a/codex-rs/connectors/src/merge.rs +++ b/codex-rs/connectors/src/merge.rs @@ -1,9 +1,9 @@ use std::collections::HashMap; use std::collections::HashSet; +use crate::AppInfo; use crate::metadata::connector_install_url; use crate::metadata::sort_connectors_by_accessibility_and_name; -use codex_app_server_protocol::AppInfo; pub fn merge_connectors( connectors: Vec, diff --git a/codex-rs/connectors/src/metadata.rs b/codex-rs/connectors/src/metadata.rs index 0a7eebe4e..9deeabf04 100644 --- a/codex-rs/connectors/src/metadata.rs +++ b/codex-rs/connectors/src/metadata.rs @@ -1,4 +1,4 @@ -use codex_app_server_protocol::AppInfo; +use crate::AppInfo; pub fn connector_display_label(connector: &AppInfo) -> String { connector.name.clone() diff --git a/codex-rs/core/src/apps/render.rs b/codex-rs/core/src/apps/render.rs index 3793231e1..98d702249 100644 --- a/codex-rs/core/src/apps/render.rs +++ b/codex-rs/core/src/apps/render.rs @@ -1,6 +1,6 @@ +use crate::connectors::AppInfo; use crate::context::AppsInstructions; use crate::context::ContextualUserFragment; -use codex_app_server_protocol::AppInfo; use codex_protocol::protocol::APPS_INSTRUCTIONS_CLOSE_TAG; use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG; diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index 87c309716..79978da99 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -6,9 +6,9 @@ use std::time::Duration; use std::time::Instant; use async_channel::unbounded; -pub use codex_app_server_protocol::AppBranding; -pub use codex_app_server_protocol::AppInfo; -pub use codex_app_server_protocol::AppMetadata; +pub use codex_connectors::AppBranding; +pub use codex_connectors::AppInfo; +pub use codex_connectors::AppMetadata; use codex_connectors::ConnectorDirectoryCacheContext; use codex_connectors::ConnectorDirectoryCacheKey; use codex_connectors::app_is_enabled; diff --git a/codex-rs/core/src/context/apps_instructions.rs b/codex-rs/core/src/context/apps_instructions.rs index f88ee4967..761187c79 100644 --- a/codex-rs/core/src/context/apps_instructions.rs +++ b/codex-rs/core/src/context/apps_instructions.rs @@ -1,4 +1,4 @@ -use codex_app_server_protocol::AppInfo; +use crate::connectors::AppInfo; use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; use codex_protocol::protocol::APPS_INSTRUCTIONS_CLOSE_TAG; use codex_protocol::protocol::APPS_INSTRUCTIONS_OPEN_TAG; diff --git a/codex-rs/core/src/session/tests.rs b/codex-rs/core/src/session/tests.rs index 51f5694f8..cbc2128f5 100644 --- a/codex-rs/core/src/session/tests.rs +++ b/codex-rs/core/src/session/tests.rs @@ -66,6 +66,7 @@ use codex_protocol::request_permissions::RequestPermissionProfile; use codex_utils_path_uri::PathUri; use tracing::Span; +use crate::connectors::AppInfo; use crate::rollout::recorder::RolloutRecorder; use crate::state::ActiveTurn; use crate::state::TaskKind; @@ -83,7 +84,6 @@ use crate::tools::handlers::ShellCommandHandler; use crate::tools::registry::ToolExecutor; use crate::tools::router::ToolCallSource; use crate::turn_diff_tracker::TurnDiffTracker; -use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::McpElicitationSchema; use codex_config::config_toml::ConfigToml; use codex_config::config_toml::ProjectConfig; diff --git a/codex-rs/core/src/tools/handlers/request_plugin_install.rs b/codex-rs/core/src/tools/handlers/request_plugin_install.rs index ff7b421fd..b51345050 100644 --- a/codex-rs/core/src/tools/handlers/request_plugin_install.rs +++ b/codex-rs/core/src/tools/handlers/request_plugin_install.rs @@ -1,6 +1,6 @@ use std::collections::HashSet; -use codex_app_server_protocol::AppInfo; +use crate::connectors::AppInfo; use codex_config::types::ToolSuggestDisabledTool; use codex_core_plugins::remote::REMOTE_GLOBAL_MARKETPLACE_NAME; use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; diff --git a/codex-rs/tools/Cargo.toml b/codex-rs/tools/Cargo.toml index 7d06acb0c..f06e5f073 100644 --- a/codex-rs/tools/Cargo.toml +++ b/codex-rs/tools/Cargo.toml @@ -10,6 +10,7 @@ workspace = true [dependencies] codex-app-server-protocol = { workspace = true } codex-code-mode = { workspace = true } +codex-connectors = { workspace = true } codex-features = { workspace = true } codex-file-system = { workspace = true } codex-protocol = { workspace = true } diff --git a/codex-rs/tools/src/request_plugin_install.rs b/codex-rs/tools/src/request_plugin_install.rs index 7bc0f6d4c..c8c640606 100644 --- a/codex-rs/tools/src/request_plugin_install.rs +++ b/codex-rs/tools/src/request_plugin_install.rs @@ -1,10 +1,10 @@ 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 codex_connectors::AppInfo; use serde::Deserialize; use serde::Serialize; use serde_json::json; diff --git a/codex-rs/tools/src/tool_discovery.rs b/codex-rs/tools/src/tool_discovery.rs index 2584d7f17..9ac810644 100644 --- a/codex-rs/tools/src/tool_discovery.rs +++ b/codex-rs/tools/src/tool_discovery.rs @@ -1,4 +1,4 @@ -use codex_app_server_protocol::AppInfo; +use codex_connectors::AppInfo; use serde::Deserialize; use serde::Serialize; diff --git a/codex-rs/tools/src/tool_discovery_tests.rs b/codex-rs/tools/src/tool_discovery_tests.rs index 5a3310057..8e026dd30 100644 --- a/codex-rs/tools/src/tool_discovery_tests.rs +++ b/codex-rs/tools/src/tool_discovery_tests.rs @@ -1,5 +1,5 @@ use super::*; -use codex_app_server_protocol::AppInfo; +use codex_connectors::AppInfo; use pretty_assertions::assert_eq; use serde_json::json; diff --git a/codex-rs/tui/src/app/app_server_events.rs b/codex-rs/tui/src/app/app_server_events.rs index 567a4b26a..4bd794a55 100644 --- a/codex-rs/tui/src/app/app_server_events.rs +++ b/codex-rs/tui/src/app/app_server_events.rs @@ -7,6 +7,7 @@ use super::app_server_event_targets::server_request_thread_id; use crate::app_command::AppCommand; use crate::app_event::AppEvent; use crate::app_event::ConnectorsSnapshot; +use crate::app_info::app_info_from_api; use crate::app_server_session::AppServerSession; use crate::app_server_session::status_account_display_from_auth_mode; use codex_app_server_client::AppServerEvent; @@ -127,7 +128,12 @@ impl App { ServerNotification::AppListUpdated(notification) => { self.chat_widget.on_connectors_loaded( Ok(ConnectorsSnapshot { - connectors: notification.data.clone(), + connectors: notification + .data + .iter() + .cloned() + .map(app_info_from_api) + .collect(), }), /*is_final*/ false, ); diff --git a/codex-rs/tui/src/app/background_requests.rs b/codex-rs/tui/src/app/background_requests.rs index 4a0652166..9391f4b3e 100644 --- a/codex-rs/tui/src/app/background_requests.rs +++ b/codex-rs/tui/src/app/background_requests.rs @@ -7,6 +7,7 @@ use super::plugin_mentions::fetch_plugin_mentions; use super::*; use crate::app_event::ConnectorsSnapshot; +use crate::app_info::app_info_from_api; use crate::config_update::format_config_error; use codex_app_server_protocol::AppsListParams; use codex_app_server_protocol::AppsListResponse; @@ -870,7 +871,7 @@ pub(super) async fn fetch_connectors_list( .await .wrap_err("app/list failed in TUI")?; Ok(ConnectorsSnapshot { - connectors: response.data, + connectors: response.data.into_iter().map(app_info_from_api).collect(), }) } diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index 55d67f292..1c5c949eb 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -12,7 +12,6 @@ use std::path::PathBuf; use codex_app_server_protocol::AddCreditsNudgeCreditType; use codex_app_server_protocol::AddCreditsNudgeEmailStatus; -use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::ConsumeAccountRateLimitResetCreditResponse; use codex_app_server_protocol::GetAccountRateLimitsResponse; use codex_app_server_protocol::GetAccountTokenUsageResponse; @@ -29,6 +28,7 @@ use codex_app_server_protocol::PluginReadResponse; use codex_app_server_protocol::PluginUninstallResponse; use codex_app_server_protocol::SkillsListResponse; use codex_app_server_protocol::ThreadGoalStatus; +use codex_connectors::AppInfo; use codex_file_search::FileMatch; use codex_protocol::ThreadId; use codex_protocol::openai_models::ModelPreset; diff --git a/codex-rs/tui/src/app_info.rs b/codex-rs/tui/src/app_info.rs new file mode 100644 index 000000000..ff97bec98 --- /dev/null +++ b/codex-rs/tui/src/app_info.rs @@ -0,0 +1,122 @@ +use codex_app_server_protocol::AppBranding as ApiAppBranding; +use codex_app_server_protocol::AppInfo as ApiAppInfo; +use codex_app_server_protocol::AppMetadata as ApiAppMetadata; +use codex_app_server_protocol::AppReview as ApiAppReview; +use codex_app_server_protocol::AppScreenshot as ApiAppScreenshot; +use codex_connectors::AppBranding; +use codex_connectors::AppInfo; +use codex_connectors::AppMetadata; +use codex_connectors::AppReview; +use codex_connectors::AppScreenshot; + +/// Converts the app-server wire type owned by `codex-app-server-protocol` into connector-domain +/// app metadata owned by `codex-connectors`. +/// +/// The types stay separate so app-server protocol ownership does not leak into the connector +/// domain crate. Because this crate owns neither type, Rust's orphan rules require an explicit +/// conversion function instead of a `From` implementation. +pub(crate) fn app_info_from_api(app: ApiAppInfo) -> AppInfo { + let ApiAppInfo { + id, + name, + description, + logo_url, + logo_url_dark, + distribution_channel, + branding, + app_metadata, + labels, + install_url, + is_accessible, + is_enabled, + plugin_display_names, + } = app; + AppInfo { + id, + name, + description, + logo_url, + logo_url_dark, + distribution_channel, + branding: branding.map(app_branding_from_api), + app_metadata: app_metadata.map(app_metadata_from_api), + labels, + install_url, + is_accessible, + is_enabled, + plugin_display_names, + } +} + +fn app_branding_from_api(branding: ApiAppBranding) -> AppBranding { + let ApiAppBranding { + category, + developer, + website, + privacy_policy, + terms_of_service, + is_discoverable_app, + } = branding; + AppBranding { + category, + developer, + website, + privacy_policy, + terms_of_service, + is_discoverable_app, + } +} + +fn app_review_from_api(review: ApiAppReview) -> AppReview { + let ApiAppReview { status } = review; + AppReview { status } +} + +fn app_screenshot_from_api(screenshot: ApiAppScreenshot) -> AppScreenshot { + let ApiAppScreenshot { + url, + file_id, + user_prompt, + } = screenshot; + AppScreenshot { + url, + file_id, + user_prompt, + } +} + +fn app_metadata_from_api(metadata: ApiAppMetadata) -> AppMetadata { + let ApiAppMetadata { + review, + categories, + sub_categories, + seo_description, + screenshots, + developer, + version, + version_id, + version_notes, + first_party_type, + first_party_requires_install, + show_in_composer_when_unlinked, + } = metadata; + AppMetadata { + review: review.map(app_review_from_api), + categories, + sub_categories, + seo_description, + screenshots: screenshots.map(|screenshots| { + screenshots + .into_iter() + .map(app_screenshot_from_api) + .collect() + }), + developer, + version, + version_id, + version_notes, + first_party_type, + first_party_requires_install, + show_in_composer_when_unlinked, + } +} diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 2423647b3..53502b53e 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -241,7 +241,7 @@ use crate::history_cell; use crate::skills_helpers::skill_display_name; use crate::tui::FrameRequester; use crate::ui_consts::LIVE_PREFIX_COLS; -use codex_app_server_protocol::AppInfo; +use codex_connectors::AppInfo; #[cfg(test)] use codex_core_skills::model::SkillInterface; use codex_core_skills::model::SkillMetadata; diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 89408c4e5..75d319bae 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -80,7 +80,6 @@ use crate::token_usage::TokenUsageInfo; use crate::version::CODEX_CLI_VERSION; use codex_app_server_protocol::AddCreditsNudgeCreditType; use codex_app_server_protocol::AddCreditsNudgeEmailStatus; -use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::AppSummary; use codex_app_server_protocol::CodexErrorInfo as AppServerCodexErrorInfo; use codex_app_server_protocol::CollabAgentTool; @@ -123,6 +122,7 @@ use codex_config::ConstraintResult; use codex_config::types::ApprovalsReviewer; use codex_config::types::Notifications; use codex_config::types::WindowsSandboxModeToml; +use codex_connectors::AppInfo; use codex_core_skills::model::SkillMetadata; use codex_features::FEATURES; use codex_features::Feature; diff --git a/codex-rs/tui/src/chatwidget/skills.rs b/codex-rs/tui/src/chatwidget/skills.rs index 53b70be5c..134f44115 100644 --- a/codex-rs/tui/src/chatwidget/skills.rs +++ b/codex-rs/tui/src/chatwidget/skills.rs @@ -10,10 +10,10 @@ use crate::bottom_pane::SkillsToggleView; use crate::bottom_pane::popup_consts::standard_popup_hint_line; use crate::skills_helpers::skill_description; use crate::skills_helpers::skill_display_name; -use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::SkillMetadata as ProtocolSkillMetadata; use codex_app_server_protocol::SkillsListEntry; use codex_app_server_protocol::SkillsListResponse; +use codex_connectors::AppInfo; use codex_core_skills::model::SkillDependencies; use codex_core_skills::model::SkillInterface; use codex_core_skills::model::SkillMetadata; diff --git a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs index f97d93a0c..47bff114f 100644 --- a/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs +++ b/codex-rs/tui/src/chatwidget/tests/popups_and_settings.rs @@ -1,7 +1,6 @@ use super::*; use crate::app_event::ConnectorsSnapshot; use crate::chatwidget::connectors::ConnectorsCacheState; -use codex_app_server_protocol::AppInfo; use codex_app_server_protocol::HookErrorInfo; use codex_app_server_protocol::HooksListEntry; use codex_app_server_protocol::HooksListResponse; @@ -11,6 +10,7 @@ use codex_app_server_protocol::PluginAvailability; use codex_app_server_protocol::PluginShareContext; use codex_app_server_protocol::PluginShareDiscoverability; use codex_app_server_protocol::PluginSource; +use codex_connectors::AppInfo; use codex_features::Stage; use pretty_assertions::assert_eq; diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index 6219432b3..2eb48b21d 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -91,6 +91,7 @@ mod app_backtrack; mod app_command; mod app_event; mod app_event_sender; +mod app_info; mod app_server_approval_conversions; mod app_server_session; mod approval_events;