This commit is contained in:
Roy Han
2026-03-31 15:07:23 -07:00
1807 changed files with 41233 additions and 163241 deletions
@@ -134,7 +134,7 @@ fn session_configured_produces_thread_started_event() {
#[test]
fn turn_started_emits_turn_started_event() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected =
processor.collect_thread_events(ServerNotification::TurnStarted(TurnStartedNotification {
@@ -159,7 +159,7 @@ fn turn_started_emits_turn_started_event() {
#[test]
fn command_execution_started_and_completed_translate_to_thread_events() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let command_item = ThreadItem::CommandExecution {
id: "cmd-1".to_string(),
command: "ls".to_string(),
@@ -236,7 +236,7 @@ fn command_execution_started_and_completed_translate_to_thread_events() {
#[test]
fn empty_reasoning_items_are_ignored() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -261,7 +261,7 @@ fn empty_reasoning_items_are_ignored() {
#[test]
fn unsupported_items_do_not_consume_synthetic_ids() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let ignored = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -313,7 +313,7 @@ fn unsupported_items_do_not_consume_synthetic_ids() {
#[test]
fn reasoning_items_emit_summary_not_raw_content() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -345,7 +345,7 @@ fn reasoning_items_emit_summary_not_raw_content() {
#[test]
fn web_search_completion_preserves_query_and_action() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -385,7 +385,7 @@ fn web_search_completion_preserves_query_and_action() {
#[test]
fn web_search_start_and_completion_reuse_item_id() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let started =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
@@ -452,7 +452,7 @@ fn web_search_start_and_completion_reuse_item_id() {
#[test]
fn mcp_tool_call_begin_and_end_emit_item_events() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let started =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
@@ -534,7 +534,7 @@ fn mcp_tool_call_begin_and_end_emit_item_events() {
#[test]
fn mcp_tool_call_failure_sets_failed_status() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -580,7 +580,7 @@ fn mcp_tool_call_failure_sets_failed_status() {
#[test]
fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let started =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
@@ -668,7 +668,7 @@ fn mcp_tool_call_defaults_arguments_and_preserves_structured_content() {
#[test]
fn collab_spawn_begin_and_end_emit_item_events() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let started =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
@@ -758,7 +758,7 @@ fn collab_spawn_begin_and_end_emit_item_events() {
#[test]
fn file_change_completion_maps_change_kinds() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -820,7 +820,7 @@ fn file_change_completion_maps_change_kinds() {
#[test]
fn file_change_declined_maps_to_failed_status() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -860,7 +860,7 @@ fn file_change_declined_maps_to_failed_status() {
#[test]
fn agent_message_item_updates_final_message() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -894,7 +894,7 @@ fn agent_message_item_updates_final_message() {
#[test]
fn agent_message_item_started_is_ignored() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
@@ -919,7 +919,7 @@ fn agent_message_item_started_is_ignored() {
#[test]
fn reasoning_item_completed_uses_synthetic_id() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -951,7 +951,7 @@ fn reasoning_item_completed_uses_synthetic_id() {
#[test]
fn warning_event_produces_error_item() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_warning(
"Heads up: Long conversations and multiple compactions can cause the model to be less accurate. Start a new conversation when possible to keep conversations small and targeted.".to_string(),
@@ -975,7 +975,7 @@ fn warning_event_produces_error_item() {
#[test]
fn plan_update_emits_started_then_updated_then_completed() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let started = processor.collect_thread_events(ServerNotification::TurnPlanUpdated(
TurnPlanUpdatedNotification {
@@ -1104,7 +1104,7 @@ fn plan_update_emits_started_then_updated_then_completed() {
#[test]
fn plan_update_after_completion_starts_new_todo_list_with_new_id() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let _ = processor.collect_thread_events(ServerNotification::TurnPlanUpdated(
TurnPlanUpdatedNotification {
@@ -1164,7 +1164,7 @@ fn plan_update_after_completion_starts_new_todo_list_with_new_id() {
#[test]
fn token_usage_update_is_emitted_on_turn_completion() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let usage_update =
processor.collect_thread_events(ServerNotification::ThreadTokenUsageUpdated(
@@ -1228,7 +1228,7 @@ fn token_usage_update_is_emitted_on_turn_completion() {
#[test]
fn turn_completion_recovers_final_message_from_turn_items() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let completed = processor.collect_thread_events(ServerNotification::TurnCompleted(
TurnCompletedNotification {
@@ -1263,7 +1263,7 @@ fn turn_completion_recovers_final_message_from_turn_items() {
#[test]
fn turn_completion_reconciles_started_items_from_turn_items() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let started =
processor.collect_thread_events(ServerNotification::ItemStarted(ItemStartedNotification {
@@ -1351,7 +1351,7 @@ fn turn_completion_reconciles_started_items_from_turn_items() {
#[test]
fn turn_completion_overwrites_stale_final_message_from_turn_items() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let _ = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
item: ThreadItem::AgentMessage {
@@ -1398,7 +1398,7 @@ fn turn_completion_overwrites_stale_final_message_from_turn_items() {
#[test]
fn turn_completion_preserves_streamed_final_message_when_turn_items_are_empty() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let _ = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
item: ThreadItem::AgentMessage {
@@ -1440,7 +1440,7 @@ fn turn_completion_preserves_streamed_final_message_when_turn_items_are_empty()
#[test]
fn failed_turn_clears_stale_final_message() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ItemCompleted(
ItemCompletedNotification {
@@ -1482,7 +1482,7 @@ fn failed_turn_clears_stale_final_message() {
#[test]
fn turn_completion_falls_back_to_final_plan_text() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let completed = processor.collect_thread_events(ServerNotification::TurnCompleted(
TurnCompletedNotification {
@@ -1515,7 +1515,7 @@ fn turn_completion_falls_back_to_final_plan_text() {
#[test]
fn turn_failure_prefers_structured_error_message() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let error = processor.collect_thread_events(ServerNotification::Error(ErrorNotification {
error: TurnError {
@@ -1565,7 +1565,7 @@ fn turn_failure_prefers_structured_error_message() {
#[test]
fn model_reroute_surfaces_as_error_item() {
let mut processor = EventProcessorWithJsonOutput::new(None);
let mut processor = EventProcessorWithJsonOutput::new(/*last_message_path*/ None);
let collected = processor.collect_thread_events(ServerNotification::ModelRerouted(
codex_app_server_protocol::ModelReroutedNotification {
+1
View File
@@ -6,6 +6,7 @@ mod ephemeral;
mod mcp_required_exit;
mod originator;
mod output_schema;
mod prompt_stdin;
mod resume;
mod sandbox;
mod server_error_exit;
+171
View File
@@ -0,0 +1,171 @@
#![cfg(not(target_os = "windows"))]
#![allow(clippy::expect_used, clippy::unwrap_used)]
use core_test_support::responses;
use core_test_support::test_codex_exec::test_codex_exec;
use predicates::str::contains;
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_appends_piped_stdin_to_prompt_argument() -> anyhow::Result<()> {
let test = test_codex_exec();
let server = responses::start_mock_server().await;
let body = responses::sse(vec![
responses::ev_response_created("resp1"),
responses::ev_assistant_message("m1", "fixture hello"),
responses::ev_completed("resp1"),
]);
let response_mock = responses::mount_sse_once(&server, body).await;
// echo "my output" | codex exec --skip-git-repo-check -C <cwd> -m gpt-5.1 "Summarize this concisely"
test.cmd_with_server(&server)
.arg("--skip-git-repo-check")
.arg("-C")
.arg(test.cwd_path())
.arg("-m")
.arg("gpt-5.1")
.arg("Summarize this concisely")
.write_stdin("my output\n")
.assert()
.success();
let request = response_mock.single_request();
assert!(
request.has_message_with_input_texts("user", |texts| {
texts == ["Summarize this concisely\n\n<stdin>\nmy output\n</stdin>".to_string()]
}),
"request should include a user message with the prompt plus piped stdin context"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_ignores_empty_piped_stdin_when_prompt_argument_is_present() -> anyhow::Result<()> {
let test = test_codex_exec();
let server = responses::start_mock_server().await;
let body = responses::sse(vec![
responses::ev_response_created("resp1"),
responses::ev_assistant_message("m1", "fixture hello"),
responses::ev_completed("resp1"),
]);
let response_mock = responses::mount_sse_once(&server, body).await;
// printf "" | codex exec --skip-git-repo-check -C <cwd> -m gpt-5.1 "Summarize this concisely"
test.cmd_with_server(&server)
.arg("--skip-git-repo-check")
.arg("-C")
.arg(test.cwd_path())
.arg("-m")
.arg("gpt-5.1")
.arg("Summarize this concisely")
.write_stdin("")
.assert()
.success();
let request = response_mock.single_request();
assert!(
request.has_message_with_input_texts("user", |texts| texts
== ["Summarize this concisely".to_string()]),
"request should preserve the prompt when stdin is empty"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_dash_prompt_reads_stdin_as_the_prompt() -> anyhow::Result<()> {
let test = test_codex_exec();
let server = responses::start_mock_server().await;
let body = responses::sse(vec![
responses::ev_response_created("resp1"),
responses::ev_assistant_message("m1", "fixture hello"),
responses::ev_completed("resp1"),
]);
let response_mock = responses::mount_sse_once(&server, body).await;
// echo "prompt from stdin" | codex exec --skip-git-repo-check -C <cwd> -m gpt-5.1 -
test.cmd_with_server(&server)
.arg("--skip-git-repo-check")
.arg("-C")
.arg(test.cwd_path())
.arg("-m")
.arg("gpt-5.1")
.arg("-")
.write_stdin("prompt from stdin\n")
.assert()
.success();
let request = response_mock.single_request();
assert!(
request.has_message_with_input_texts("user", |texts| {
texts == ["prompt from stdin\n".to_string()]
}),
"dash prompt should preserve the existing forced-stdin behavior"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn exec_without_prompt_argument_reads_piped_stdin_as_the_prompt() -> anyhow::Result<()> {
let test = test_codex_exec();
let server = responses::start_mock_server().await;
let body = responses::sse(vec![
responses::ev_response_created("resp1"),
responses::ev_assistant_message("m1", "fixture hello"),
responses::ev_completed("resp1"),
]);
let response_mock = responses::mount_sse_once(&server, body).await;
// echo "prompt from stdin" | codex exec --skip-git-repo-check -C <cwd> -m gpt-5.1
test.cmd_with_server(&server)
.arg("--skip-git-repo-check")
.arg("-C")
.arg(test.cwd_path())
.arg("-m")
.arg("gpt-5.1")
.write_stdin("prompt from stdin\n")
.assert()
.success();
let request = response_mock.single_request();
assert!(
request.has_message_with_input_texts("user", |texts| {
texts == ["prompt from stdin\n".to_string()]
}),
"missing prompt argument should preserve the existing piped-stdin prompt behavior"
);
Ok(())
}
#[test]
fn exec_without_prompt_argument_rejects_empty_piped_stdin() {
let test = test_codex_exec();
// printf "" | codex exec --skip-git-repo-check -C <cwd>
test.cmd()
.arg("--skip-git-repo-check")
.arg("-C")
.arg(test.cwd_path())
.write_stdin("")
.assert()
.code(1)
.stderr(contains("No prompt provided via stdin."));
}
#[test]
fn exec_dash_prompt_rejects_empty_piped_stdin() {
let test = test_codex_exec();
// printf "" | codex exec --skip-git-repo-check -C <cwd> -
test.cmd()
.arg("--skip-git-repo-check")
.arg("-C")
.arg(test.cwd_path())
.arg("-")
.write_stdin("")
.assert()
.code(1)
.stderr(contains("No prompt provided via stdin."));
}
+3 -3
View File
@@ -27,7 +27,7 @@ async fn spawn_command_under_sandbox(
sandbox_policy,
sandbox_cwd,
stdio_policy,
None,
/*network*/ None,
env,
)
.await
@@ -51,9 +51,9 @@ async fn spawn_command_under_sandbox(
command_cwd,
sandbox_policy,
sandbox_cwd,
false,
/*use_legacy_landlock*/ false,
stdio_policy,
None,
/*network*/ None,
env,
)
.await