Enable paginated thread history in app-server (#33364)

## What changed

- Allow `thread/start` to select experimental `historyMode: "paginated"` when the thread store supports turn and item pagination.
- Resume paginated threads with bounded model context and require `excludeTurns: true` instead of loading full history.
- Return `turnsBackwardsCursor` and `itemsBackwardsCursor` from `thread/resume` so clients can hydrate durable history through `thread/turns/list` and `thread/items/list` while newer records arrive through live notifications.
- Reject operations that still require full history, including `initialTurnsPage`, `thread/read` with `includeTurns: true`, `thread/rollback`, and detached review.
- Make item cursors independent of the optional `turnId` filter.

## Testing

- Cover paginated thread start, cold and live resume, backwards cursor hydration, metadata reads, unsupported operations, and stores without pagination support.

GitOrigin-RevId: c42f9b268e29d049843330f474fa3ecde12f5b8a
This commit is contained in:
Owen Lin authored and copyberry committed 2026-07-15 15:29:13 +00:00
1 parent 1bbdb32789
commit da61f7d8e1
26 files changed
+758 -138

No files matched your search

+15 -13
View File
@@ -344,6 +344,10 @@ impl ThreadStore for LocalThreadStore {
Box::pin(async move { list_threads::list_threads(self, params).await })
}
fn supports_paginated_history_lists(&self) -> bool {
true
}
fn list_turns(&self, params: ListTurnsParams) -> ThreadStoreFuture<'_, TurnPage> {
Box::pin(LocalThreadStore::list_turns(self, params))
}
@@ -1244,7 +1248,7 @@ mod tests {
}
#[tokio::test]
async fn paginated_threads_allow_metadata_reads_and_reject_legacy_history_paths() {
async fn paginated_threads_allow_metadata_reads_and_resume_but_reject_legacy_history_paths() {
let home = TempDir::new().expect("temp dir");
let store = LocalThreadStore::new(test_config(home.path()), /*state_db*/ None);
let uuid = uuid::Uuid::from_u128(408);
@@ -1308,18 +1312,16 @@ mod tests {
.await
.expect_err("history load should fail"),
);
assert_paginated_threads_unsupported(
store
.resume_thread(ResumeThreadParams {
thread_id,
rollout_path: Some(rollout_path),
history: None,
include_archived: false,
metadata: thread_metadata(),
})
.await
.expect_err("resume should fail"),
);
store
.resume_thread(ResumeThreadParams {
thread_id,
rollout_path: Some(rollout_path),
history: None,
include_archived: false,
metadata: thread_metadata(),
})
.await
.expect("resume should succeed");
}
#[tokio::test]