[codex] Emit image view as core item (#20512)

## Why

Image-view results should be represented as a core-produced turn item
instead of being reconstructed by app-server. At the same time, existing
rollout/history paths still understand the legacy `ViewImageToolCall`
event, so this keeps that event as compatibility output generated from
the new item lifecycle.

## What changed

- Added `TurnItem::ImageView` to `codex-protocol`.
- Emitted image-view item start/completion directly from the core
`view_image` handler.
- Kept `ViewImageToolCall` as a legacy event and generate it from
completed `TurnItem::ImageView` items.
- Kept `thread_history.rs` on the legacy `ViewImageToolCall` replay
path, with `ImageView` item lifecycle events ignored there.
- Updated app-server protocol conversion, rollout persistence, and
affected exhaustive event matches for the new item plus legacy fan-out
shape.

## Verification

- `cargo test -p codex-protocol -p codex-app-server-protocol -p
codex-rollout -p codex-rollout-trace -p codex-mcp-server -p
codex-app-server --lib`
- `cargo test -p codex-core --test all
view_image_tool_attaches_local_image`
- `just fix -p codex-protocol -p codex-core -p codex-app-server-protocol
-p codex-app-server -p codex-rollout -p codex-rollout-trace -p
codex-mcp-server`
- `git diff --check`
This commit is contained in:
pakrym-oai
2026-05-01 11:28:30 -07:00
committed by GitHub
Unverified
parent 610eefb86b
commit aed74e5ee4
10 changed files with 83 additions and 46 deletions
@@ -356,6 +356,7 @@ impl ThreadHistoryBuilder {
| codex_protocol::items::TurnItem::AgentMessage(_)
| codex_protocol::items::TurnItem::Reasoning(_)
| codex_protocol::items::TurnItem::WebSearch(_)
| codex_protocol::items::TurnItem::ImageView(_)
| codex_protocol::items::TurnItem::ImageGeneration(_)
| codex_protocol::items::TurnItem::FileChange(_)
| codex_protocol::items::TurnItem::ContextCompaction(_) => {}
@@ -378,6 +379,7 @@ impl ThreadHistoryBuilder {
| codex_protocol::items::TurnItem::AgentMessage(_)
| codex_protocol::items::TurnItem::Reasoning(_)
| codex_protocol::items::TurnItem::WebSearch(_)
| codex_protocol::items::TurnItem::ImageView(_)
| codex_protocol::items::TurnItem::ImageGeneration(_)
| codex_protocol::items::TurnItem::FileChange(_)
| codex_protocol::items::TurnItem::ContextCompaction(_) => {}
@@ -6463,6 +6463,10 @@ impl From<CoreTurnItem> for ThreadItem {
query: search.query,
action: Some(WebSearchAction::from(search.action)),
},
CoreTurnItem::ImageView(image) => ThreadItem::ImageView {
id: image.id,
path: image.path,
},
CoreTurnItem::ImageGeneration(image) => ThreadItem::ImageGeneration {
id: image.id,
status: image.status,
@@ -8089,6 +8093,7 @@ mod tests {
use codex_protocol::items::AgentMessageContent;
use codex_protocol::items::AgentMessageItem;
use codex_protocol::items::FileChangeItem;
use codex_protocol::items::ImageViewItem;
use codex_protocol::items::ReasoningItem;
use codex_protocol::items::TurnItem;
use codex_protocol::items::UserMessageItem;
@@ -10370,6 +10375,19 @@ mod tests {
}
);
let image_view_item = TurnItem::ImageView(ImageViewItem {
id: "view-image-1".to_string(),
path: test_path_buf("/tmp/view-image.png").abs(),
});
assert_eq!(
ThreadItem::from(image_view_item),
ThreadItem::ImageView {
id: "view-image-1".to_string(),
path: test_path_buf("/tmp/view-image.png").abs(),
}
);
let file_change_item = TurnItem::FileChange(FileChangeItem {
id: "patch-1".to_string(),
changes: [(