Switch to uuid_v7 and tighten ConversationId usage (#3819)

Make sure conversations have a timestamp.
This commit is contained in:
pakrym-oai
2025-09-18 07:37:03 -07:00
committed by GitHub
parent 4c97eeb32a
commit d4aba772cb
9 changed files with 73 additions and 25 deletions

View File

@@ -4,9 +4,9 @@ name = "codex-core"
version = { workspace = true }
[lib]
doctest = false
name = "codex_core"
path = "src/lib.rs"
doctest = false
[lints]
workspace = true
@@ -41,7 +41,12 @@ similar = "2.7.0"
strum_macros = "0.27.2"
tempfile = "3"
thiserror = "2.0.16"
time = { version = "0.3", features = ["formatting", "parsing", "local-offset", "macros"] }
time = { version = "0.3", features = [
"formatting",
"parsing",
"local-offset",
"macros",
] }
tokio = { version = "1", features = [
"io-std",
"macros",

View File

@@ -36,7 +36,7 @@ tokio = { version = "1", features = [
toml = "0.9"
tracing = { version = "0.1.41", features = ["log"] }
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }
uuid = { version = "1", features = ["serde", "v4"] }
uuid = { version = "1", features = ["serde", "v7"] }
[dev-dependencies]
assert_cmd = "2"

View File

@@ -814,7 +814,7 @@ impl CodexMessageProcessor {
return;
};
let required_suffix = format!("{}.jsonl", conversation_id.0);
let required_suffix = format!("{conversation_id}.jsonl");
let Some(file_name) = canonical_rollout_path.file_name().map(OsStr::to_owned) else {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
@@ -1414,13 +1414,13 @@ mod tests {
#[test]
fn extract_conversation_summary_prefers_plain_user_messages() {
let conversation_id =
ConversationId(Uuid::parse_str("3f941c35-29b3-493b-b0a4-e25800d9aeb0").unwrap());
ConversationId::from_string("3f941c35-29b3-493b-b0a4-e25800d9aeb0").unwrap();
let timestamp = Some("2025-09-05T16:53:11.850Z".to_string());
let path = PathBuf::from("rollout.jsonl");
let head = vec![
json!({
"id": conversation_id.0,
"id": conversation_id.to_string(),
"timestamp": timestamp,
"cwd": "/",
"originator": "codex",

View File

@@ -36,7 +36,6 @@ use serde_json::json;
use std::sync::Arc;
use tokio::sync::Mutex;
use tokio::task;
use uuid::Uuid;
pub(crate) struct MessageProcessor {
codex_message_processor: CodexMessageProcessor,
@@ -484,8 +483,8 @@ impl MessageProcessor {
return;
}
};
let conversation_id = match Uuid::parse_str(&conversation_id) {
Ok(id) => ConversationId::from(id),
let conversation_id = match ConversationId::from_string(&conversation_id) {
Ok(id) => id,
Err(e) => {
tracing::error!("Failed to parse conversation_id: {e}");
let result = CallToolResult {

View File

@@ -142,7 +142,7 @@ async fn test_list_and_resume_conversations() {
} = to_response::<ResumeConversationResponse>(resume_resp)
.expect("deserialize resumeConversation response");
// conversation id should be a valid UUID
let _: uuid::Uuid = conversation_id.into();
assert!(!conversation_id.to_string().is_empty());
}
fn create_fake_rollout(codex_home: &Path, filename_ts: &str, meta_rfc3339: &str, preview: &str) {

View File

@@ -136,7 +136,7 @@ async fn test_send_message_session_not_found() {
.expect("timeout")
.expect("init");
let unknown = ConversationId(uuid::Uuid::new_v4());
let unknown = ConversationId::new();
let req_id = mcp
.send_send_user_message_request(SendUserMessageParams {
conversation_id: unknown,

View File

@@ -23,8 +23,12 @@ strum = "0.27.2"
strum_macros = "0.27.2"
sys-locale = "0.3.2"
tracing = "0.1.41"
ts-rs = { version = "11", features = ["uuid-impl", "serde-json-impl", "no-serde-warnings"] }
uuid = { version = "1", features = ["serde", "v4"] }
ts-rs = { version = "11", features = [
"uuid-impl",
"serde-json-impl",
"no-serde-warnings",
] }
uuid = { version = "1", features = ["serde", "v7"] }
[dev-dependencies]
pretty_assertions = "1.4.1"

View File

@@ -19,13 +19,23 @@ use strum_macros::Display;
use ts_rs::TS;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, TS, Hash)]
#[ts(type = "string")]
pub struct ConversationId(pub Uuid);
pub struct ConversationId {
uuid: Uuid,
}
impl ConversationId {
pub fn new() -> Self {
Self(Uuid::new_v4())
Self {
uuid: Uuid::now_v7(),
}
}
pub fn from_string(s: &str) -> Result<Self, uuid::Error> {
Ok(Self {
uuid: Uuid::parse_str(s)?,
})
}
}
@@ -37,19 +47,27 @@ impl Default for ConversationId {
impl Display for ConversationId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
write!(f, "{}", self.uuid)
}
}
impl From<Uuid> for ConversationId {
fn from(value: Uuid) -> Self {
Self(value)
impl Serialize for ConversationId {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.collect_str(&self.uuid)
}
}
impl From<ConversationId> for Uuid {
fn from(value: ConversationId) -> Self {
value.0
impl<'de> Deserialize<'de> for ConversationId {
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 })
}
}
@@ -719,6 +737,27 @@ mod tests {
#[test]
fn test_conversation_id_default_is_not_zeroes() {
let id = ConversationId::default();
assert_ne!(id.0, Uuid::nil());
assert_ne!(id.uuid, Uuid::nil());
}
#[test]
fn conversation_id_serializes_as_plain_string() {
let id = ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
assert_eq!(
json!("67e55044-10b1-426f-9247-bb680e5fe0c8"),
serde_json::to_value(id).unwrap()
);
}
#[test]
fn conversation_id_deserializes_from_plain_string() {
let id: ConversationId =
serde_json::from_value(json!("67e55044-10b1-426f-9247-bb680e5fe0c8")).unwrap();
assert_eq!(
ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap(),
id,
);
}
}

View File

@@ -1252,7 +1252,8 @@ mod tests {
/// amount of nesting.
#[test]
fn serialize_event() {
let conversation_id = ConversationId(uuid::uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"));
let conversation_id =
ConversationId::from_string("67e55044-10b1-426f-9247-bb680e5fe0c8").unwrap();
let rollout_file = NamedTempFile::new().unwrap();
let event = Event {
id: "1234".to_string(),