Persistence Adapters

Persistence adapters for saving assistant-ui message history, remote thread lists, and long-running chat sessions across browser reloads.

API Reference

ExportedMessageRepository

ExportedMessageRepository
headId?string | null

messagesArray<{ message: ThreadMessage; parentId: string | null; runConfig?: RunConfig; }>

GenericThreadHistoryAdapter

GenericThreadHistoryAdapter
load() => Promise<MessageFormatRepository<TMessage>>

pin?() => void

Snapshot the current thread so later writes survive a switch.

append(item: MessageFormatItem<TMessage>) => Promise<void>

update?(item: MessageFormatItem<TMessage>, localMessageId: string) => Promise<void>

delete?(items: MessageFormatItem<TMessage>[]) => Promise<void>

reportTelemetry?(items: MessageFormatItem<TMessage>[], options?: { durationMs?: number; stepTimestamps?: { start_ms: number; end_ms: number; }[]; message?: ThreadMessage; }) => void

InMemoryThreadListAdapter

InMemoryThreadListAdapter
list?() => Promise<RemoteThreadListResponse>

rename?() => Promise<void>

updateCustom?() => Promise<void>

archive?() => Promise<void>

unarchive?() => Promise<void>

delete?() => Promise<void>

initialize?(threadId: string) => Promise<RemoteThreadInitializeResponse>

generateTitle?() => Promise<AssistantStream>

fetch?(threadId: string) => Promise<RemoteThreadMetadata>

MessageFormatAdapter

MessageFormatAdapter
formatstring

encode(item: MessageFormatItem<TMessage>) => TStorageFormat

decode(stored: MessageStorageEntry<TStorageFormat>) => MessageFormatItem<TMessage>

getId(message: TMessage) => string

RemoteThreadListAdapter

const runtime = useRemoteThreadListRuntime({
  adapter: myRemoteThreadListAdapter,
  runtimeHook: () => useLocalRuntime(chatModelAdapter),
});
RemoteThreadListAdapter
list(params?: RemoteThreadListPageOptions) => Promise<RemoteThreadListResponse>

rename(remoteId: string, newTitle: string) => Promise<void>

updateCustom?(remoteId: string, custom: Record<string, unknown> | undefined) => Promise<void>

archive(remoteId: string) => Promise<void>

unarchive(remoteId: string) => Promise<void>

delete(remoteId: string) => Promise<void>

initialize(threadId: string) => Promise<RemoteThreadInitializeResponse>

generateTitle(remoteId: string, unstable_messages: readonly ThreadMessage[]) => Promise<AssistantStream>

Generates a title for the thread and streams it back. When the generation persists the title itself, the returned stream must not complete before that write has landed. Concurrent generations are ordered by stream completion, and a run whose write outlives its stream can overwrite a newer title.

fetch(threadId: string) => Promise<RemoteThreadMetadata>

unstable_Provider?RemoteThreadListProviderComponent | undefinedunstable

Optional React component wrapped around each active thread. Use it to inject per-thread context such as a history or attachments adapter (see `useCloudThreadListAdapter` for the canonical shape). `useRemoteThreadListRuntime` renders this component when present. If it is omitted, that host synthesizes a `RuntimeAdapterProvider` from `unstable_useAdapters`. The `RemoteThreadList` store entry ignores it; expose `unstable_useAdapters` for that host. The Provider must render `children` on its first commit; deferring them behind a loading state, a Suspense boundary, or a `useEffect`-gated render is unsupported and leaves thread context unavailable to downstream consumers. Load data inside an always-mounted child instead.

unstable_useAdapters?(() => RuntimeAdapters | null | undefined) | undefinedunstable

Hook the `RemoteThreadList` store entry calls once per mounted thread body (the main-thread slot by default; every started thread with `backgroundThreads`), then provides to the `thread` factory. This is not mounted per listed thread. `useRemoteThreadListRuntime` also calls it when `unstable_Provider` is omitted. Resolve `threadListItem` lazily on each adapter call; do not capture it at hook mount. The hook must keep a stable hook count across adapter swaps; a different count throws. Memoize the returned object. Per-thread history also requires the `thread` factory to be keyed by thread id (`withKey(id, thread(...))`). History loaders such as `useExternalHistory` run once per mount; an unkeyed factory keeps one instance across switches and the next thread's messages never load.

ThreadHistoryAdapter

const runtime = useLocalRuntime(chatModelAdapter, {
  adapters: {
    history: myHistoryAdapter,
  },
});
ThreadHistoryAdapter
load() => Promise<ExportedMessageRepository & { state?: ReadonlyJSONValue; unstable_resume?: boolean; }>

resume?(options: ChatModelRunOptions) => AsyncGenerator<ChatModelRunResult, void, unknown>

append(item: ExportedMessageRepositoryItem) => Promise<void>

update?(item: ExportedMessageRepositoryItem) => Promise<void>

Rewrites a previously appended message in place, keyed by its message id. Adapters that implement this let a runtime persist a run paused for tool approval and finalize the same message once the run resumes. An update may arrive for an id whose earlier write failed; treat it as an upsert keyed on the message id rather than assuming the entry exists.

delete?(items: ExportedMessageRepositoryItem[]) => Promise<void>

withFormat?<TMessage, TStorageFormat extends Record<string, unknown>>(formatAdapter: MessageFormatAdapter<TMessage, TStorageFormat>) => GenericThreadHistoryAdapter<TMessage>

Required when used with `useAISDKRuntime` / `useChatRuntime`.