Extract persisted history types into a dedicated crate (#37871)

## What changed

- Add `codex-history` for model-history and persisted-rollout domain types, including `RolloutItem`, `RolloutLine`, `CompactedItem`, and initial/resumed history state.
- Re-export the persisted types from `codex-rollout` and update consumers to use the new crate boundary instead of `codex-protocol`.
- Preserve existing rollout serialization, including legacy numeric compacted-window IDs.

## Testing

- Add `codex-history` tests for rollout JSON round trips, compacted-history compatibility, persisted history modes, and multi-agent version selection.

GitOrigin-RevId: 944daa9297ddd231d3aebbdcb05fff4adf8b4e1b
This commit is contained in:
Adam Perry @ OpenAI authored and copyberry committed 2026-08-10 19:26:52 +00:00
1 parent a1c88e865d
commit 63002bdb26
127 files changed
+791 -673

No files matched your search

+179
View File
@@ -0,0 +1,179 @@
use anyhow::Result;
use pretty_assertions::assert_eq;
use serde_json::json;
use super::*;
#[test]
fn response_item_rollout_line_preserves_shape() -> Result<()> {
let legacy_line = json!({
"timestamp": "2025-01-03T12:00:00.000Z",
"ordinal": 7,
"type": "response_item",
"payload": {
"type": "message",
"role": "user",
"content": [{
"type": "input_text",
"text": "hello",
}],
},
});
let line = serde_json::from_value::<RolloutLine>(legacy_line.clone())?;
let RolloutItem::ResponseItem(item) = &line.item else {
panic!("expected response item");
};
assert!(matches!(item, ResponseItem::Message { .. }));
assert_eq!(serde_json::to_value(line)?, legacy_line);
Ok(())
}
#[test]
fn response_item_replacement_history_preserves_shape() -> Result<()> {
let legacy_item = json!({
"message": "summary",
"replacement_history": [{
"type": "message",
"role": "user",
"content": [{
"type": "input_text",
"text": "hello",
}],
}],
});
let item = serde_json::from_value::<CompactedItem>(legacy_item.clone())?;
let replacement_history = item
.replacement_history
.as_ref()
.expect("replacement history");
assert!(matches!(
&replacement_history[0],
ResponseItem::Message { .. }
));
assert_eq!(serde_json::to_value(item)?, legacy_item);
Ok(())
}
#[test]
fn compacted_item_serializes_window_number_and_id() -> Result<()> {
let item = CompactedItem {
message: "summary".to_string(),
replacement_history: None,
window_number: Some(3),
first_window_id: Some("019b3f6e-0000-7000-8000-000000000001".to_string()),
previous_window_id: Some("019b3f6e-0000-7000-8000-000000000002".to_string()),
window_id: Some("019b3f6e-7a10-7cc3-8b6e-1d09e2f7a001".to_string()),
};
assert_eq!(
serde_json::to_value(item)?,
json!({
"message": "summary",
"window_number": 3,
"first_window_id": "019b3f6e-0000-7000-8000-000000000001",
"previous_window_id": "019b3f6e-0000-7000-8000-000000000002",
"window_id": "019b3f6e-7a10-7cc3-8b6e-1d09e2f7a001",
})
);
Ok(())
}
#[test]
fn compacted_item_migrates_legacy_numeric_window_id() -> Result<()> {
let item = serde_json::from_value::<CompactedItem>(json!({
"message": "summary",
"window_id": 3,
}))?;
assert_eq!(
item,
CompactedItem {
message: "summary".to_string(),
replacement_history: None,
window_number: Some(3),
first_window_id: None,
previous_window_id: None,
window_id: None,
}
);
Ok(())
}
#[test]
fn copied_history_uses_persisted_history_mode() -> Result<()> {
let thread_id = ThreadId::from_string("00000000-0000-0000-0000-000000000001")?;
let session_meta = RolloutItem::SessionMeta(SessionMetaLine {
meta: SessionMeta {
session_id: thread_id.into(),
id: thread_id,
history_mode: ThreadHistoryMode::Legacy,
..SessionMeta::default()
},
git: None,
});
let history = InitialHistory::Resumed(ResumedHistory {
conversation_id: thread_id,
history: Arc::new(vec![session_meta.clone()]),
rollout_path: None,
});
assert_eq!(
history.get_history_mode(ThreadHistoryMode::Paginated),
ThreadHistoryMode::Legacy
);
assert_eq!(
InitialHistory::Forked(vec![session_meta]).get_history_mode(ThreadHistoryMode::Paginated),
ThreadHistoryMode::Legacy
);
assert_eq!(
InitialHistory::New.get_history_mode(ThreadHistoryMode::Paginated),
ThreadHistoryMode::Paginated
);
assert_eq!(
InitialHistory::Resumed(ResumedHistory {
conversation_id: thread_id,
history: Arc::new(Vec::new()),
rollout_path: None,
})
.get_history_mode(ThreadHistoryMode::Paginated),
ThreadHistoryMode::Paginated
);
Ok(())
}
#[test]
fn multi_agent_version_uses_newest_present_session_meta_value() -> Result<()> {
let thread_id = ThreadId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8")?;
let older_meta = SessionMetaLine {
meta: SessionMeta {
session_id: thread_id.into(),
id: thread_id,
multi_agent_version: Some(MultiAgentVersion::V2),
..Default::default()
},
git: None,
};
let newer_meta_without_version = SessionMetaLine {
meta: SessionMeta {
session_id: thread_id.into(),
id: thread_id,
multi_agent_version: None,
..Default::default()
},
git: None,
};
assert_eq!(
multi_agent_version_from_items(
&[
RolloutItem::SessionMeta(older_meta),
RolloutItem::SessionMeta(newer_meta_without_version),
],
Some(thread_id),
),
Some(MultiAgentVersion::V2)
);
Ok(())
}