[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
+15
View File
@@ -14,6 +14,7 @@ use crate::protocol::PatchApplyBeginEvent;
use crate::protocol::PatchApplyEndEvent;
use crate::protocol::PatchApplyStatus;
use crate::protocol::UserMessageEvent;
use crate::protocol::ViewImageToolCallEvent;
use crate::protocol::WebSearchEndEvent;
use crate::user_input::ByteRange;
use crate::user_input::TextElement;
@@ -38,6 +39,7 @@ pub enum TurnItem {
Plan(PlanItem),
Reasoning(ReasoningItem),
WebSearch(WebSearchItem),
ImageView(ImageViewItem),
ImageGeneration(ImageGenerationItem),
FileChange(FileChangeItem),
ContextCompaction(ContextCompactionItem),
@@ -121,6 +123,12 @@ pub struct WebSearchItem {
pub action: WebSearchAction,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
pub struct ImageViewItem {
pub id: String,
pub path: AbsolutePathBuf,
}
#[derive(Debug, Clone, Deserialize, Serialize, TS, JsonSchema, PartialEq)]
pub struct ImageGenerationItem {
pub id: String,
@@ -439,6 +447,7 @@ impl TurnItem {
TurnItem::Plan(item) => item.id.clone(),
TurnItem::Reasoning(item) => item.id.clone(),
TurnItem::WebSearch(item) => item.id.clone(),
TurnItem::ImageView(item) => item.id.clone(),
TurnItem::ImageGeneration(item) => item.id.clone(),
TurnItem::FileChange(item) => item.id.clone(),
TurnItem::ContextCompaction(item) => item.id.clone(),
@@ -452,6 +461,12 @@ impl TurnItem {
TurnItem::AgentMessage(item) => item.as_legacy_events(),
TurnItem::Plan(_) => Vec::new(),
TurnItem::WebSearch(item) => vec![item.as_legacy_event()],
TurnItem::ImageView(item) => {
vec![EventMsg::ViewImageToolCall(ViewImageToolCallEvent {
call_id: item.id.clone(),
path: item.path.clone(),
})]
}
TurnItem::ImageGeneration(item) => vec![item.as_legacy_event()],
TurnItem::FileChange(item) => item
.as_legacy_end_event(String::new())
+1
View File
@@ -1836,6 +1836,7 @@ impl HasLegacyEvent for ItemStartedEvent {
TurnItem::WebSearch(item) => vec![EventMsg::WebSearchBegin(WebSearchBeginEvent {
call_id: item.id.clone(),
})],
TurnItem::ImageView(_) => Vec::new(),
TurnItem::ImageGeneration(item) => {
vec![EventMsg::ImageGenerationBegin(ImageGenerationBeginEvent {
call_id: item.id.clone(),