Collapse Codex Apps core surface
This commit is contained in:
parent
49dd8dc52b
commit
6d3c17cd0f
11 changed files with 44 additions and 601 deletions
|
|
@ -828,6 +828,13 @@ no longer merges plugin-provided apps; TUI plugin mentions no longer scan core
|
|||
plugins. Remaining `codex-plugin` references live in other Codex product crates
|
||||
or prompt/snapshot fixtures, not in the core runtime spine.
|
||||
|
||||
Codex Apps are now compatibility dust inside core. Core connector listing
|
||||
returns empty, app prompt instructions and app-rendering code were deleted,
|
||||
`TurnContext::apps_enabled()` is gone, refresh no longer auto-adds
|
||||
`codex_apps` as an MCP server, and the connector/app policy surface is only a
|
||||
tiny default stub so legacy callers compile. User-declared MCP servers remain
|
||||
the only MCP authority; Codex Apps are not part of the OpenAI auth/model spine.
|
||||
|
||||
The Phase 6 freshness slice is landed. It exposes read-only
|
||||
`thread/epiphany/freshness` from live retrieval summaries plus graph
|
||||
frontier/churn state and, for loaded threads, watcher-backed invalidation
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -293,5 +293,9 @@ current_status:
|
|||
modules/tests are gone, CLI marketplace commands are disabled compatibility
|
||||
shells, MCP CLI setup no longer creates plugin managers, ChatGPT connector
|
||||
listing no longer merges plugin-provided apps, and TUI plugin mentions no
|
||||
longer scan core plugins.
|
||||
longer scan core plugins. Codex Apps are now a tiny compatibility stub in
|
||||
core: connector listing returns empty, app prompt instructions/rendering are
|
||||
deleted, `TurnContext::apps_enabled()` is gone, refresh no longer auto-adds
|
||||
`codex_apps` as an MCP server, and only user-declared MCP servers remain as
|
||||
runtime MCP authority.
|
||||
next_action: Continue starving coordinator/runtime execution and operator surfaces off app-server JSON-RPC and out of `codex_message_processor.rs`; keep the vestigial Codex organ narrowed to subscription auth/model transport.
|
||||
|
|
|
|||
61
vendor/codex/codex-rs/core/src/apps/render.rs
vendored
61
vendor/codex/codex-rs/core/src/apps/render.rs
vendored
|
|
@ -1,61 +0,0 @@
|
|||
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;
|
||||
|
||||
pub(crate) fn render_apps_section(connectors: &[AppInfo]) -> Option<String> {
|
||||
AppsInstructions::from_connectors(connectors).map(|instructions| instructions.render())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn connector(id: &str, is_accessible: bool, is_enabled: bool) -> AppInfo {
|
||||
AppInfo {
|
||||
id: id.to_string(),
|
||||
name: id.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,
|
||||
is_enabled,
|
||||
plugin_display_names: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn omits_apps_section_without_accessible_and_enabled_apps() {
|
||||
assert_eq!(render_apps_section(&[]), None);
|
||||
assert_eq!(
|
||||
render_apps_section(&[connector(
|
||||
"calendar", /*is_accessible*/ true, /*is_enabled*/ false
|
||||
)]),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
render_apps_section(&[connector(
|
||||
"calendar", /*is_accessible*/ false, /*is_enabled*/ true
|
||||
)]),
|
||||
None
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn renders_apps_section_with_an_accessible_and_enabled_app() {
|
||||
let rendered = render_apps_section(&[connector(
|
||||
"calendar", /*is_accessible*/ true, /*is_enabled*/ true,
|
||||
)])
|
||||
.expect("expected apps section");
|
||||
|
||||
assert!(rendered.starts_with(APPS_INSTRUCTIONS_OPEN_TAG));
|
||||
assert!(rendered.contains("## Apps (Connectors)"));
|
||||
assert!(rendered.ends_with(APPS_INSTRUCTIONS_CLOSE_TAG));
|
||||
}
|
||||
}
|
||||
519
vendor/codex/codex-rs/core/src/connectors.rs
vendored
519
vendor/codex/codex-rs/core/src/connectors.rs
vendored
|
|
@ -1,40 +1,11 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
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;
|
||||
use codex_exec_server::EnvironmentManager;
|
||||
use codex_exec_server::EnvironmentManagerArgs;
|
||||
use codex_exec_server::ExecServerRuntimePaths;
|
||||
use codex_login::token_data::TokenData;
|
||||
use codex_protocol::protocol::SandboxPolicy;
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_mcp::ToolInfo;
|
||||
use rmcp::model::ToolAnnotations;
|
||||
use serde::Deserialize;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::config_loader::AppsRequirementsToml;
|
||||
use crate::session::INITIAL_SUBMIT_ID;
|
||||
use codex_config::types::AppToolApproval;
|
||||
use codex_config::types::AppsConfigToml;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::originator;
|
||||
use codex_mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use codex_mcp::McpConnectionManager;
|
||||
use codex_mcp::McpRuntimeEnvironment;
|
||||
use codex_mcp::ToolInfo;
|
||||
use codex_mcp::ToolPluginProvenance;
|
||||
use codex_mcp::codex_apps_tools_cache_key;
|
||||
use codex_mcp::compute_auth_statuses;
|
||||
use codex_mcp::with_codex_apps_mcp;
|
||||
|
||||
const CONNECTORS_READY_TIMEOUT_ON_EMPTY_TOOLS: Duration = Duration::from_secs(30);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct AppToolPolicy {
|
||||
|
|
@ -51,24 +22,6 @@ impl Default for AppToolPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
struct AccessibleConnectorsCacheKey {
|
||||
chatgpt_base_url: String,
|
||||
account_id: Option<String>,
|
||||
chatgpt_user_id: Option<String>,
|
||||
is_workspace_account: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CachedAccessibleConnectors {
|
||||
key: AccessibleConnectorsCacheKey,
|
||||
expires_at: Instant,
|
||||
connectors: Vec<AppInfo>,
|
||||
}
|
||||
|
||||
static ACCESSIBLE_CONNECTORS_CACHE: LazyLock<StdMutex<Option<CachedAccessibleConnectors>>> =
|
||||
LazyLock::new(|| StdMutex::new(None));
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AccessibleConnectorsStatus {
|
||||
pub connectors: Vec<AppInfo>,
|
||||
|
|
@ -76,469 +29,65 @@ pub struct AccessibleConnectorsStatus {
|
|||
}
|
||||
|
||||
pub async fn list_accessible_connectors_from_mcp_tools(
|
||||
config: &Config,
|
||||
_config: &Config,
|
||||
) -> anyhow::Result<Vec<AppInfo>> {
|
||||
Ok(
|
||||
list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
config, /*force_refetch*/ false,
|
||||
)
|
||||
.await?
|
||||
.connectors,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) async fn list_accessible_and_enabled_connectors_from_manager(
|
||||
mcp_connection_manager: &McpConnectionManager,
|
||||
config: &Config,
|
||||
) -> Vec<AppInfo> {
|
||||
with_app_enabled_state(
|
||||
accessible_connectors_from_mcp_tools(&mcp_connection_manager.list_all_tools().await),
|
||||
config,
|
||||
)
|
||||
.into_iter()
|
||||
.filter(|connector| connector.is_accessible && connector.is_enabled)
|
||||
.collect()
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
pub async fn list_cached_accessible_connectors_from_mcp_tools(
|
||||
config: &Config,
|
||||
_config: &Config,
|
||||
) -> Option<Vec<AppInfo>> {
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
|
||||
let auth = auth_manager.auth().await;
|
||||
if !config
|
||||
.features
|
||||
.apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::is_chatgpt_auth))
|
||||
{
|
||||
return Some(Vec::new());
|
||||
}
|
||||
let cache_key = accessible_connectors_cache_key(config, auth.as_ref());
|
||||
read_cached_accessible_connectors(&cache_key).map(|connectors| {
|
||||
codex_connectors::filter::filter_disallowed_connectors(
|
||||
connectors,
|
||||
originator().value.as_str(),
|
||||
)
|
||||
})
|
||||
Some(Vec::new())
|
||||
}
|
||||
|
||||
pub async fn list_accessible_connectors_from_mcp_tools_with_options(
|
||||
config: &Config,
|
||||
force_refetch: bool,
|
||||
_config: &Config,
|
||||
_force_refetch: bool,
|
||||
) -> anyhow::Result<Vec<AppInfo>> {
|
||||
Ok(
|
||||
list_accessible_connectors_from_mcp_tools_with_options_and_status(config, force_refetch)
|
||||
.await?
|
||||
.connectors,
|
||||
)
|
||||
Ok(Vec::new())
|
||||
}
|
||||
|
||||
pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
config: &Config,
|
||||
force_refetch: bool,
|
||||
_config: &Config,
|
||||
_force_refetch: bool,
|
||||
) -> anyhow::Result<AccessibleConnectorsStatus> {
|
||||
// TODO: Wire callers that already own an EnvironmentManager into
|
||||
// list_accessible_connectors_from_mcp_tools_with_environment_manager instead
|
||||
// of constructing a temporary manager here.
|
||||
let local_runtime_paths = ExecServerRuntimePaths::from_optional_paths(
|
||||
config.codex_self_exe.clone(),
|
||||
config.codex_linux_sandbox_exe.clone(),
|
||||
)?;
|
||||
let environment_manager =
|
||||
EnvironmentManager::new(EnvironmentManagerArgs::from_env(local_runtime_paths));
|
||||
list_accessible_connectors_from_mcp_tools_with_environment_manager(
|
||||
config,
|
||||
force_refetch,
|
||||
&environment_manager,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
|
||||
config: &Config,
|
||||
force_refetch: bool,
|
||||
environment_manager: &EnvironmentManager,
|
||||
) -> anyhow::Result<AccessibleConnectorsStatus> {
|
||||
let auth_manager =
|
||||
AuthManager::shared_from_config(config, /*enable_codex_api_key_env*/ false);
|
||||
let auth = auth_manager.auth().await;
|
||||
if !config
|
||||
.features
|
||||
.apps_enabled_for_auth(auth.as_ref().is_some_and(CodexAuth::is_chatgpt_auth))
|
||||
{
|
||||
return Ok(AccessibleConnectorsStatus {
|
||||
connectors: Vec::new(),
|
||||
codex_apps_ready: true,
|
||||
});
|
||||
}
|
||||
let cache_key = accessible_connectors_cache_key(config, auth.as_ref());
|
||||
if !force_refetch && let Some(cached_connectors) = read_cached_accessible_connectors(&cache_key)
|
||||
{
|
||||
let cached_connectors = codex_connectors::filter::filter_disallowed_connectors(
|
||||
cached_connectors,
|
||||
originator().value.as_str(),
|
||||
);
|
||||
return Ok(AccessibleConnectorsStatus {
|
||||
connectors: cached_connectors,
|
||||
codex_apps_ready: true,
|
||||
});
|
||||
}
|
||||
|
||||
let mcp_config = config.to_mcp_config();
|
||||
let mcp_servers = with_codex_apps_mcp(HashMap::new(), auth.as_ref(), &mcp_config);
|
||||
if mcp_servers.is_empty() {
|
||||
return Ok(AccessibleConnectorsStatus {
|
||||
connectors: Vec::new(),
|
||||
codex_apps_ready: true,
|
||||
});
|
||||
}
|
||||
|
||||
let auth_status_entries =
|
||||
compute_auth_statuses(mcp_servers.iter(), config.mcp_oauth_credentials_store_mode).await;
|
||||
|
||||
let (tx_event, rx_event) = unbounded();
|
||||
drop(rx_event);
|
||||
|
||||
let environment = environment_manager
|
||||
.default_environment()
|
||||
.unwrap_or_else(|| environment_manager.local_environment());
|
||||
|
||||
let (mcp_connection_manager, cancel_token) = McpConnectionManager::new(
|
||||
&mcp_servers,
|
||||
config.mcp_oauth_credentials_store_mode,
|
||||
auth_status_entries,
|
||||
&config.permissions.approval_policy,
|
||||
INITIAL_SUBMIT_ID.to_owned(),
|
||||
tx_event,
|
||||
SandboxPolicy::new_read_only_policy(),
|
||||
McpRuntimeEnvironment::new(environment, config.cwd.to_path_buf()),
|
||||
config.codex_home.to_path_buf(),
|
||||
codex_apps_tools_cache_key(auth.as_ref()),
|
||||
ToolPluginProvenance::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
let refreshed_tools = if force_refetch {
|
||||
match mcp_connection_manager
|
||||
.hard_refresh_codex_apps_tools_cache()
|
||||
.await
|
||||
{
|
||||
Ok(tools) => Some(tools),
|
||||
Err(err) => {
|
||||
warn!(
|
||||
"failed to force-refresh tools for MCP server '{CODEX_APPS_MCP_SERVER_NAME}', using cached/startup tools: {err:#}"
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let refreshed_tools_succeeded = refreshed_tools.is_some();
|
||||
|
||||
let mut tools = if let Some(tools) = refreshed_tools {
|
||||
tools
|
||||
} else {
|
||||
mcp_connection_manager.list_all_tools().await
|
||||
};
|
||||
let mut should_reload_tools = false;
|
||||
let codex_apps_ready = if refreshed_tools_succeeded {
|
||||
true
|
||||
} else if let Some(cfg) = mcp_servers.get(CODEX_APPS_MCP_SERVER_NAME) {
|
||||
let immediate_ready = mcp_connection_manager
|
||||
.wait_for_server_ready(CODEX_APPS_MCP_SERVER_NAME, Duration::ZERO)
|
||||
.await;
|
||||
if immediate_ready {
|
||||
true
|
||||
} else if tools.is_empty() {
|
||||
let timeout = cfg
|
||||
.startup_timeout_sec
|
||||
.unwrap_or(CONNECTORS_READY_TIMEOUT_ON_EMPTY_TOOLS);
|
||||
let ready = mcp_connection_manager
|
||||
.wait_for_server_ready(CODEX_APPS_MCP_SERVER_NAME, timeout)
|
||||
.await;
|
||||
should_reload_tools = ready;
|
||||
ready
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if should_reload_tools {
|
||||
tools = mcp_connection_manager.list_all_tools().await;
|
||||
}
|
||||
if codex_apps_ready {
|
||||
cancel_token.cancel();
|
||||
}
|
||||
|
||||
let accessible_connectors = codex_connectors::filter::filter_disallowed_connectors(
|
||||
accessible_connectors_from_mcp_tools(&tools),
|
||||
originator().value.as_str(),
|
||||
);
|
||||
if codex_apps_ready || !accessible_connectors.is_empty() {
|
||||
write_cached_accessible_connectors(cache_key, &accessible_connectors);
|
||||
}
|
||||
Ok(AccessibleConnectorsStatus {
|
||||
connectors: accessible_connectors,
|
||||
codex_apps_ready,
|
||||
connectors: Vec::new(),
|
||||
codex_apps_ready: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn accessible_connectors_cache_key(
|
||||
config: &Config,
|
||||
auth: Option<&CodexAuth>,
|
||||
) -> AccessibleConnectorsCacheKey {
|
||||
let token_data: Option<TokenData> = auth.and_then(|auth| auth.get_token_data().ok());
|
||||
let account_id = token_data
|
||||
.as_ref()
|
||||
.and_then(|token_data| token_data.account_id.clone());
|
||||
let chatgpt_user_id = token_data
|
||||
.as_ref()
|
||||
.and_then(|token_data| token_data.id_token.chatgpt_user_id.clone());
|
||||
let is_workspace_account = token_data
|
||||
.as_ref()
|
||||
.is_some_and(|token_data| token_data.id_token.is_workspace_account());
|
||||
AccessibleConnectorsCacheKey {
|
||||
chatgpt_base_url: config.chatgpt_base_url.clone(),
|
||||
account_id,
|
||||
chatgpt_user_id,
|
||||
is_workspace_account,
|
||||
}
|
||||
pub async fn list_accessible_connectors_from_mcp_tools_with_environment_manager(
|
||||
_config: &Config,
|
||||
_force_refetch: bool,
|
||||
_environment_manager: &codex_exec_server::EnvironmentManager,
|
||||
) -> anyhow::Result<AccessibleConnectorsStatus> {
|
||||
Ok(AccessibleConnectorsStatus {
|
||||
connectors: Vec::new(),
|
||||
codex_apps_ready: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_cached_accessible_connectors(
|
||||
cache_key: &AccessibleConnectorsCacheKey,
|
||||
) -> Option<Vec<AppInfo>> {
|
||||
let mut cache_guard = ACCESSIBLE_CONNECTORS_CACHE
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
let now = Instant::now();
|
||||
|
||||
if let Some(cached) = cache_guard.as_ref() {
|
||||
if now < cached.expires_at && cached.key == *cache_key {
|
||||
return Some(cached.connectors.clone());
|
||||
}
|
||||
if now >= cached.expires_at {
|
||||
*cache_guard = None;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn write_cached_accessible_connectors(
|
||||
cache_key: AccessibleConnectorsCacheKey,
|
||||
connectors: &[AppInfo],
|
||||
) {
|
||||
let mut cache_guard = ACCESSIBLE_CONNECTORS_CACHE
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
*cache_guard = Some(CachedAccessibleConnectors {
|
||||
key: cache_key,
|
||||
expires_at: Instant::now() + codex_connectors::CONNECTORS_CACHE_TTL,
|
||||
connectors: connectors.to_vec(),
|
||||
});
|
||||
}
|
||||
|
||||
pub(crate) fn accessible_connectors_from_mcp_tools(
|
||||
mcp_tools: &HashMap<String, ToolInfo>,
|
||||
pub fn accessible_connectors_from_mcp_tools(
|
||||
_mcp_tools: &std::collections::HashMap<String, ToolInfo>,
|
||||
) -> Vec<AppInfo> {
|
||||
// ToolInfo already carries plugin provenance, so app-level plugin sources
|
||||
// can be derived here instead of requiring a separate enrichment pass.
|
||||
let tools = mcp_tools.values().filter_map(|tool| {
|
||||
if tool.server_name != CODEX_APPS_MCP_SERVER_NAME {
|
||||
return None;
|
||||
}
|
||||
let connector_id = tool.connector_id.as_deref()?;
|
||||
Some(codex_connectors::accessible::AccessibleConnectorTool {
|
||||
connector_id: connector_id.to_string(),
|
||||
connector_name: tool.connector_name.clone(),
|
||||
connector_description: tool.connector_description.clone(),
|
||||
plugin_display_names: tool.plugin_display_names.clone(),
|
||||
})
|
||||
});
|
||||
codex_connectors::accessible::collect_accessible_connectors(tools)
|
||||
Vec::new()
|
||||
}
|
||||
|
||||
pub fn with_app_enabled_state(mut connectors: Vec<AppInfo>, config: &Config) -> Vec<AppInfo> {
|
||||
let user_apps_config = read_user_apps_config(config);
|
||||
let requirements_apps_config = config.config_layer_stack.requirements_toml().apps.as_ref();
|
||||
if user_apps_config.is_none() && requirements_apps_config.is_none() {
|
||||
return connectors;
|
||||
}
|
||||
|
||||
for connector in &mut connectors {
|
||||
if let Some(apps_config) = user_apps_config.as_ref()
|
||||
&& (apps_config.default.is_some()
|
||||
|| apps_config.apps.contains_key(connector.id.as_str()))
|
||||
{
|
||||
connector.is_enabled = app_is_enabled(apps_config, Some(connector.id.as_str()));
|
||||
}
|
||||
|
||||
if requirements_apps_config
|
||||
.and_then(|apps| apps.apps.get(connector.id.as_str()))
|
||||
.is_some_and(|app| app.enabled == Some(false))
|
||||
{
|
||||
connector.is_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_app_enabled_state(connectors: Vec<AppInfo>, _config: &Config) -> Vec<AppInfo> {
|
||||
connectors
|
||||
}
|
||||
|
||||
pub(crate) fn app_tool_policy(
|
||||
config: &Config,
|
||||
connector_id: Option<&str>,
|
||||
tool_name: &str,
|
||||
tool_title: Option<&str>,
|
||||
annotations: Option<&ToolAnnotations>,
|
||||
_config: &Config,
|
||||
_connector_id: Option<&str>,
|
||||
_tool_name: &str,
|
||||
_tool_title: Option<&str>,
|
||||
_annotations: Option<&ToolAnnotations>,
|
||||
) -> AppToolPolicy {
|
||||
let apps_config = read_apps_config(config);
|
||||
app_tool_policy_from_apps_config(
|
||||
apps_config.as_ref(),
|
||||
connector_id,
|
||||
tool_name,
|
||||
tool_title,
|
||||
annotations,
|
||||
)
|
||||
AppToolPolicy::default()
|
||||
}
|
||||
|
||||
pub(crate) fn codex_app_tool_is_enabled(config: &Config, tool_info: &ToolInfo) -> bool {
|
||||
if tool_info.server_name != CODEX_APPS_MCP_SERVER_NAME {
|
||||
return true;
|
||||
}
|
||||
|
||||
app_tool_policy(
|
||||
config,
|
||||
tool_info.connector_id.as_deref(),
|
||||
&tool_info.tool.name,
|
||||
tool_info.tool.title.as_deref(),
|
||||
tool_info.tool.annotations.as_ref(),
|
||||
)
|
||||
.enabled
|
||||
pub(crate) fn codex_app_tool_is_enabled(_config: &Config, _tool_info: &ToolInfo) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn read_apps_config(config: &Config) -> Option<AppsConfigToml> {
|
||||
let apps_config = read_user_apps_config(config);
|
||||
let had_apps_config = apps_config.is_some();
|
||||
let mut apps_config = apps_config.unwrap_or_default();
|
||||
apply_requirements_apps_constraints(
|
||||
&mut apps_config,
|
||||
config.config_layer_stack.requirements_toml().apps.as_ref(),
|
||||
);
|
||||
if had_apps_config || apps_config.default.is_some() || !apps_config.apps.is_empty() {
|
||||
Some(apps_config)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn read_user_apps_config(config: &Config) -> Option<AppsConfigToml> {
|
||||
config
|
||||
.config_layer_stack
|
||||
.effective_config()
|
||||
.as_table()
|
||||
.and_then(|table| table.get("apps"))
|
||||
.cloned()
|
||||
.and_then(|value| AppsConfigToml::deserialize(value).ok())
|
||||
}
|
||||
|
||||
fn apply_requirements_apps_constraints(
|
||||
apps_config: &mut AppsConfigToml,
|
||||
requirements_apps_config: Option<&AppsRequirementsToml>,
|
||||
) {
|
||||
let Some(requirements_apps_config) = requirements_apps_config else {
|
||||
return;
|
||||
};
|
||||
|
||||
for (app_id, requirement) in &requirements_apps_config.apps {
|
||||
if requirement.enabled != Some(false) {
|
||||
continue;
|
||||
}
|
||||
let app = apps_config.apps.entry(app_id.clone()).or_default();
|
||||
app.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
fn app_is_enabled(apps_config: &AppsConfigToml, connector_id: Option<&str>) -> bool {
|
||||
let default_enabled = apps_config
|
||||
.default
|
||||
.as_ref()
|
||||
.map(|defaults| defaults.enabled)
|
||||
.unwrap_or(true);
|
||||
|
||||
connector_id
|
||||
.and_then(|connector_id| apps_config.apps.get(connector_id))
|
||||
.map(|app| app.enabled)
|
||||
.unwrap_or(default_enabled)
|
||||
}
|
||||
|
||||
fn app_tool_policy_from_apps_config(
|
||||
apps_config: Option<&AppsConfigToml>,
|
||||
connector_id: Option<&str>,
|
||||
tool_name: &str,
|
||||
tool_title: Option<&str>,
|
||||
annotations: Option<&ToolAnnotations>,
|
||||
) -> AppToolPolicy {
|
||||
let Some(apps_config) = apps_config else {
|
||||
return AppToolPolicy::default();
|
||||
};
|
||||
|
||||
let app = connector_id.and_then(|connector_id| apps_config.apps.get(connector_id));
|
||||
let tools = app.and_then(|app| app.tools.as_ref());
|
||||
let tool_config = tools.and_then(|tools| {
|
||||
tools
|
||||
.tools
|
||||
.get(tool_name)
|
||||
.or_else(|| tool_title.and_then(|title| tools.tools.get(title)))
|
||||
});
|
||||
let approval = tool_config
|
||||
.and_then(|tool| tool.approval_mode)
|
||||
.or_else(|| app.and_then(|app| app.default_tools_approval_mode))
|
||||
.unwrap_or(AppToolApproval::Auto);
|
||||
|
||||
if !app_is_enabled(apps_config, connector_id) {
|
||||
return AppToolPolicy {
|
||||
enabled: false,
|
||||
approval,
|
||||
};
|
||||
}
|
||||
|
||||
if let Some(enabled) = tool_config.and_then(|tool| tool.enabled) {
|
||||
return AppToolPolicy { enabled, approval };
|
||||
}
|
||||
|
||||
if let Some(enabled) = app.and_then(|app| app.default_tools_enabled) {
|
||||
return AppToolPolicy { enabled, approval };
|
||||
}
|
||||
|
||||
let app_defaults = apps_config.default.as_ref();
|
||||
let destructive_enabled = app
|
||||
.and_then(|app| app.destructive_enabled)
|
||||
.unwrap_or_else(|| {
|
||||
app_defaults
|
||||
.map(|defaults| defaults.destructive_enabled)
|
||||
.unwrap_or(true)
|
||||
});
|
||||
let open_world_enabled = app
|
||||
.and_then(|app| app.open_world_enabled)
|
||||
.unwrap_or_else(|| {
|
||||
app_defaults
|
||||
.map(|defaults| defaults.open_world_enabled)
|
||||
.unwrap_or(true)
|
||||
});
|
||||
let destructive_hint = annotations
|
||||
.and_then(|annotations| annotations.destructive_hint)
|
||||
.unwrap_or(true);
|
||||
let open_world_hint = annotations
|
||||
.and_then(|annotations| annotations.open_world_hint)
|
||||
.unwrap_or(true);
|
||||
let enabled =
|
||||
(destructive_enabled || !destructive_hint) && (open_world_enabled || !open_world_hint);
|
||||
|
||||
AppToolPolicy { enabled, approval }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "connectors_tests.rs"]
|
||||
mod tests;
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
use codex_app_server_protocol::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;
|
||||
|
||||
use super::ContextualUserFragment;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct AppsInstructions;
|
||||
|
||||
impl AppsInstructions {
|
||||
pub(crate) fn from_connectors(connectors: &[AppInfo]) -> Option<Self> {
|
||||
connectors
|
||||
.iter()
|
||||
.any(|connector| connector.is_accessible && connector.is_enabled)
|
||||
.then_some(Self)
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for AppsInstructions {
|
||||
const ROLE: &'static str = "developer";
|
||||
const START_MARKER: &'static str = APPS_INSTRUCTIONS_OPEN_TAG;
|
||||
const END_MARKER: &'static str = APPS_INSTRUCTIONS_CLOSE_TAG;
|
||||
|
||||
fn body(&self) -> String {
|
||||
format!(
|
||||
"\n## Apps (Connectors)\nApps (Connectors) can be explicitly triggered in user messages in the format `[$app-name](app://{{connector_id}})`. Apps can also be implicitly triggered as long as the context suggests usage of available apps.\nAn app is equivalent to a set of MCP tools within the `{CODEX_APPS_MCP_SERVER_NAME}` MCP.\nAn installed app's MCP tools are either provided to you already, or can be lazy-loaded through the `tool_search` tool. If `tool_search` is available, the apps that are searchable by `tools_search` will be listed by it.\nDo not additionally call list_mcp_resources or list_mcp_resource_templates for apps.\n"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,6 @@
|
|||
//! Context fragments injected into model input.
|
||||
|
||||
mod approved_command_prefix_saved;
|
||||
mod apps_instructions;
|
||||
mod collaboration_mode_instructions;
|
||||
mod contextual_user_message;
|
||||
mod environment_context;
|
||||
|
|
@ -25,7 +24,6 @@ mod user_instructions;
|
|||
mod user_shell_command;
|
||||
|
||||
pub(crate) use approved_command_prefix_saved::ApprovedCommandPrefixSaved;
|
||||
pub(crate) use apps_instructions::AppsInstructions;
|
||||
pub(crate) use collaboration_mode_instructions::CollaborationModeInstructions;
|
||||
pub(crate) use contextual_user_message::is_contextual_user_fragment;
|
||||
pub(crate) use contextual_user_message::is_memory_excluded_contextual_user_fragment;
|
||||
|
|
|
|||
1
vendor/codex/codex-rs/core/src/lib.rs
vendored
1
vendor/codex/codex-rs/core/src/lib.rs
vendored
|
|
@ -6,7 +6,6 @@
|
|||
#![deny(clippy::print_stdout, clippy::print_stderr)]
|
||||
|
||||
mod apply_patch;
|
||||
mod apps;
|
||||
mod arc_monitor;
|
||||
mod client;
|
||||
mod client_common;
|
||||
|
|
|
|||
|
|
@ -210,8 +210,6 @@ impl Session {
|
|||
) {
|
||||
let auth = self.services.auth_manager.auth().await;
|
||||
let config = self.get_config().await;
|
||||
let mcp_config = config.to_mcp_config();
|
||||
let mcp_servers = with_codex_apps_mcp(mcp_servers, auth.as_ref(), &mcp_config);
|
||||
let auth_statuses = compute_auth_statuses(mcp_servers.iter(), store_mode).await;
|
||||
{
|
||||
let mut guard = self.services.mcp_startup_cancellation_token.lock().await;
|
||||
|
|
|
|||
17
vendor/codex/codex-rs/core/src/session/mod.rs
vendored
17
vendor/codex/codex-rs/core/src/session/mod.rs
vendored
|
|
@ -16,9 +16,7 @@ use crate::agent::status::is_final;
|
|||
use crate::commit_attribution::commit_message_trailer_instruction;
|
||||
use crate::compact;
|
||||
use crate::config::ManagedFeatures;
|
||||
use crate::connectors;
|
||||
use crate::context::ApprovedCommandPrefixSaved;
|
||||
use crate::context::AppsInstructions;
|
||||
use crate::context::CollaborationModeInstructions;
|
||||
use crate::context::ContextualUserFragment;
|
||||
use crate::context::EpiphanyStateInstructions;
|
||||
|
|
@ -291,7 +289,6 @@ use crate::unified_exec::UnifiedExecProcessManager;
|
|||
use crate::windows_sandbox::WindowsSandboxLevelExt;
|
||||
use codex_git_utils::get_git_repo_root;
|
||||
use codex_mcp::compute_auth_statuses;
|
||||
use codex_mcp::with_codex_apps_mcp;
|
||||
use codex_otel::SessionTelemetry;
|
||||
use codex_otel::THREAD_STARTED_METRIC;
|
||||
use codex_otel::TelemetryAuthMode;
|
||||
|
|
@ -2449,20 +2446,6 @@ impl Session {
|
|||
.push(PersonalitySpecInstructions::new(personality_message).render());
|
||||
}
|
||||
}
|
||||
if turn_context.config.include_apps_instructions && turn_context.apps_enabled() {
|
||||
let mcp_connection_manager = self.services.mcp_connection_manager.read().await;
|
||||
let accessible_and_enabled_connectors =
|
||||
connectors::list_accessible_and_enabled_connectors_from_manager(
|
||||
&mcp_connection_manager,
|
||||
&turn_context.config,
|
||||
)
|
||||
.await;
|
||||
if let Some(apps_instructions) =
|
||||
AppsInstructions::from_connectors(&accessible_and_enabled_connectors)
|
||||
{
|
||||
developer_sections.push(apps_instructions.render());
|
||||
}
|
||||
}
|
||||
if turn_context.features.enabled(Feature::CodexGitCommit)
|
||||
&& let Some(commit_message_instruction) = commit_message_trailer_instruction(
|
||||
turn_context.config.commit_attribution.as_deref(),
|
||||
|
|
|
|||
|
|
@ -100,10 +100,6 @@ impl TurnContext {
|
|||
self.model_info.auto_compact_token_limit()
|
||||
}
|
||||
|
||||
pub(crate) fn apps_enabled(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub(crate) async fn with_model(&self, model: String, models_manager: &ModelsManager) -> Self {
|
||||
let mut config = (*self.config).clone();
|
||||
config.model = Some(model.clone());
|
||||
|
|
|
|||
Reference in a new issue