From df70ca822a84bbbac2878a7c51a0fbca5f005ae4 Mon Sep 17 00:00:00 2001 From: Richard Lee Date: Mon, 6 Jul 2026 10:47:41 -0700 Subject: [PATCH] [thread-store] add updated item list API --- codex-rs/thread-store/src/in_memory.rs | 20 ++++++++++++++ codex-rs/thread-store/src/lib.rs | 2 ++ codex-rs/thread-store/src/store.rs | 14 ++++++++++ codex-rs/thread-store/src/types.rs | 38 ++++++++++++++++++++++++++ 4 files changed, 74 insertions(+) diff --git a/codex-rs/thread-store/src/in_memory.rs b/codex-rs/thread-store/src/in_memory.rs index cb3f198ff3..c80d013ba2 100644 --- a/codex-rs/thread-store/src/in_memory.rs +++ b/codex-rs/thread-store/src/in_memory.rs @@ -52,6 +52,7 @@ mod tests { use super::*; use crate::ListItemsParams; use crate::ListTurnsParams; + use crate::ListUpdatedItemsParams; use crate::SortDirection; use crate::StoredTurnItemsView; use crate::ThreadPersistenceMetadata; @@ -99,6 +100,25 @@ mod tests { operation: "list_items" } )); + + let updated_items_err = store + .list_updated_items(ListUpdatedItemsParams { + thread_id, + turn_id: None, + after_updated_at: None, + through_updated_at: None, + cursor: None, + page_size: 10, + sort_direction: SortDirection::Asc, + }) + .await + .expect_err("default list_updated_items should be unsupported"); + assert!(matches!( + updated_items_err, + ThreadStoreError::Unsupported { + operation: "list_updated_items" + } + )); } #[tokio::test] diff --git a/codex-rs/thread-store/src/lib.rs b/codex-rs/thread-store/src/lib.rs index 34a0ab2ba1..ebb7b6b44d 100644 --- a/codex-rs/thread-store/src/lib.rs +++ b/codex-rs/thread-store/src/lib.rs @@ -33,6 +33,7 @@ pub use types::ItemPage; pub use types::ListItemsParams; pub use types::ListThreadsParams; pub use types::ListTurnsParams; +pub use types::ListUpdatedItemsParams; pub use types::LoadThreadHistoryParams; pub use types::ReadThreadByRolloutPathParams; pub use types::ReadThreadParams; @@ -55,3 +56,4 @@ pub use types::ThreadSearchPage; pub use types::ThreadSortKey; pub use types::TurnPage; pub use types::UpdateThreadMetadataParams; +pub use types::UpdatedItemsPage; diff --git a/codex-rs/thread-store/src/store.rs b/codex-rs/thread-store/src/store.rs index 67ee603f49..77ba42bb58 100644 --- a/codex-rs/thread-store/src/store.rs +++ b/codex-rs/thread-store/src/store.rs @@ -12,6 +12,7 @@ use crate::ItemPage; use crate::ListItemsParams; use crate::ListThreadsParams; use crate::ListTurnsParams; +use crate::ListUpdatedItemsParams; use crate::LoadThreadHistoryParams; use crate::ReadThreadByRolloutPathParams; use crate::ReadThreadParams; @@ -25,6 +26,7 @@ use crate::ThreadStoreError; use crate::ThreadStoreResult; use crate::TurnPage; use crate::UpdateThreadMetadataParams; +use crate::UpdatedItemsPage; /// Future returned by [`ThreadStore`] operations. pub type ThreadStoreFuture<'a, T> = Pin> + Send + 'a>>; @@ -120,6 +122,18 @@ pub trait ThreadStore: Any + Send + Sync { }) } + /// Lists latest item snapshots changed within a timestamp-bounded interval. + fn list_updated_items( + &self, + _params: ListUpdatedItemsParams, + ) -> ThreadStoreFuture<'_, UpdatedItemsPage> { + Box::pin(async { + Err(ThreadStoreError::Unsupported { + operation: "list_updated_items", + }) + }) + } + /// Applies a literal metadata patch and returns the updated thread. /// /// Implementations should apply the supplied fields directly. Policy such as deciding whether diff --git a/codex-rs/thread-store/src/types.rs b/codex-rs/thread-store/src/types.rs index 4e688eca6f..c6f1dea7c6 100644 --- a/codex-rs/thread-store/src/types.rs +++ b/codex-rs/thread-store/src/types.rs @@ -389,6 +389,30 @@ pub struct ListItemsParams { pub sort_direction: SortDirection, } +/// Parameters for listing items changed within a timestamp-bounded interval. +/// +/// When both bounds are present, implementations should interpret the interval as +/// `(after_updated_at, through_updated_at]`. Bounds remain optional so each store owns validation +/// of partial ranges. Callers should use [`crate::ThreadStore::list_items`] when neither bound is +/// present, and must reuse the same bounds when following a page cursor. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct ListUpdatedItemsParams { + /// Thread id to read. + pub thread_id: ThreadId, + /// Optional turn id to filter by. When omitted, returns items across the thread. + pub turn_id: Option, + /// Exclusive lower bound for item updates. + pub after_updated_at: Option>, + /// Inclusive upper bound for item updates. + pub through_updated_at: Option>, + /// Opaque cursor returned by a previous list call. + pub cursor: Option, + /// Maximum number of items to return. + pub page_size: usize, + /// Sort direction requested by the caller. + pub sort_direction: SortDirection, +} + /// A projected app-server `ThreadItem` snapshot within a turn. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] pub struct StoredThreadItem { @@ -410,6 +434,20 @@ pub struct ItemPage { pub backwards_cursor: Option, } +/// A page of items changed within a timestamp-bounded interval. +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +pub struct UpdatedItemsPage { + /// Latest snapshots of items changed within the requested interval. + pub items: Vec, + /// Opaque cursor to continue listing with the same timestamp bounds. + pub next_cursor: Option, + /// Opaque cursor for fetching in the opposite direction with the same timestamp bounds. + pub backwards_cursor: Option, + /// Store-confirmed inclusive upper bound. On success, this must match the requested upper + /// bound, including when that bound is absent. + pub read_through_updated_at: Option>, +} + /// Store-owned thread metadata used by list/read/resume responses. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct StoredThread {