mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Move apply-patch file changes into turn items (#20540)
## Why Apply-patch file changes are now part of the core turn item stream, so v2 clients can consume the same first-class item lifecycle path used by other turn items instead of relying on app-server-specific remapping from legacy patch events. ## What changed - Added a core `TurnItem::FileChange` carrying apply-patch changes and completion metadata. - Updated the apply-patch tool emitter to send `ItemStarted` / `ItemCompleted` with the new `FileChange` item while preserving legacy `PatchApplyBegin` / `PatchApplyEnd` fan-out. - Updated app-server v2 conversion to render the new core item directly and stopped `event_mapping` from remapping old patch begin/end events into item notifications. - Kept thread history reconstruction based on the existing old apply-patch events for rollout compatibility. ## Verification - `cargo test -p codex-protocol -p codex-app-server-protocol` - `cargo test -p codex-core --test all apply_patch_tool_executes_and_emits_patch_events` - `cargo test -p codex-app-server bespoke_event_handling`
This commit is contained in:
@@ -1841,6 +1841,7 @@ impl HasLegacyEvent for ItemStartedEvent {
|
||||
call_id: item.id.clone(),
|
||||
})]
|
||||
}
|
||||
TurnItem::FileChange(item) => vec![item.as_legacy_begin_event(self.turn_id.clone())],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -1859,7 +1860,13 @@ pub trait HasLegacyEvent {
|
||||
|
||||
impl HasLegacyEvent for ItemCompletedEvent {
|
||||
fn as_legacy_events(&self, show_raw_agent_reasoning: bool) -> Vec<EventMsg> {
|
||||
self.item.as_legacy_events(show_raw_agent_reasoning)
|
||||
match &self.item {
|
||||
TurnItem::FileChange(item) => item
|
||||
.as_legacy_end_event(self.turn_id.clone())
|
||||
.into_iter()
|
||||
.collect(),
|
||||
_ => self.item.as_legacy_events(show_raw_agent_reasoning),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3928,6 +3935,7 @@ pub struct CollabResumeEndEvent {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::items::FileChangeItem;
|
||||
use crate::items::ImageGenerationItem;
|
||||
use crate::items::UserMessageItem;
|
||||
use crate::items::WebSearchItem;
|
||||
@@ -4630,6 +4638,41 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_started_event_from_file_change_emits_patch_begin_event() {
|
||||
let event = ItemStartedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
item: TurnItem::FileChange(FileChangeItem {
|
||||
id: "patch-1".into(),
|
||||
changes: [(
|
||||
PathBuf::from("new.txt"),
|
||||
FileChange::Add {
|
||||
content: "hello".into(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
status: None,
|
||||
auto_approved: Some(true),
|
||||
stdout: None,
|
||||
stderr: None,
|
||||
}),
|
||||
};
|
||||
|
||||
let legacy_events = event.as_legacy_events(/*show_raw_agent_reasoning*/ false);
|
||||
assert_eq!(legacy_events.len(), 1);
|
||||
match &legacy_events[0] {
|
||||
EventMsg::PatchApplyBegin(event) => {
|
||||
assert_eq!(event.call_id, "patch-1");
|
||||
assert_eq!(event.turn_id, "turn-1");
|
||||
assert!(event.auto_approved);
|
||||
assert!(event.changes.contains_key(&PathBuf::from("new.txt")));
|
||||
}
|
||||
_ => panic!("expected PatchApplyBegin event"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_completed_event_from_image_generation_emits_end_event() {
|
||||
let event = ItemCompletedEvent {
|
||||
@@ -4661,6 +4704,43 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn item_completed_event_from_file_change_emits_patch_end_event() {
|
||||
let event = ItemCompletedEvent {
|
||||
thread_id: ThreadId::new(),
|
||||
turn_id: "turn-1".into(),
|
||||
item: TurnItem::FileChange(FileChangeItem {
|
||||
id: "patch-1".into(),
|
||||
changes: [(
|
||||
PathBuf::from("new.txt"),
|
||||
FileChange::Add {
|
||||
content: "hello".into(),
|
||||
},
|
||||
)]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
status: Some(PatchApplyStatus::Completed),
|
||||
auto_approved: None,
|
||||
stdout: Some("Done!".into()),
|
||||
stderr: Some(String::new()),
|
||||
}),
|
||||
};
|
||||
|
||||
let legacy_events = event.as_legacy_events(/*show_raw_agent_reasoning*/ false);
|
||||
assert_eq!(legacy_events.len(), 1);
|
||||
match &legacy_events[0] {
|
||||
EventMsg::PatchApplyEnd(event) => {
|
||||
assert_eq!(event.call_id, "patch-1");
|
||||
assert_eq!(event.turn_id, "turn-1");
|
||||
assert_eq!(event.stdout, "Done!");
|
||||
assert!(event.success);
|
||||
assert_eq!(event.status, PatchApplyStatus::Completed);
|
||||
assert!(event.changes.contains_key(&PathBuf::from("new.txt")));
|
||||
}
|
||||
_ => panic!("expected PatchApplyEnd event"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rollback_failed_error_does_not_affect_turn_status() {
|
||||
let event = ErrorEvent {
|
||||
|
||||
Reference in New Issue
Block a user