mirror of
https://github.com/openai/codex.git
synced 2026-09-05 15:18:41 +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
122 lines
2.8 KiB
Rust
122 lines
2.8 KiB
Rust
use std::fmt::Display;
|
|
|
|
use schemars::JsonSchema;
|
|
use schemars::r#gen::SchemaGenerator;
|
|
use schemars::schema::Schema;
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
use ts_rs::TS;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, TS, Hash)]
|
|
#[ts(type = "string")]
|
|
/// Identifier for a Codex thread.
|
|
///
|
|
/// Codex-generated thread IDs are UUIDv7, and some use cases rely on that.
|
|
pub struct ThreadId {
|
|
pub(crate) uuid: Uuid,
|
|
}
|
|
|
|
/// Identifier encoded in a rollout filename.
|
|
///
|
|
/// Rollout IDs use the same UUID representation as thread IDs. Ordinary rollout files use the
|
|
/// thread ID as their rollout ID; \`thread/revert\` creates a new rollout file with a distinct
|
|
/// rollout ID while preserving the thread ID.
|
|
pub type RolloutId = ThreadId;
|
|
|
|
impl ThreadId {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
uuid: Uuid::now_v7(),
|
|
}
|
|
}
|
|
|
|
/// Construct an identifier from a UUID's 128-bit representation.
|
|
pub fn from_u128(value: u128) -> Self {
|
|
Self {
|
|
uuid: Uuid::from_u128(value),
|
|
}
|
|
}
|
|
|
|
pub fn from_string(s: &str) -> Result<Self, uuid::Error> {
|
|
Ok(Self {
|
|
uuid: Uuid::parse_str(s)?,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl TryFrom<&str> for ThreadId {
|
|
type Error = uuid::Error;
|
|
|
|
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
|
Self::from_string(value)
|
|
}
|
|
}
|
|
|
|
impl TryFrom<String> for ThreadId {
|
|
type Error = uuid::Error;
|
|
|
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
|
Self::from_string(value.as_str())
|
|
}
|
|
}
|
|
|
|
impl From<ThreadId> for String {
|
|
fn from(value: ThreadId) -> Self {
|
|
value.to_string()
|
|
}
|
|
}
|
|
|
|
impl Default for ThreadId {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl Display for ThreadId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
Display::fmt(&self.uuid, f)
|
|
}
|
|
}
|
|
|
|
impl Serialize for ThreadId {
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
where
|
|
S: serde::Serializer,
|
|
{
|
|
serializer.collect_str(&self.uuid)
|
|
}
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for ThreadId {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let value = String::deserialize(deserializer)?;
|
|
let uuid = Uuid::parse_str(&value).map_err(serde::de::Error::custom)?;
|
|
Ok(Self { uuid })
|
|
}
|
|
}
|
|
|
|
impl JsonSchema for ThreadId {
|
|
fn schema_name() -> String {
|
|
"ThreadId".to_string()
|
|
}
|
|
|
|
fn json_schema(generator: &mut SchemaGenerator) -> Schema {
|
|
<String>::json_schema(generator)
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_thread_id_default_is_not_zeroes() {
|
|
let id = ThreadId::default();
|
|
assert_ne!(id.uuid, Uuid::nil());
|
|
}
|
|
}
|