Persist paginated items in the local thread store (#32289)

## What changed

- Allow the local thread store to create paginated threads while keeping them unsupported through the app-server API.
- Filter live append items using the history mode recorded with the thread's live recorder, so paginated threads persist canonical `ItemCompleted` events instead of legacy history events.

## Testing

- Add a local-store test that creates a paginated thread and verifies that a paginated item is persisted while a legacy user-message event is omitted.

GitOrigin-RevId: 83d93660afd5f0217213d4f21b5bf2a97745ff19
This commit is contained in:
Owen Lin authored and copyberry committed 2026-07-10 23:26:58 +00:00
1 parent 610f09abcf
commit bfe31598c7
4 files changed
+74 -14

No files matched your search

+55 -7
View File
@@ -326,12 +326,15 @@ mod tests {
use std::sync::Arc;
use codex_protocol::ThreadId;
use codex_protocol::items::TurnItem;
use codex_protocol::items::UserMessageItem;
use codex_protocol::models::BaseInstructions;
use codex_protocol::models::FunctionCallOutputPayload;
use codex_protocol::models::MessagePhase;
use codex_protocol::models::ResponseItem;
use codex_protocol::protocol::AgentMessageEvent;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::ItemCompletedEvent;
use codex_protocol::protocol::RolloutItem;
use codex_protocol::protocol::SessionSource;
use codex_protocol::protocol::ThreadHistoryMode;
@@ -1259,15 +1262,60 @@ mod tests {
.await
.expect_err("resume should fail"),
);
}
let mut create_params = create_thread_params(ThreadId::default());
#[tokio::test]
async fn paginated_live_appends_use_paginated_history_mode() {
let home = TempDir::new().expect("temp dir");
let store = LocalThreadStore::new(test_config(home.path()), /*state_db*/ None);
let thread_id = ThreadId::default();
let mut create_params = create_thread_params(thread_id);
create_params.history_mode = ThreadHistoryMode::Paginated;
assert_paginated_threads_unsupported(
store
.create_thread(create_params)
.await
.expect_err("paginated create should fail"),
);
store
.create_thread(create_params)
.await
.expect("create paginated thread");
let paginated_item = RolloutItem::EventMsg(EventMsg::ItemCompleted(ItemCompletedEvent {
thread_id,
turn_id: "turn-1".to_string(),
item: TurnItem::UserMessage(UserMessageItem {
id: "item-1".to_string(),
client_id: None,
content: Vec::new(),
}),
completed_at_ms: 1,
}));
store
.append_items(AppendThreadItemsParams {
thread_id,
items: vec![
user_message_item("legacy event should not persist"),
paginated_item,
],
})
.await
.expect("append paginated item");
let rollout_path = store
.live_rollout_path(thread_id)
.await
.expect("paginated rollout path");
let (items, _, _) = RolloutRecorder::load_rollout_items(rollout_path.as_path())
.await
.expect("load paginated rollout");
assert!(items.iter().any(|item| {
matches!(
item,
RolloutItem::EventMsg(EventMsg::ItemCompleted(event))
if event.turn_id == "turn-1"
)
}));
assert!(!items.iter().any(|item| {
matches!(
item,
RolloutItem::EventMsg(EventMsg::UserMessage(event))
if event.message == "legacy event should not persist"
)
}));
}
fn create_thread_params(thread_id: ThreadId) -> CreateThreadParams {