mirror of
https://github.com/openai/codex.git
synced 2026-10-26 18:38:47 +00:00
rebase
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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."));
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user