diff --git a/codex-rs/core/src/codex_apps_file_download.rs b/codex-rs/core/src/codex_apps_file_download.rs index b26ba072c2..eee08c4fe1 100644 --- a/codex-rs/core/src/codex_apps_file_download.rs +++ b/codex-rs/core/src/codex_apps_file_download.rs @@ -1,3 +1,4 @@ +use crate::mcp_openai_file::OPENAI_LIBRARY_CONNECTOR_ID; use crate::session::session::Session; use crate::session::turn_context::TurnContext; use codex_api::OPENAI_FILE_UPLOAD_LIMIT_BYTES; @@ -36,12 +37,17 @@ fn codex_apps_download_base_url(turn_context: &TurnContext) -> &str { fn should_materialize_codex_apps_file_download( server: &str, + connector_id: Option<&str>, codex_apps_meta: Option<&JsonMap>, ) -> bool { if server != codex_mcp::CODEX_APPS_MCP_SERVER_NAME { return false; } + if connector_id != Some(OPENAI_LIBRARY_CONNECTOR_ID) { + return false; + } + let Some(codex_apps_meta) = codex_apps_meta else { return false; }; @@ -56,10 +62,11 @@ pub(crate) async fn maybe_materialize_codex_apps_file_download_result( sess: &Session, turn_context: &TurnContext, server: &str, + connector_id: Option<&str>, codex_apps_meta: Option<&JsonMap>, mut result: CallToolResult, ) -> CallToolResult { - if !should_materialize_codex_apps_file_download(server, codex_apps_meta) + if !should_materialize_codex_apps_file_download(server, connector_id, codex_apps_meta) || result.is_error == Some(true) { return result; @@ -512,4 +519,48 @@ mod tests { }) })); } + + #[tokio::test] + async fn codex_apps_file_download_materialization_is_noop_for_non_library_connector() { + let (session, turn_context) = make_session_and_context().await; + let original = CallToolResult { + content: vec![serde_json::json!({ + "type": "text", + "text": serde_json::json!({ + "file_id": "file_123", + "file_name": "testing-file.txt", + "file_uri": { + "download_url": "https://attacker.example/download/file_123", + "file_name": "testing-file.txt", + } + }) + .to_string(), + })], + structured_content: Some(serde_json::json!({ + "file_id": "file_123", + "file_name": "testing-file.txt", + "file_uri": { + "download_url": "https://attacker.example/download/file_123", + "file_name": "testing-file.txt", + } + })), + is_error: Some(false), + meta: None, + }; + + let result = maybe_materialize_codex_apps_file_download_result( + &session, + &turn_context, + codex_mcp::CODEX_APPS_MCP_SERVER_NAME, + Some("connector_not_library"), + Some(&serde_json::Map::from_iter([( + CODEX_APPS_META_MATERIALIZE_FILE_DOWNLOAD_KEY.to_string(), + JsonValue::Bool(true), + )])), + original.clone(), + ) + .await; + + assert_eq!(result, original); + } } diff --git a/codex-rs/core/src/mcp_openai_file.rs b/codex-rs/core/src/mcp_openai_file.rs index bc415a6ab9..bbefef7755 100644 --- a/codex-rs/core/src/mcp_openai_file.rs +++ b/codex-rs/core/src/mcp_openai_file.rs @@ -20,6 +20,8 @@ use codex_protocol::mcp::CallToolResult; use serde_json::Map as JsonMap; use serde_json::Value as JsonValue; +pub(crate) const OPENAI_LIBRARY_CONNECTOR_ID: &str = "connector_openai_library"; + pub(crate) async fn rewrite_mcp_tool_arguments_for_openai_files( sess: &Session, turn_context: &TurnContext, @@ -69,6 +71,7 @@ pub(crate) async fn postprocess_mcp_tool_result_for_openai_files( sess: &Session, turn_context: &TurnContext, server: &str, + connector_id: Option<&str>, codex_apps_meta: Option<&JsonMap>, result: CallToolResult, ) -> CallToolResult { @@ -76,6 +79,7 @@ pub(crate) async fn postprocess_mcp_tool_result_for_openai_files( sess, turn_context, server, + connector_id, codex_apps_meta, result, ) diff --git a/codex-rs/core/src/mcp_tool_call.rs b/codex-rs/core/src/mcp_tool_call.rs index db5cbb63a4..c27a9d29ef 100644 --- a/codex-rs/core/src/mcp_tool_call.rs +++ b/codex-rs/core/src/mcp_tool_call.rs @@ -26,6 +26,7 @@ use crate::guardian::new_guardian_review_id; use crate::guardian::review_approval_request; use crate::guardian::routes_approval_to_guardian; use crate::hook_runtime::run_permission_request_hooks; +use crate::mcp_openai_file::OPENAI_LIBRARY_CONNECTOR_ID; use crate::mcp_openai_file::postprocess_mcp_tool_result_for_openai_files; use crate::mcp_openai_file::rewrite_mcp_tool_arguments_for_openai_files; use crate::mcp_tool_approval_templates::RenderedMcpToolApprovalParam; @@ -343,6 +344,7 @@ async fn handle_approved_mcp_tool_call( sess, turn_context, &server, + metadata.and_then(|metadata| metadata.connector_id.as_deref()), metadata.and_then(|metadata| metadata.codex_apps_meta.as_ref()), result, ) @@ -730,7 +732,6 @@ pub(crate) struct McpToolApprovalMetadata { } const CODEX_APPS_META_KEY: &str = "_codex_apps"; -const OPENAI_LIBRARY_CONNECTOR_ID: &str = "connector_openai_library"; const MCP_TOOL_OPENAI_OUTPUT_TEMPLATE_META_KEY: &str = "openai/outputTemplate"; const MCP_TOOL_UI_RESOURCE_URI_META_KEY: &str = "ui/resourceUri"; const MCP_TOOL_THREAD_ID_META_KEY: &str = "threadId";