Add item streaming events (#5546)

Adds AgentMessageContentDelta, ReasoningContentDelta,
ReasoningRawContentDelta item streaming events while maintaining
compatibility for old events.

---------

Co-authored-by: Owen Lin <owen@openai.com>
This commit is contained in:
pakrym-oai
2025-10-29 15:33:57 -07:00
committed by GitHub
parent 815ae4164a
commit 3429e82e45
18 changed files with 662 additions and 243 deletions

View File

@@ -1262,6 +1262,10 @@ async fn history_dedupes_streamed_and_final_messages_across_turns() {
// Build a small SSE stream with deltas and a final assistant message.
// We emit the same body for all 3 turns; ids vary but are unused by assertions.
let sse_raw = r##"[
{"type":"response.output_item.added", "item":{
"type":"message", "role":"assistant",
"content":[{"type":"output_text","text":""}]
}},
{"type":"response.output_text.delta", "delta":"Hey "},
{"type":"response.output_text.delta", "delta":"there"},
{"type":"response.output_text.delta", "delta":"!\n"},

View File

@@ -9,7 +9,12 @@ use codex_protocol::items::TurnItem;
use codex_protocol::user_input::UserInput;
use core_test_support::responses::ev_assistant_message;
use core_test_support::responses::ev_completed;
use core_test_support::responses::ev_message_item_added;
use core_test_support::responses::ev_output_text_delta;
use core_test_support::responses::ev_reasoning_item;
use core_test_support::responses::ev_reasoning_item_added;
use core_test_support::responses::ev_reasoning_summary_text_delta;
use core_test_support::responses::ev_reasoning_text_delta;
use core_test_support::responses::ev_response_created;
use core_test_support::responses::ev_web_search_call_added;
use core_test_support::responses::ev_web_search_call_done;
@@ -234,3 +239,181 @@ async fn web_search_item_is_emitted() -> anyhow::Result<()> {
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn agent_message_content_delta_has_item_metadata() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let TestCodex {
codex,
session_configured,
..
} = test_codex().build(&server).await?;
let stream = sse(vec![
ev_response_created("resp-1"),
ev_message_item_added("msg-1", ""),
ev_output_text_delta("streamed response"),
ev_assistant_message("msg-1", "streamed response"),
ev_completed("resp-1"),
]);
mount_sse_once_match(&server, any(), stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "please stream text".into(),
}],
})
.await?;
let (started_turn_id, started_item) = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ItemStarted(ItemStartedEvent {
turn_id,
item: TurnItem::AgentMessage(item),
..
}) => Some((turn_id.clone(), item.clone())),
_ => None,
})
.await;
let delta_event = wait_for_event_match(&codex, |ev| match ev {
EventMsg::AgentMessageContentDelta(event) => Some(event.clone()),
_ => None,
})
.await;
let legacy_delta = wait_for_event_match(&codex, |ev| match ev {
EventMsg::AgentMessageDelta(event) => Some(event.clone()),
_ => None,
})
.await;
let completed_item = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ItemCompleted(ItemCompletedEvent {
item: TurnItem::AgentMessage(item),
..
}) => Some(item.clone()),
_ => None,
})
.await;
let session_id = session_configured.session_id.to_string();
assert_eq!(delta_event.thread_id, session_id);
assert_eq!(delta_event.turn_id, started_turn_id);
assert_eq!(delta_event.item_id, started_item.id);
assert_eq!(delta_event.delta, "streamed response");
assert_eq!(legacy_delta.delta, "streamed response");
assert_eq!(completed_item.id, started_item.id);
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_content_delta_has_item_metadata() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let TestCodex { codex, .. } = test_codex().build(&server).await?;
let stream = sse(vec![
ev_response_created("resp-1"),
ev_reasoning_item_added("reasoning-1", &[""]),
ev_reasoning_summary_text_delta("step one"),
ev_reasoning_item("reasoning-1", &["step one"], &[]),
ev_completed("resp-1"),
]);
mount_sse_once_match(&server, any(), stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "reason through it".into(),
}],
})
.await?;
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ItemStarted(ItemStartedEvent {
item: TurnItem::Reasoning(item),
..
}) => Some(item.clone()),
_ => None,
})
.await;
let delta_event = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ReasoningContentDelta(event) => Some(event.clone()),
_ => None,
})
.await;
let legacy_delta = wait_for_event_match(&codex, |ev| match ev {
EventMsg::AgentReasoningDelta(event) => Some(event.clone()),
_ => None,
})
.await;
assert_eq!(delta_event.item_id, reasoning_item.id);
assert_eq!(delta_event.delta, "step one");
assert_eq!(legacy_delta.delta, "step one");
Ok(())
}
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn reasoning_raw_content_delta_respects_flag() -> anyhow::Result<()> {
skip_if_no_network!(Ok(()));
let server = start_mock_server().await;
let TestCodex { codex, .. } = test_codex()
.with_config(|config| {
config.show_raw_agent_reasoning = true;
})
.build(&server)
.await?;
let stream = sse(vec![
ev_response_created("resp-1"),
ev_reasoning_item_added("reasoning-raw", &[""]),
ev_reasoning_text_delta("raw detail"),
ev_reasoning_item("reasoning-raw", &["complete"], &["raw detail"]),
ev_completed("resp-1"),
]);
mount_sse_once_match(&server, any(), stream).await;
codex
.submit(Op::UserInput {
items: vec![UserInput::Text {
text: "show raw reasoning".into(),
}],
})
.await?;
let reasoning_item = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ItemStarted(ItemStartedEvent {
item: TurnItem::Reasoning(item),
..
}) => Some(item.clone()),
_ => None,
})
.await;
let delta_event = wait_for_event_match(&codex, |ev| match ev {
EventMsg::ReasoningRawContentDelta(event) => Some(event.clone()),
_ => None,
})
.await;
let legacy_delta = wait_for_event_match(&codex, |ev| match ev {
EventMsg::AgentReasoningRawContentDelta(event) => Some(event.clone()),
_ => None,
})
.await;
assert_eq!(delta_event.item_id, reasoning_item.id);
assert_eq!(delta_event.delta, "raw detail");
assert_eq!(legacy_delta.delta, "raw detail");
Ok(())
}