Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 138 additions & 0 deletions codex-rs/core/tests/suite/openai_file_mcp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ use core_test_support::apps_test_server::DOCUMENT_EXTRACT_TEXT_RESOURCE_URI;
use core_test_support::hooks::trust_discovered_hooks;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_custom_tool_call;
use core_test_support::responses::ev_function_call_with_namespace;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::mount_sse_sequence;
use core_test_support::responses::sse;
use core_test_support::responses::start_mock_server;
use core_test_support::skip_if_no_network;
use core_test_support::test_codex::test_codex;
use pretty_assertions::assert_eq;
use serde_json::Value;
Expand Down Expand Up @@ -245,3 +247,139 @@ async fn codex_apps_file_params_upload_local_paths_before_mcp_tool_call() -> Res
server.verify().await;
Ok(())
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn code_mode_nested_mcp_file_params_upload_local_paths_before_tool_call() -> Result<()> {
skip_if_no_network!(Ok(()));

let server = start_mock_server().await;
let apps_server = AppsTestServer::mount(&server).await?;

Mock::given(method("POST"))
.and(path("/files"))
.and(header("chatgpt-account-id", "account_id"))
.and(body_json(json!({
"file_name": "report.txt",
"file_size": 11,
"use_case": "codex",
})))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"file_id": "file_123",
"upload_url": format!("{}/upload/file_123", server.uri()),
})))
.expect(1)
.mount(&server)
.await;
Mock::given(method("PUT"))
.and(path("/upload/file_123"))
.and(header("content-length", "11"))
.respond_with(ResponseTemplate::new(200))
.expect(1)
.mount(&server)
.await;
Mock::given(method("POST"))
.and(path("/files/file_123/uploaded"))
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"status": "success",
"download_url": format!("{}/download/file_123", server.uri()),
"file_name": "report.txt",
"mime_type": "text/plain",
"file_size_bytes": 11,
})))
.expect(1)
.mount(&server)
.await;

let code = r#"
const result = await tools.mcp__codex_apps__calendar_extract_text({
file: "report.txt",
});
text(result.content?.[0]?.text ?? "missing result");
"#;
mount_sse_sequence(
&server,
vec![
sse(vec![
ev_response_created("resp-1"),
ev_custom_tool_call("call-1", "exec", code),
ev_completed("resp-1"),
]),
sse(vec![
ev_response_created("resp-2"),
ev_assistant_message("msg-1", "done"),
ev_completed("resp-2"),
]),
],
)
.await;

let mut builder = test_codex()
.with_auth(CodexAuth::create_dummy_chatgpt_auth_for_testing())
.with_config(move |config| {
configure_apps(config, apps_server.chatgpt_base_url.as_str());
config
.features
.enable(Feature::CodeMode)
.expect("test config should allow feature update");
});
let test = builder.build(&server).await?;
tokio::fs::write(test.cwd.path().join("report.txt"), b"hello world").await?;

test.submit_turn_with_approval_and_permission_profile(
"Use code mode to extract the report text with the app tool.",
AskForApproval::Never,
PermissionProfile::Disabled,
)
.await?;

let apps_tool_call = server
.received_requests()
.await
.unwrap_or_default()
.into_iter()
.find_map(|request| {
let body: Value = serde_json::from_slice(&request.body).ok()?;
(request.url.path() == "/api/codex/apps"
&& body.get("method").and_then(Value::as_str) == Some("tools/call")
&& body.pointer("/params/name").and_then(Value::as_str)
== Some("calendar_extract_text"))
.then_some(body)
})
.expect("apps calendar_extract_text tools/call request should be recorded");

assert_eq!(
apps_tool_call.pointer("/params/arguments/file"),
Some(&json!({
"download_url": format!("{}/download/file_123", server.uri()),
"file_id": "file_123",
"mime_type": "text/plain",
"file_name": "report.txt",
"uri": "sediment://file_123",
"file_size_bytes": 11,
}))
);
let codex_apps_meta = apps_tool_call
.pointer("/params/_meta/_codex_apps")
.expect("codex apps metadata should be forwarded");
assert!(
codex_apps_meta
.pointer("/call_id")
.and_then(Value::as_str)
.is_some_and(|call_id| call_id.starts_with("exec-"))
);
assert_eq!(
codex_apps_meta.pointer("/resource_uri"),
Some(&json!(DOCUMENT_EXTRACT_TEXT_RESOURCE_URI))
);
assert_eq!(
codex_apps_meta.pointer("/contains_mcp_source"),
Some(&json!(true))
);
assert_eq!(
codex_apps_meta.pointer("/connector_id"),
Some(&json!("calendar"))
);

server.verify().await;
Ok(())
}
Loading