Persistence & history
The default Drizzle store writes conversations and messages to Postgres. It is constructed per request bound to the verified user.
Drizzle schema
chat_conversations: id, user id, title, metadata, created and updated times.chat_messages: id, conversation id, role, canonical parts, text projection, model, and created time.
messageCount is not stored on the conversation row. The Drizzle
listConversations query computes it from messages for the history sidebar.
Turn persistence
The handler persists the latest user message, streams the response, then calls
saveTurn for the completed assistant turn. Store implementations must verify
ownership and deduplicate stable message ids.
await store.saveTurn({
conversationId,
messages: finalMessages,
model,
usage,
});The complete parts array is authoritative. The text column is a denormalized
projection for previews/search.
History routes
GET /historycallslistConversations.GET /history/:conversationIdcallslistMessages, returns chronological messages, and re-signs stored attachments when storage is configured.
The Drizzle store supports limit and before pagination and caps pages at 100.
Warning
The current hosted store ignores ListMessagesOptions and fetches the complete
conversation. Do not rely on before pagination when using that adapter until
hosted pagination is implemented.
Model context versus stored history
maxHistoryMessages (default 30) and maxMessageChars (default 4000) affect
what is sent to the model. They do not delete stored messages or hide history
from the UI.
export const { GET, POST, DELETE } = createChatHandler({
getUserId: getChatUserId,
model,
store: createDrizzleChatStore(),
maxHistoryMessages: 40,
maxMessageChars: 6000,
});Set maxMessageChars: 0 to disable the character cap. Use
summarizeHistory when dropped early messages must remain available as condensed,
non-authoritative background.
See ChatStore for the full contract.