Support optional thread IDs in hosted file uploads (#59795)

## What changed

Add an optional `thread_id` to `HostedFileUploadContext` and include it as `codex_thread_id` in file creation requests when provided. Keep MCP app uploads without this field while retaining the thread ID in tool-call metadata.

## Testing

Extend upload tests to check inclusion and omission of `codex_thread_id`, and assert that MCP app calls retain `_meta.threadId` without adding it to file uploads.

GitOrigin-RevId: cff15b35e5130e56973f944b3b849807550d298b
This commit is contained in:
lt-oai
2026-11-06 02:42:02 +00:00
committed by copyberry
parent 1031a7b9aa
commit da279f6fd4
3 changed files with 23 additions and 1 deletions
+16 -1
View File
@@ -29,6 +29,7 @@ const OPENAI_FILE_USE_CASE: &str = "codex";
#[derive(Debug)]
pub struct HostedFileUploadContext {
pub thread_id: Option<String>,
pub connector_id: String,
pub action_name: String,
pub model: String,
@@ -158,7 +159,7 @@ where
"use_case": OPENAI_FILE_USE_CASE,
});
let request = authorized_request(client_pool, auth, Method::POST, &create_url).await?;
let create_request = match hosted_upload {
let mut create_request = match hosted_upload {
Some(context) => serde_json::json!({
"file_name": file_name.as_str(),
"file_size": file_size_bytes,
@@ -169,6 +170,9 @@ where
}),
None => create_request,
};
if let Some(thread_id) = hosted_upload.and_then(|context| context.thread_id.as_ref()) {
create_request["codex_thread_id"] = serde_json::Value::String(thread_id.clone());
}
let create_response = request
.json(&create_request)
.send()
@@ -636,6 +640,7 @@ mod tests {
"file_name": "report.pdf",
"file_size": 8,
"use_case": "codex",
"codex_thread_id": "00000000-0000-0000-0000-000000000001",
"codex_connector_id": "library",
"codex_action_name": "create_library_file",
"codex_model": "gpt-work",
@@ -677,6 +682,7 @@ mod tests {
.await;
let hosted_upload = HostedFileUploadContext {
thread_id: Some("00000000-0000-0000-0000-000000000001".to_string()),
connector_id: "library".to_string(),
action_name: "create_library_file".to_string(),
model: "gpt-work".to_string(),
@@ -702,12 +708,21 @@ mod tests {
async fn upload_hosted_app_preserves_empty_finalization_for_older_servers() {
let server = MockServer::start().await;
let hosted_upload = HostedFileUploadContext {
thread_id: None,
connector_id: "library".to_string(),
action_name: "create_library_file".to_string(),
model: "gpt-work".to_string(),
};
Mock::given(method("POST"))
.and(path("/backend-api/files"))
.and(body_json(serde_json::json!({
"file_name": "report.pdf",
"file_size": 8,
"use_case": "codex",
"codex_connector_id": "library",
"codex_action_name": "create_library_file",
"codex_model": "gpt-work",
})))
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
"file_id": "file_pdf",
"upload_url": format!("{}/upload/file_pdf", server.uri()),
+1
View File
@@ -549,6 +549,7 @@ async fn handle_approved_mcp_tool_call(
.as_ref()
.zip(item_metadata.action_name.as_ref())
.map(|(connector_id, action_name)| HostedFileUploadContext {
thread_id: None,
connector_id: connector_id.clone(),
action_name: action_name.clone(),
model: step_context.settings.model_info.slug.clone(),
@@ -291,6 +291,12 @@ async fn codex_apps_file_params_omit_fields_absent_from_tool_schema() -> Result<
upload_body.get("codex_connector_id"),
Some(&json!("calendar"))
);
let thread_id = json!(test.session_configured.session_id);
assert_eq!(
apps_tool_call.pointer("/params/_meta/threadId"),
Some(&thread_id)
);
assert_eq!(upload_body.get("codex_thread_id"), None);
assert_eq!(upload_body.get("upload_source"), None);
assert_eq!(upload_body.get("store_in_library"), None);