Runtime components, options, and adapters for using assistant-ui with externally owned chat state.
API Reference
ExternalStoreAdapter
ExternalStoreAdapterisDisabled?: booleanWhether the entire thread is disabled. When `true`, the composer's input is also disabled (the user cannot type, attach files, or submit). For a narrower gate that keeps the input usable but blocks only sending, use `isSendDisabled`.
isSendDisabled?: booleanWhether sending new messages is currently disabled. When `true`, the thread composer's input remains usable but `send()` becomes a no-op and the thread composer's `canSend` is `false`. Use this to gate sending on external React state (e.g. while tool config is loading) without disabling the input itself the way `isDisabled` does. Edit composers (saving message edits) intentionally ignore this flag.
isRunning?: booleanWhether the thread is running. When provided, this value flows directly to `thread.isRunning`, letting the application keep the thread in a running state even after the last assistant message has completed (for example while non-message stream chunks like suggestions or metadata updates are still arriving). When omitted, `thread.isRunning` falls back to the last-message-status heuristic.
isLoading?: booleanmessages?: readonly T[]messageRepository?: ExportedMessageRepositoryheadId?: string | nullmessages: Array<{
message: ThreadMessage;
parentId: string | null;
runConfig?: RunConfig;
}>suggestions?: readonly ThreadSuggestion[]state?: ReadonlyJSONValueextras?: unknownsetMessages?: ((messages: readonly T[]) => void)onImport?: ((messages: readonly ThreadMessage[]) => void)onExportExternalState?: (() => any)onLoadExternalState?: ((state: any) => void)onNew: (message: AppendMessage) => Promise<void>onEdit?: ((message: AppendMessage) => Promise<void>)onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>)onResume?: ((config: ResumeRunConfig) => Promise<void>)onCancel?: (() => Promise<void>)onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void)onResumeToolCall?: ((options: { toolCallId: string; payload: unknown }) => void)convertMessage?: ExternalStoreMessageConverter<T>adapters?: ExternalStoreAdapter["adapters"]attachments?: AttachmentAdapteraccept: stringadd: (state: { file: File; }) => Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>remove: (attachment: Attachment) => Promise<void>send: (attachment: PendingAttachment) => Promise<CompleteAttachment>speech?: SpeechSynthesisAdapterspeak: (text: string) => SpeechSynthesisAdapter.Utterancedictation?: DictationAdapterlisten: () => DictationAdapter.SessiondisableInputDuringDictation?: booleanvoice?: RealtimeVoiceAdapterconnect: (options: {
abortSignal?: AbortSignal;
}) => RealtimeVoiceAdapter.Sessionfeedback?: FeedbackAdaptersubmit: (feedback: FeedbackAdapterFeedback) => voidthreadListdeprecated?: ExternalStoreThreadListAdapterDeprecated: This API is still under active development and might change without notice.
threadIddeprecated?: stringDeprecated: This API is still under active development and might change without notice.
isLoading?: booleanthreads?: readonly ExternalStoreThreadData<"regular">[]archivedThreads?: readonly ExternalStoreThreadData<"archived">[]onSwitchToNewThreaddeprecated?: (() => Promise<void> | void)Deprecated: This API is still under active development and might change without notice.
onSwitchToThreaddeprecated?: ((threadId: string) => Promise<void> | void)Deprecated: This API is still under active development and might change without notice.
onRename?: (
threadId: string,
newTitle: string,
) => (Promise<void> | void)onArchive?: ((threadId: string) => Promise<void> | void)onUnarchive?: ((threadId: string) => Promise<void> | void)onDelete?: ((threadId: string) => Promise<void> | void)unstable_capabilitiesunstable?: ExternalStoreAdapter["unstable_capabilities"]copy?: booleanExternalThread
ExternalThread propsmessages: readonly ExternalThreadMessage[]isRunning?: booleanisSendDisabled?: booleanWhether sending new messages is currently disabled. When `true`, the thread composer's input remains usable but `send()` is a no-op and `composer.canSend` is `false`. Edit composers (saving message edits) intentionally ignore this flag.
onNew?: (message: AppendMessage) => voidCallback for new messages (non-queue runtimes).
onEdit?: (message: AppendMessage) => voidonReload?: (parentId: string | null) => voidonStartRun?: () => voidonCancel?: () => voidqueue?: ExternalThreadQueueAdapterQueue adapter for runtimes that support message queuing and steering.
items: readonly QueueItemState[]The current queue items.
enqueue: (message: AppendMessage, opts: { steer: boolean }) => voidCalled when a message is submitted via the composer. Receives the steer preference.
steer: (queueItemId: string) => voidCalled to promote an existing queue item (cancel current run, run this immediately).
remove: (queueItemId: string) => voidCalled to remove an item from the queue.
clear: (reason: "edit" | "reload" | "cancel-run") => voidCalled to clear all pending queue items, with the reason for clearing.
ExternalThreadProps
ExternalThreadPropsmessages: readonly ExternalThreadMessage[]isRunning?: booleanisSendDisabled?: booleanWhether sending new messages is currently disabled. When `true`, the thread composer's input remains usable but `send()` is a no-op and `composer.canSend` is `false`. Edit composers (saving message edits) intentionally ignore this flag.
onNew?: (message: AppendMessage) => voidCallback for new messages (non-queue runtimes).
onEdit?: (message: AppendMessage) => voidonReload?: (parentId: string | null) => voidonStartRun?: () => voidonCancel?: () => voidqueue?: ExternalThreadQueueAdapterQueue adapter for runtimes that support message queuing and steering.
items: readonly QueueItemState[]The current queue items.
enqueue: (message: AppendMessage, opts: { steer: boolean }) => voidCalled when a message is submitted via the composer. Receives the steer preference.
steer: (queueItemId: string) => voidCalled to promote an existing queue item (cancel current run, run this immediately).
remove: (queueItemId: string) => voidCalled to remove an item from the queue.
clear: (reason: "edit" | "reload" | "cancel-run") => voidCalled to clear all pending queue items, with the reason for clearing.
ExternalThreadQueueAdapter
ExternalThreadQueueAdapteritems: readonly QueueItemState[]The current queue items.
enqueue: (message: AppendMessage, opts: { steer: boolean }) => voidCalled when a message is submitted via the composer. Receives the steer preference.
steer: (queueItemId: string) => voidCalled to promote an existing queue item (cancel current run, run this immediately).
remove: (queueItemId: string) => voidCalled to remove an item from the queue.
clear: (reason: "edit" | "reload" | "cancel-run") => voidCalled to clear all pending queue items, with the reason for clearing.
useExternalStoreRuntime
useExternalStoreRuntimestore: ExternalStoreAdapter<T>isDisabled?: booleanWhether the entire thread is disabled. When `true`, the composer's input is also disabled (the user cannot type, attach files, or submit). For a narrower gate that keeps the input usable but blocks only sending, use `isSendDisabled`.
isSendDisabled?: booleanWhether sending new messages is currently disabled. When `true`, the thread composer's input remains usable but `send()` becomes a no-op and the thread composer's `canSend` is `false`. Use this to gate sending on external React state (e.g. while tool config is loading) without disabling the input itself the way `isDisabled` does. Edit composers (saving message edits) intentionally ignore this flag.
isRunning?: booleanWhether the thread is running. When provided, this value flows directly to `thread.isRunning`, letting the application keep the thread in a running state even after the last assistant message has completed (for example while non-message stream chunks like suggestions or metadata updates are still arriving). When omitted, `thread.isRunning` falls back to the last-message-status heuristic.
isLoading?: booleanmessages?: readonly T[]messageRepository?: ExportedMessageRepositoryheadId?: string | nullmessages: Array<{
message: ThreadMessage;
parentId: string | null;
runConfig?: RunConfig;
}>suggestions?: readonly ThreadSuggestion[]state?: ReadonlyJSONValueextras?: unknownsetMessages?: ((messages: readonly T[]) => void)onImport?: ((messages: readonly ThreadMessage[]) => void)onExportExternalState?: (() => any)onLoadExternalState?: ((state: any) => void)onNew: (message: AppendMessage) => Promise<void>onEdit?: ((message: AppendMessage) => Promise<void>)onReload?: ((parentId: string | null, config: StartRunConfig) => Promise<void>)onResume?: ((config: ResumeRunConfig) => Promise<void>)onCancel?: (() => Promise<void>)onAddToolResult?: ((options: AddToolResultOptions) => Promise<void> | void)onResumeToolCall?: ((options: { toolCallId: string; payload: unknown }) => void)convertMessage?: ExternalStoreMessageConverter<T>adapters?: ExternalStoreAdapter["adapters"]attachments?: AttachmentAdapteraccept: stringadd: (state: { file: File; }) => Promise<PendingAttachment> | AsyncGenerator<PendingAttachment, void>remove: (attachment: Attachment) => Promise<void>send: (attachment: PendingAttachment) => Promise<CompleteAttachment>speech?: SpeechSynthesisAdapterspeak: (text: string) => SpeechSynthesisAdapter.Utterancedictation?: DictationAdapterlisten: () => DictationAdapter.SessiondisableInputDuringDictation?: booleanvoice?: RealtimeVoiceAdapterconnect: (options: {
abortSignal?: AbortSignal;
}) => RealtimeVoiceAdapter.Sessionfeedback?: FeedbackAdaptersubmit: (feedback: FeedbackAdapterFeedback) => voidthreadListdeprecated?: ExternalStoreThreadListAdapterDeprecated: This API is still under active development and might change without notice.
threadIddeprecated?: stringDeprecated: This API is still under active development and might change without notice.
isLoading?: booleanthreads?: readonly ExternalStoreThreadData<"regular">[]archivedThreads?: readonly ExternalStoreThreadData<"archived">[]onSwitchToNewThreaddeprecated?: (() => Promise<void> | void)Deprecated: This API is still under active development and might change without notice.
onSwitchToThreaddeprecated?: ((threadId: string) => Promise<void> | void)Deprecated: This API is still under active development and might change without notice.
onRename?: (
threadId: string,
newTitle: string,
) => (Promise<void> | void)onArchive?: ((threadId: string) => Promise<void> | void)onUnarchive?: ((threadId: string) => Promise<void> | void)onDelete?: ((threadId: string) => Promise<void> | void)unstable_capabilitiesunstable?: ExternalStoreAdapter["unstable_capabilities"]copy?: boolean