Centralize Codex Apps client handling (#29528)

## Why

Codex Apps-specific behavior is currently distributed across cache
helpers, startup, tool conversion, and model-visible annotation. Each
layer independently checks the reserved server name, which obscures the
boundary between trusted host-owned connector metadata and regular MCP
server data.

Classifying the server once when `AsyncManagedClient` is created gives
the client a single source of truth and makes the two processing paths
explicit.

## What changed

- Record whether an `AsyncManagedClient` represents the Codex Apps
server at construction time.
- Route startup cache loading, cache persistence, and cache telemetry
through the Codex Apps branch.
- Split uncached tool conversion between Codex Apps normalization and
regular MCP metadata sanitization.
- Split model-visible schema and plugin provenance handling along the
same boundary.
- Remove redundant server-name guards from helpers that are now called
only from the Codex Apps branch.

## Verification

- Preserve behavioral coverage that verifies Codex Apps connector
metadata and the complete converted `ToolInfo` shape.

## Stack

Depends on #29518.
This commit is contained in:
Ahmed Ibrahim authored and GitHub committed 2026-06-23 00:00:25 -07:00
1 parent 33cc928d33
commit f0ad028a74
6 files changed
+262 -240

No files matched your search

+15 -15
View File
@@ -113,9 +113,9 @@ impl ToolFilter {
}
}
/// Returns the model-visible view of a tool while preserving the raw metadata
/// used by execution. Keep cache entries raw and call this at manager return
/// boundaries.
/// Returns the model-visible view of a tool while preserving the raw metadata used by execution.
/// Declared file parameters are presented as local file paths; execution later uploads those files
/// and replaces the paths with the uploaded-file objects expected by the app.
pub(crate) fn tool_with_model_visible_input_schema(tool: &Tool) -> Tool {
let file_params = declared_openai_file_input_param_names(tool.meta.as_deref());
if file_params.is_empty() {
@@ -124,7 +124,7 @@ pub(crate) fn tool_with_model_visible_input_schema(tool: &Tool) -> Tool {
let mut tool = tool.clone();
let mut input_schema = JsonValue::Object(tool.input_schema.as_ref().clone());
mask_input_schema_for_file_path_params(&mut input_schema, &file_params);
rewrite_input_schema_for_local_file_paths(&mut input_schema, &file_params);
if let JsonValue::Object(input_schema) = input_schema {
tool.input_schema = Arc::new(input_schema);
}
@@ -262,15 +262,7 @@ const MAX_TOOL_NAME_LENGTH: usize = 64;
const CALLABLE_NAME_HASH_LEN: usize = 12;
const META_OPENAI_FILE_PARAMS: &str = "openai/fileParams";
fn callable_namespace_with_prefix(namespace: &str, prefix_mcp_tool_names: bool) -> String {
if !prefix_mcp_tool_names || namespace.starts_with(LEGACY_MCP_TOOL_NAME_PREFIX) {
namespace.to_string()
} else {
format!("{LEGACY_MCP_TOOL_NAME_PREFIX}{namespace}")
}
}
fn mask_input_schema_for_file_path_params(input_schema: &mut JsonValue, file_params: &[String]) {
fn rewrite_input_schema_for_local_file_paths(input_schema: &mut JsonValue, file_params: &[String]) {
let Some(properties) = input_schema
.as_object_mut()
.and_then(|schema| schema.get_mut("properties"))
@@ -283,11 +275,11 @@ fn mask_input_schema_for_file_path_params(input_schema: &mut JsonValue, file_par
let Some(property_schema) = properties.get_mut(field_name) else {
continue;
};
mask_input_property_schema(property_schema);
rewrite_input_property_schema_as_local_file_path(property_schema);
}
}
fn mask_input_property_schema(schema: &mut JsonValue) {
fn rewrite_input_property_schema_as_local_file_path(schema: &mut JsonValue) {
let Some(object) = schema.as_object_mut() else {
return;
};
@@ -316,6 +308,14 @@ fn mask_input_property_schema(schema: &mut JsonValue) {
}
}
fn callable_namespace_with_prefix(namespace: &str, prefix_mcp_tool_names: bool) -> String {
if !prefix_mcp_tool_names || namespace.starts_with(LEGACY_MCP_TOOL_NAME_PREFIX) {
namespace.to_string()
} else {
format!("{LEGACY_MCP_TOOL_NAME_PREFIX}{namespace}")
}
}
fn sha1_hex(s: &str) -> String {
let mut hasher = Sha1::new();
hasher.update(s.as_bytes());