Skip to main content

codex_protocol/
compacted_item.rs

1use crate::models::ResponseItem;
2use crate::protocol::CompactedItem;
3use serde::Deserialize;
4
5// Before `window_number` was introduced, the numeric window number was serialized as
6// `window_id`. Accept that shape so existing rollouts remain resumable.
7impl<'de> Deserialize<'de> for CompactedItem {
8    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
9    where
10        D: serde::Deserializer<'de>,
11    {
12        let serialized = SerializedCompactedItem::deserialize(deserializer)?;
13        let mut window_number = serialized.window_number;
14        let window_id = match serialized.window_id {
15            Some(SerializedWindowId::Id(window_id)) => Some(window_id),
16            Some(SerializedWindowId::LegacyWindowNumber(legacy_window_number)) => {
17                window_number.get_or_insert(legacy_window_number);
18                None
19            }
20            None => None,
21        };
22        Ok(Self {
23            message: serialized.message,
24            replacement_history: serialized.replacement_history,
25            window_number,
26            first_window_id: serialized.first_window_id,
27            previous_window_id: serialized.previous_window_id,
28            window_id,
29        })
30    }
31}
32
33#[derive(Deserialize)]
34struct SerializedCompactedItem {
35    message: String,
36    #[serde(default)]
37    replacement_history: Option<Vec<ResponseItem>>,
38    #[serde(default)]
39    window_number: Option<u64>,
40    #[serde(default)]
41    first_window_id: Option<String>,
42    #[serde(default)]
43    previous_window_id: Option<String>,
44    #[serde(default)]
45    window_id: Option<SerializedWindowId>,
46}
47
48#[derive(Deserialize)]
49#[serde(untagged)]
50enum SerializedWindowId {
51    Id(String),
52    LegacyWindowNumber(u64),
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use anyhow::Result;
59    use pretty_assertions::assert_eq;
60    use serde_json::json;
61
62    #[test]
63    fn serializes_window_number_and_id() -> Result<()> {
64        let item = CompactedItem {
65            message: "summary".to_string(),
66            replacement_history: None,
67            window_number: Some(3),
68            first_window_id: Some("019b3f6e-0000-7000-8000-000000000001".to_string()),
69            previous_window_id: Some("019b3f6e-0000-7000-8000-000000000002".to_string()),
70            window_id: Some("019b3f6e-7a10-7cc3-8b6e-1d09e2f7a001".to_string()),
71        };
72
73        assert_eq!(
74            serde_json::to_value(item)?,
75            json!({
76                "message": "summary",
77                "window_number": 3,
78                "first_window_id": "019b3f6e-0000-7000-8000-000000000001",
79                "previous_window_id": "019b3f6e-0000-7000-8000-000000000002",
80                "window_id": "019b3f6e-7a10-7cc3-8b6e-1d09e2f7a001",
81            })
82        );
83        Ok(())
84    }
85
86    #[test]
87    fn migrates_legacy_numeric_window_id() -> Result<()> {
88        let item = serde_json::from_value::<CompactedItem>(json!({
89            "message": "summary",
90            "window_id": 3,
91        }))?;
92
93        assert_eq!(
94            item,
95            CompactedItem {
96                message: "summary".to_string(),
97                replacement_history: None,
98                window_number: Some(3),
99                first_window_id: None,
100                previous_window_id: None,
101                window_id: None,
102            }
103        );
104        Ok(())
105    }
106}