core: resolve app tool file paths in selected environments

This commit is contained in:
Adam Perry
2026-06-24 22:35:46 +00:00
parent 81f340436c
commit 710dbd5eda
2 changed files with 38 additions and 21 deletions
+7 -18
View File
@@ -16,7 +16,6 @@ use crate::session::turn_context::TurnContext;
use codex_api::OPENAI_FILE_UPLOAD_LIMIT_BYTES;
use codex_api::upload_openai_file;
use codex_login::CodexAuth;
use codex_utils_path_uri::PathUri;
use serde_json::Value as JsonValue;
pub(crate) async fn rewrite_mcp_tool_arguments_for_openai_files(
@@ -122,14 +121,11 @@ async fn build_uploaded_argument_value(
"no primary turn environment is available".to_string(),
));
};
// TODO(anp): Resolve app tool file arguments using the selected environment's native path
// convention so uploads can read relative paths from foreign environments.
let native_environment_cwd = turn_environment
let path_uri = turn_environment
.cwd()
.to_abs_path()
.join(file_path)
.map_err(|error| contextualize_error(error.to_string()))?;
let resolved_path = native_environment_cwd.join(file_path);
let path_uri = PathUri::from_abs_path(&resolved_path);
let display_path = path_uri.inferred_native_path_string();
let fs = turn_environment.environment.get_filesystem();
let metadata = fs
.get_metadata(&path_uri, /*sandbox*/ None)
@@ -137,27 +133,20 @@ async fn build_uploaded_argument_value(
.map_err(|error| contextualize_error(error.to_string()))?;
if !metadata.is_file {
return Err(contextualize_error(format!(
"path `{}` is not a file",
resolved_path.display()
"path `{display_path}` is not a file"
)));
}
if metadata.size > OPENAI_FILE_UPLOAD_LIMIT_BYTES {
return Err(contextualize_error(format!(
"file `{}` is too large: {} bytes exceeds the limit of {} bytes",
resolved_path.display(),
metadata.size,
OPENAI_FILE_UPLOAD_LIMIT_BYTES,
"file `{display_path}` is too large: {} bytes exceeds the limit of {} bytes",
metadata.size, OPENAI_FILE_UPLOAD_LIMIT_BYTES,
)));
}
let contents = fs
.read_file_stream(&path_uri, /*sandbox*/ None)
.await
.map_err(|error| contextualize_error(error.to_string()))?;
let file_name = resolved_path
.file_name()
.and_then(|value| value.to_str())
.unwrap_or("file")
.to_string();
let file_name = path_uri.basename().unwrap_or_else(|| "file".to_string());
let upload_auth = codex_model_provider::auth_provider_from_auth(auth);
let uploaded = upload_openai_file(
turn_context.config.chatgpt_base_url.trim_end_matches('/'),
+31 -3
View File
@@ -185,9 +185,10 @@ async fn run_extract_turn(test: &TestCodex, server: &MockServer) -> Result<Respo
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn codex_apps_file_params_upload_environment_files_before_mcp_tool_call() -> Result<()> {
// TODO(anp): Remove after file-upload fixtures support target-native Windows paths.
skip_if_wine_exec!(Ok(()), "uses a host-native file-upload path");
skip_if_wine_exec!(
Ok(()),
"13 MiB fixture exceeds the Wine exec WebSocket frame limit after base64 encoding"
);
let server = start_mock_server().await;
let apps_server = AppsTestServer::mount(&server).await?;
mount_file_upload_mocks(&server, STREAMED_FILE_SIZE as u64).await;
@@ -246,6 +247,33 @@ async fn codex_apps_file_params_upload_environment_files_before_mcp_tool_call()
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn codex_apps_file_params_upload_relative_path_from_selected_environment() -> Result<()> {
let server = start_mock_server().await;
let apps_server = AppsTestServer::mount(&server).await?;
mount_file_upload_mocks(&server, /*file_size_bytes*/ 11).await;
let mut builder = apps_enabled_builder(apps_server.chatgpt_base_url.clone())
.with_workspace_setup(|cwd, fs| async move {
let report_path = PathUri::from_abs_path(&cwd.join("report.txt"));
fs.write_file(&report_path, b"hello world".to_vec(), /*sandbox*/ None)
.await?;
Ok(())
});
let test = builder.build_with_auto_env(&server).await?;
let _responses = run_extract_turn(&test, &server).await?;
let apps_tool_call =
recorded_apps_tool_call_by_name(&server, CALENDAR_EXTRACT_TEXT_TOOL_NAME).await;
assert_eq!(
apps_tool_call.pointer("/params/arguments/file"),
Some(&uploaded_file(&server, /*file_size_bytes*/ 11))
);
server.verify().await;
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn codex_apps_file_params_pass_uploaded_file_to_post_tool_use_hook() -> Result<()> {
let server = start_mock_server().await;