feat: add Codex Apps sediment file remapping (#15197)

## Summary
- bridge Codex Apps tools that declare `_meta["openai/fileParams"]`
through the OpenAI file upload flow
- mask those file params in model-visible tool schemas so the model
provides absolute local file paths instead of raw file payload objects
- rewrite those local file path arguments client-side into
`ProvidedFilePayload`-shaped objects before the normal MCP tool call

## Details
- applies to scalar and array file params declared in
`openai/fileParams`
- Codex uploads local files directly to the backend and uses the
uploaded file metadata to build the MCP tool arguments locally
- this PR is input-only

## Verification
- `just fmt`
- `cargo test -p codex-core mcp_tool_call -- --nocapture`

---------

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Casey Chow
2026-04-09 14:10:44 -04:00
committed by GitHub
parent 25a0f6784d
commit 244b15c95d
15 changed files with 1313 additions and 35 deletions

View File

@@ -22,6 +22,8 @@ const SEARCHABLE_TOOL_COUNT: usize = 100;
pub const CALENDAR_CREATE_EVENT_RESOURCE_URI: &str =
"connector://calendar/tools/calendar_create_event";
const CALENDAR_LIST_EVENTS_RESOURCE_URI: &str = "connector://calendar/tools/calendar_list_events";
pub const DOCUMENT_EXTRACT_TEXT_RESOURCE_URI: &str =
"connector://calendar/tools/calendar_extract_text";
#[derive(Clone)]
pub struct AppsTestServer {
@@ -235,6 +237,39 @@ impl Respond for CodexAppsJsonRpcResponder {
"connector_id": CONNECTOR_ID
}
}
},
{
"name": "calendar_extract_text",
"description": "Extract text from an uploaded document.",
"annotations": {
"readOnlyHint": false
},
"inputSchema": {
"type": "object",
"properties": {
"file": {
"type": "object",
"description": "Document file payload.",
"properties": {
"file_id": { "type": "string" }
},
"required": ["file_id"]
}
},
"required": ["file"],
"additionalProperties": false
},
"_meta": {
"connector_id": CONNECTOR_ID,
"connector_name": self.connector_name.clone(),
"connector_description": self.connector_description.clone(),
"openai/fileParams": ["file"],
"_codex_apps": {
"resource_uri": DOCUMENT_EXTRACT_TEXT_RESOURCE_URI,
"contains_mcp_source": true,
"connector_id": CONNECTOR_ID
}
}
}
],
"nextCursor": null
@@ -245,7 +280,7 @@ impl Respond for CodexAppsJsonRpcResponder {
.pointer_mut("/result/tools")
.and_then(Value::as_array_mut)
{
for index in 2..SEARCHABLE_TOOL_COUNT {
for index in 3..SEARCHABLE_TOOL_COUNT {
tools.push(json!({
"name": format!("calendar_timezone_option_{index}"),
"description": format!("Read timezone option {index}."),
@@ -283,6 +318,10 @@ impl Respond for CodexAppsJsonRpcResponder {
.pointer("/params/arguments/starts_at")
.and_then(Value::as_str)
.unwrap_or_default();
let file_id = body
.pointer("/params/arguments/file/file_id")
.and_then(Value::as_str)
.unwrap_or_default();
let codex_apps_meta = body.pointer("/params/_meta/_codex_apps").cloned();
ResponseTemplate::new(200).set_body_json(json!({
@@ -291,7 +330,7 @@ impl Respond for CodexAppsJsonRpcResponder {
"result": {
"content": [{
"type": "text",
"text": format!("called {tool_name} for {title} at {starts_at}")
"text": format!("called {tool_name} for {title} at {starts_at} with {file_id}")
}],
"structuredContent": {
"_codex_apps": codex_apps_meta,