Cloud Message Pagination#
GET /threads/{id}/messages caps responses at 200 rows. Before PR #6963, CloudMessagePersistence.load issued a single request with no pagination, silently dropping messages beyond that cap when reloading long threads. The fix added cursor-based pagination to both the query type and the load loop.
The API Gap (Historical vs. Current)#
Before PR #6963 — AssistantCloudThreadMessageListQuery accepted only format?: string. No limit or after cursor could be passed, even though the underlying endpoint had supported them since assistant-cloud#53.
After PR #6963 (current) — the query type gains limit?: number and after?: string, matching the query shape already present on the project-scoped variant AssistantCloudProjectThreadMessageListQuery.
| API | Query type | limit? | after cursor? |
|---|---|---|---|
Thread message list (/threads/{id}/messages) | AssistantCloudThreadMessageListQuery | ✅ (as of #6963) | ✅ (as of #6963) |
Project thread message list (/projects/threads/{id}/messages) | AssistantCloudProjectThreadMessageListQuery | ✅ | ✅ |
Thread list (/threads) | AssistantCloudThreadsListQuery | ✅ | ✅ |
How CloudMessagePersistence.load Works Now#
CloudMessagePersistence.load walks pages using a keyset cursor:
- Page size is fixed at
CLOUD_MESSAGE_PAGE_SIZE = 200, the endpoint's maximum. - Each iteration passes
{ limit: 200, after: lastMessageId }and appends fresh messages to the result. - A short page (
length < 200) signals the final page and ends the loop. - A
seenset guards against cursor invalidation: if the server can't resolve anaftercursor (e.g., message deleted mid-walk), it replays an earlier page; duplicate IDs are filtered and the walk terminates . - After loading, all remote IDs are written into
idMappingso thatisPersisted()correctly recognizes already-stored messages.
The server orders results desc(height), desc(created_at), desc(id) — newest-first. The cursor walks toward older messages. Consumers that need chronological order (e.g., AssistantCloudThreadHistoryAdapter) call .reverse() after loading.
Affected Consumers#
All message-loading flows go through CloudMessagePersistence.load:
AssistantCloudThreadHistoryAdapter(packages/core) — the React runtime adapter that loads history on thread switch.MessagePersistence.loadMessages(packages/cloud-ai-sdk) — the standaloneuseCloudChathook integration.
The generateThreadTitle function in cloud-ai-sdk calls cloud.threads.messages.list(threadId) directly (without pagination) for a quick first-page sample, then reverses the newest-first list to get chronological order for the title model. This is intentional: title generation only needs recent context, not the full history.
Key Source Files#
| File | Role |
|---|---|
AssistantCloudThreadMessages.ts | Defines list() and AssistantCloudThreadMessageListQuery — now includes limit/after |
CloudMessagePersistence.ts | Pagination loop in load(); CLOUD_MESSAGE_PAGE_SIZE; seen-set dedup |
AssistantCloudProjectThreadMessages.ts | Project-scoped variant — had limit/after before thread messages did; serves as the template |
generateThreadTitle.ts | Direct single-page list() call; reverses newest-first for the title model |
| PR #6963 | Full rationale, cursor semantics, dedup logic, and test verification |