mirror of
https://github.com/openai/codex.git
synced 2026-11-10 08:43:33 +00:00
## Why `thread/revert` preserves a thread ID while creating a new immutable rollout. Rollout persistence and filesystem fallback therefore need to distinguish the logical thread from each physical history file. ## What changed - Add a `RolloutId` alias and canonical filename parsing for both ordinary rollouts and reverted rollouts with a distinct rollout ID. - Allow the recorder to create a replacement rollout without changing the thread ID, and add lookup by either the stable thread ID or an exact rollout ID. - Index history references and compression safety checks by rollout ID so multiple rollouts for one thread remain distinct. - Preserve the rollout path selected in SQLite when filesystem repair encounters another rollout for the same thread. ## Testing Add coverage for filename round trips, replacement rollout metadata, thread and rollout lookup, reference indexing, compression, and database path preservation. GitOrigin-RevId: 14b1d0b4a9432b341a1307afc0abadd507e7ae7d
88 lines
2.6 KiB
Rust
88 lines
2.6 KiB
Rust
use codex_protocol::RolloutId;
|
|
use codex_protocol::ThreadId;
|
|
use time::OffsetDateTime;
|
|
use time::PrimitiveDateTime;
|
|
use time::format_description::FormatItem;
|
|
use time::macros::format_description;
|
|
|
|
use crate::compression;
|
|
|
|
/// Parsed canonical rollout basename.
|
|
///
|
|
/// Ordinary rollout filenames encode one ID, which is both the thread ID and rollout ID.
|
|
/// Filenames for reverted threads append an underscore and a distinct rollout ID after the stable
|
|
/// thread ID.
|
|
#[derive(Debug, PartialEq, Eq)]
|
|
pub(crate) struct RolloutFileName {
|
|
timestamp: OffsetDateTime,
|
|
thread_id: ThreadId,
|
|
rollout_id: RolloutId,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "rollout_file_name_tests.rs"]
|
|
mod tests;
|
|
|
|
impl RolloutFileName {
|
|
pub(crate) fn new(
|
|
timestamp: OffsetDateTime,
|
|
thread_id: ThreadId,
|
|
rollout_id: RolloutId,
|
|
) -> Self {
|
|
Self {
|
|
timestamp,
|
|
thread_id,
|
|
rollout_id,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn parse(name: &str) -> Option<Self> {
|
|
let name = compression::parse_rollout_file_name(name)?;
|
|
let core = name.strip_prefix("rollout-")?.strip_suffix(".jsonl")?;
|
|
let timestamp = core.get(..19)?;
|
|
if core.get(19..20)? != "-" {
|
|
return None;
|
|
}
|
|
let ids = core.get(20..)?;
|
|
let (thread_id, rollout_id) = ids.split_once('_').unwrap_or((ids, ids));
|
|
let thread_id = ThreadId::from_string(thread_id).ok()?;
|
|
let rollout_id = RolloutId::from_string(rollout_id).ok()?;
|
|
let format: &[FormatItem] =
|
|
format_description!("[year]-[month]-[day]T[hour]-[minute]-[second]");
|
|
let timestamp = PrimitiveDateTime::parse(timestamp, format)
|
|
.ok()?
|
|
.assume_utc();
|
|
Some(Self {
|
|
timestamp,
|
|
thread_id,
|
|
rollout_id,
|
|
})
|
|
}
|
|
|
|
pub(crate) fn render(&self) -> Result<String, time::error::Format> {
|
|
let format: &[FormatItem] =
|
|
format_description!("[year]-[month]-[day]T[hour]-[minute]-[second]");
|
|
let timestamp = self.timestamp.format(format)?;
|
|
Ok(if self.thread_id == self.rollout_id {
|
|
format!("rollout-{timestamp}-{}.jsonl", self.thread_id)
|
|
} else {
|
|
format!(
|
|
"rollout-{timestamp}-{}_{}.jsonl",
|
|
self.thread_id, self.rollout_id
|
|
)
|
|
})
|
|
}
|
|
|
|
pub(crate) fn timestamp(&self) -> OffsetDateTime {
|
|
self.timestamp
|
|
}
|
|
|
|
pub(crate) fn thread_id(&self) -> ThreadId {
|
|
self.thread_id
|
|
}
|
|
|
|
pub(crate) fn rollout_id(&self) -> RolloutId {
|
|
self.rollout_id
|
|
}
|
|
}
|