assistant-ui logo/Docs/API Reference
Hooks

Primitive Hooks

Primitive hooks for reading scoped assistant-ui runtime state, viewport behavior, timing, and message part data inside React components.

API Reference

useCloudThreadListAdapter

useCloudThreadListAdapter
adapter : CloudThreadListAdapterOptions

cloud ?: AssistantCloud

threads : AssistantCloudThreads

messages : AssistantCloudThreadMessages

cloud : AssistantCloudAPI

list : (query?: AssistantCloudThreadsListQuery) => Promise<AssistantCloudThreadsListResponse>

get : (threadId: string) => Promise<CloudThread>

create : (body: AssistantCloudThreadsCreateBody) => Promise<AssistantCloudThreadsCreateResponse>

update : (threadId: string, body: AssistantCloudThreadsUpdateBody) => Promise<void>

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

auth : __object

tokens : AssistantCloudAuthTokens

runs : AssistantCloudRuns

cloud : AssistantCloudAPI

stream : (body: AssistantCloudRunsStreamBody) => Promise<AssistantStream>

report : (body: AssistantCloudRunReport) => Promise<{ run_id: string; }>

files : AssistantCloudFiles

cloud : AssistantCloudAPI

pdfToImages : (body: PdfToImagesRequestBody) => Promise<PdfToImagesResponse>

generatePresignedUploadUrl : (body: GeneratePresignedUploadUrlRequestBody) => Promise<GeneratePresignedUploadUrlResponse>

telemetry : AssistantCloudTelemetryConfig

enabled ?: boolean

beforeReport ?: ( report: AssistantCloudRunReport, ) => AssistantCloudRunReport | null

Called before each telemetry report is sent. Return a modified report to enrich it (e.g. add `model_id`), or return `null` to skip the report.

create ?: (() => Promise<ThreadData>)

delete ?: ((threadId: string) => Promise<void>)

useEditComposerAttachment

const useEditComposerAttachment: { (): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }); <TSelected>(selector: (state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; })) => TSelected): TSelected; <TSelected>(selector: ((state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; })) => TSelected) | undefined): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | TSelected; (options: { optional?: false | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }); (options: { optional?: boolean | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; })) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; })) => TSelected) | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; })) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; })) => TSelected) | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "edit-composer"; } & { source: "edit-composer"; }) | TSelected | null; };

useEditComposerAttachmentRuntime

useEditComposerAttachmentRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useMessageAttachment

const useMessageAttachment: { (): { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }; <TSelected>(selector: (state: { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) => TSelected): TSelected; <TSelected>(selector: ((state: { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) => TSelected) | undefined): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) | TSelected; (options: { optional?: false | undefined; }): { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }; (options: { optional?: boolean | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) | null; <TSelected>(options: { optional?: false | undefined; selector: (state: { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) => TSelected) | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: { id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) => TSelected) | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "message"; } & { source: "message"; }) | TSelected | null; };

useMessageAttachmentRuntime

useMessageAttachmentRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useMessageQuote

Hook that returns the quote info for the current message, if any.

Reads from message.metadata.custom.quote.

function QuoteBlock() {
  const quote = useMessageQuote();
  if (!quote) return null;
  return <blockquote>{quote.text}</blockquote>;
}
const useMessageQuote: () => QuoteInfo;

useMessageTiming

Hook that returns timing information for the current assistant message.

Reads from message.metadata.timing.

function MessageStats() {
  const timing = useMessageTiming();
  if (!timing) return null;
  return <span>{timing.tokensPerSecond?.toFixed(1)} tok/s</span>;
}
const useMessageTiming: () => MessageTiming;

useRuntimeAdapters

type RuntimeAdapters = {
  modelContext?: ModelContextProvider | undefined;
  history?: ThreadHistoryAdapter | undefined;
  attachments?: AttachmentAdapter | undefined;
};

const useRuntimeAdapters: () => RuntimeAdapters | null;

useScrollLock

Locks scroll position during collapsible/height animations and hides scrollbar.

This utility prevents page jumps when content height changes during animations, providing a smooth user experience. It finds the nearest scrollable ancestor and temporarily locks its scroll position while the animation completes.

  • Prevents forced reflows: no layout reads, mutations scoped to scrollable parent only
  • Reactive: only intercepts scroll events when browser actually adjusts
  • Cleans up automatically after animation duration
const collapsibleRef = useRef<HTMLDivElement>(null);
const lockScroll = useScrollLock(collapsibleRef, 200);

const handleCollapse = () => {
  lockScroll(); // Lock scroll before collapsing
  setIsOpen(false);
};
useScrollLock
animatedElementRef : RefObject<T | null>

animationDuration : number

useThreadComposerAttachment

const useThreadComposerAttachment: { (): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }); <TSelected>(selector: (state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; })) => TSelected): TSelected; <TSelected>(selector: ((state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; })) => TSelected) | undefined): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | TSelected; (options: { optional?: false | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }); (options: { optional?: boolean | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; })) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; })) => TSelected) | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; })) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; })) => TSelected) | undefined; }): ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: PendingAttachmentStatus; file: File; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | ({ id: string; type: "image" | "document" | "file" | (string & {}); name: string; contentType?: string | undefined; file?: File; content?: ThreadUserMessagePart[]; } & { status: CompleteAttachmentStatus; content: ThreadUserMessagePart[]; } & { readonly source: "thread-composer"; } & { source: "thread-composer"; }) | TSelected | null; };

useThreadComposerAttachmentRuntime

useThreadComposerAttachmentRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useThreadViewport

const useThreadViewport: { (): ThreadViewportState; <TSelected>(selector: (state: ThreadViewportState) => TSelected): TSelected; (options: { optional: true; }): ThreadViewportState | null; <TSelected>(options: { optional: true; selector?: (state: ThreadViewportState) => TSelected; }): TSelected | null; };

useThreadViewportAutoScroll

useThreadViewportAutoScroll
options : useThreadViewportAutoScroll.Options

autoScroll ?: boolean

Whether to automatically scroll to the bottom when new messages are added. When enabled, the viewport will automatically scroll to show the latest content. Default false if `turnAnchor` is "top", otherwise defaults to true.

scrollToBottomOnRunStart ?: boolean

Whether to scroll to bottom when a new run starts. Defaults to true.

scrollToBottomOnInitialize ?: boolean

Whether to scroll to bottom when messages first appear in the thread. Defaults to true.

scrollToBottomOnThreadSwitch ?: boolean

Whether to scroll to bottom when switching to a different thread. Defaults to true.

useThreadViewportStore

const useThreadViewportStore: { (): ReadonlyStore<ThreadViewportState>; (options: { optional: true; }): ReadonlyStore<ThreadViewportState> | null; };

unstable_useComposerInput

Experimental. Under active development and might change without notice.

Headless bridge to the composer's text value and send action, for building a custom composer input without ComposerPrimitive.Input. It is a thin bridge, not a second input: it does not own keyboard behavior, autosize, IME or contentEditable sync, paste/drop attachments, focus management, or rich-text state. Spread unstable_useTriggerPopoverAriaProps() onto your element for trigger-popover combobox semantics.

const { value, setText, send, isDisabled, canSend } = unstable_useComposerInput();
<textarea
  value={value}
  disabled={isDisabled}
  onChange={(e) => setText(e.target.value)}
  onKeyDown={(e) => {
    if (e.key === "Enter" && !e.shiftKey && canSend) {
      e.preventDefault();
      send();
    }
  }}
/>
unstable_useComposerInput
options ?: Unstable_UseComposerInputOptions

disabled ?: boolean

Disables the input in addition to the composer's own disabled sources (thread disabled, active dictation). When disabled, `isDisabled` is `true` and `canSend` is `false`.

unstable_useComposerInputHistory

Experimental. Under active development and might change without notice.

Terminal-style input history for the thread composer: ArrowUp on an empty draft recalls previously sent user messages (newest first), ArrowDown steps back toward the newest and finally restores the draft that was being typed when browsing started.

Recall only triggers when the caret is on the first/last line with no selection, so multi-line editing keeps native arrow behavior. The handler yields to an open mention/slash popover, to IME composition, to modifier keys, and to consumer handlers that already called preventDefault. It is inert on edit composers.

const history = unstable_useComposerInputHistory();
<ComposerPrimitive.Input {...history} />
function unstable_useComposerInputHistory(): Unstable_ComposerInputHistory;

unstable_useMessageStallDetection

Experimental. Under active development and might change without notice.

Detects mid-run output stalls on the current message: while the message is running, watches a fingerprint of its content (part count plus text, argument, and result sizes) and reports a stall once the fingerprint stops changing for thresholdMs. Useful for re-surfacing a "still working" indicator during tool think-time or provider stalls, after the first tokens have already streamed.

Must be used inside a message scope.

unstable_useMessageStallDetection
options ?: Unstable_MessageStallDetectionOptions

thresholdMs : number = 2000

Milliseconds of unchanged message content before the message counts as stalled.

unstable_useThreadMessageIds

Experimental. Unstable / Experimental - may change in any release.

Returns the ids of the messages in the current thread, in order.

The returned array keeps a stable identity across content-only updates (e.g. streaming), changing reference only when the id sequence itself changes. Pair with ThreadPrimitive.Unstable_MessageById to drive a virtualized or custom message list.

const unstable_useThreadMessageIds: () => readonly string[];

unstable_useTriggerPopoverAriaProps

Experimental. Under active development and might change without notice.

ARIA combobox attributes for the focused element (typically the composer input) describing the open trigger popover, per the WAI-ARIA editable combobox pattern. Returns an empty object outside a TriggerPopoverRoot or when no popover is open. Spread these last so they take precedence over any matching ARIA props you set yourself, mirroring ComposerPrimitive.Input.

const aria = unstable_useTriggerPopoverAriaProps();
<textarea {...aria} />
function unstable_useTriggerPopoverAriaProps(): Unstable_TriggerPopoverAriaProps;

useAssistantRuntime

Deprecated. Use useAui instead. See the migration guide.

Hook to access the AssistantRuntime from the current context.

The AssistantRuntime provides access to the top-level assistant state and actions, including thread management, tool registration, and configuration.

// Before:
function MyComponent() {
  const runtime = useAssistantRuntime();
  const handleNewThread = () => {
    runtime.switchToNewThread();
  };
  return <button onClick={handleNewThread}>New Thread</button>;
}

// After:
function MyComponent() {
  const aui = useAui();
  const handleNewThread = () => {
    aui.threads().switchToNewThread();
  };
  return <button onClick={handleNewThread}>New Thread</button>;
}
useAssistantRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useAttachment

Deprecated. Use useAuiState: useAuiState((s) => s.attachment). See the migration guide.

const useAttachment: { (): AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }; <TSelected>(selector: (state: AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) => TSelected): TSelected; <TSelected>(selector: ((state: AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) => TSelected) | undefined): (AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) | TSelected; (options: { optional?: false | undefined; }): AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }; (options: { optional?: boolean | undefined; }): (AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) | null; <TSelected>(options: { optional?: false | undefined; selector: (state: AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) => TSelected) | undefined; }): (AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) => TSelected) | undefined; }): (AttachmentState & { source: "message" | "thread-composer" | "edit-composer"; }) | TSelected | null; };

useAttachmentRuntime

Deprecated. Use useAui with aui.attachment() instead. See the migration guide.

useAttachmentRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useComposer

Deprecated. Use useAuiState: useAuiState((s) => s.composer). See the migration guide.

Hook to access the current composer state.

This hook provides reactive access to the composer's state, including text content, attachments, editing status, and send/cancel capabilities.

// Before:
function ComposerStatus() {
  const text = useComposer((state) => state.text);
  const canSend = useComposer((state) => state.canSend);
  const attachmentCount = useComposer((state) => state.attachments.length);
  return (
    <div>
      Text: {text.length} chars,
      Attachments: {attachmentCount},
      Can send: {canSend}
    </div>
  );
}

// After:
function ComposerStatus() {
  const text = useAuiState((s) => s.composer.text);
  const canSend = useAuiState((s) => s.composer.canSend);
  const attachmentCount = useAuiState((s) => s.composer.attachments.length);
  return (
    <div>
      Text: {text.length} chars,
      Attachments: {attachmentCount},
      Can send: {canSend}
    </div>
  );
}
const useComposer: { (): ComposerState; <TSelected>(selector: (state: ComposerState) => TSelected): TSelected; <TSelected>(selector: ((state: ComposerState) => TSelected) | undefined): ComposerState | TSelected; (options: { optional?: false | undefined; }): ComposerState; (options: { optional?: boolean | undefined; }): ComposerState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ComposerState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ComposerState) => TSelected) | undefined; }): ComposerState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ComposerState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ComposerState) => TSelected) | undefined; }): ComposerState | TSelected | null; };

useComposerRuntime

Deprecated. Use useAui with aui.composer() instead. See the migration guide.

Hook to access the ComposerRuntime from the current context.

The ComposerRuntime provides access to composer state and actions for message composition, including text input, attachments, and sending functionality. This hook automatically resolves to either the message's edit composer or the thread's main composer depending on the context.

// Before:
function ComposerActions() {
  const runtime = useComposerRuntime();
  const handleSend = () => {
    if (runtime.getState().canSend) {
      runtime.send();
    }
  };
  const handleCancel = () => {
    if (runtime.getState().canCancel) {
      runtime.cancel();
    }
  };
  return (
    <div>
      <button onClick={handleSend}>Send</button>
      <button onClick={handleCancel}>Cancel</button>
    </div>
  );
}

// After:
function ComposerActions() {
  const aui = useAui();
  const canSend = useAuiState((s) => s.composer.canSend);
  const canCancel = useAuiState((s) => s.composer.canCancel);
  const handleSend = () => {
    if (canSend) {
      aui.composer().send();
    }
  };
  const handleCancel = () => {
    if (canCancel) {
      aui.composer().cancel();
    }
  };
  return (
    <div>
      <button onClick={handleSend}>Send</button>
      <button onClick={handleCancel}>Cancel</button>
    </div>
  );
}
useComposerRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useEditComposer

Deprecated. Use useAuiState: useAuiState((s) => s.message.composer). See the migration guide.

const useEditComposer: { (): EditComposerState; <TSelected>(selector: (state: EditComposerState) => TSelected): TSelected; <TSelected>(selector: ((state: EditComposerState) => TSelected) | undefined): EditComposerState | TSelected; (options: { optional?: false | undefined; }): EditComposerState; (options: { optional?: boolean | undefined; }): EditComposerState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: EditComposerState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: EditComposerState) => TSelected) | undefined; }): EditComposerState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: EditComposerState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: EditComposerState) => TSelected) | undefined; }): EditComposerState | TSelected | null; };

useMessage

Deprecated. Use useAuiState: useAuiState((s) => s.message). See the migration guide.

Hook to access the current message state.

This hook provides reactive access to the message's state, including content, role, status, and other message-level properties.

// Before:
function MessageContent() {
  const role = useMessage((state) => state.role);
  const content = useMessage((state) => state.content);
  const isLoading = useMessage((state) => state.status.type === "running");
  return (
    <div className={`message-${role}`}>
      {isLoading ? "Loading..." : content.map(part => part.text).join("")}
    </div>
  );
}

// After:
function MessageContent() {
  const role = useAuiState((s) => s.message.role);
  const content = useAuiState((s) => s.message.content);
  const isLoading = useAuiState((s) => s.message.status.type === "running");
  return (
    <div className={`message-${role}`}>
      {isLoading ? "Loading..." : content.map(part => part.text).join("")}
    </div>
  );
}
const useMessage: { (): MessageState; <TSelected>(selector: (state: MessageState) => TSelected): TSelected; <TSelected>(selector: ((state: MessageState) => TSelected) | undefined): MessageState | TSelected; (options: { optional?: false | undefined; }): MessageState; (options: { optional?: boolean | undefined; }): MessageState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: MessageState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: MessageState) => TSelected) | undefined; }): MessageState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: MessageState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: MessageState) => TSelected) | undefined; }): MessageState | TSelected | null; };

useMessagePart

Deprecated. Use useAuiState: useAuiState((s) => s.part). See the migration guide.

const useMessagePart: { (): MessagePartState; <TSelected>(selector: (state: MessagePartState) => TSelected): TSelected; <TSelected>(selector: ((state: MessagePartState) => TSelected) | undefined): MessagePartState | TSelected; (options: { optional?: false | undefined; }): MessagePartState; (options: { optional?: boolean | undefined; }): MessagePartState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: MessagePartState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: MessagePartState) => TSelected) | undefined; }): MessagePartState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: MessagePartState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: MessagePartState) => TSelected) | undefined; }): MessagePartState | TSelected | null; };

useMessagePartData

Deprecated. Use useAuiState to select and narrow s.part. Return null for optional rendering, or throw inside the selector to preserve the old hook's strict behavior.

const part = useAuiState((s) =>
  s.part.type === "data" && (!name || s.part.name === name)
    ? s.part
    : null,
);

See the migration guide.

useMessagePartData
name ?: string

useMessagePartFile

Deprecated. Use useAuiState to select and narrow s.part. Return null for optional rendering, or throw inside the selector to preserve the old hook's strict behavior.

const file = useAuiState((s) => {
  if (s.part.type !== "file") return null;
  return s.part;
});

See the migration guide.

const useMessagePartFile: () => FileMessagePart & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; };

useMessagePartImage

Deprecated. Use useAuiState to select and narrow s.part. Return null for optional rendering, or throw inside the selector to preserve the old hook's strict behavior.

const image = useAuiState((s) => {
  if (s.part.type !== "image") return null;
  return s.part;
});

See the migration guide.

const useMessagePartImage: () => ImageMessagePart & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; };

useMessagePartReasoning

Deprecated. Use useAuiState to select and narrow s.part. Return null for optional rendering, or throw inside the selector to preserve the old hook's strict behavior.

const reasoning = useAuiState((s) => {
  if (s.part.type !== "reasoning") return null;
  return s.part;
});

See the migration guide.

const useMessagePartReasoning: () => ReasoningMessagePart & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; };

useMessagePartRuntime

Deprecated. Use useAui with aui.part() instead. See the migration guide.

useMessagePartRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useMessagePartSource

Deprecated. Use useAuiState to select and narrow s.part. Return null for optional rendering, or throw inside the selector to preserve the old hook's strict behavior.

const source = useAuiState((s) => {
  if (s.part.type !== "source") return null;
  return s.part;
});

See the migration guide.

const useMessagePartSource: () => ({ readonly type: "source"; readonly sourceType: "url"; readonly id: string; readonly url: string; readonly title?: string; readonly providerMetadata?: SourceProviderMetadata; readonly parentId?: string; } & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; }) | ({ readonly type: "source"; readonly sourceType: "document"; readonly id: string; readonly url?: undefined; readonly title: string; readonly mediaType: string; readonly filename?: string; readonly providerMetadata?: SourceProviderMetadata; readonly parentId?: string; } & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; });

useMessagePartText

Deprecated. Use useAuiState to select and narrow s.part. Return null for optional rendering, or throw inside the selector to preserve the old hook's strict behavior.

const text = useAuiState((s) => {
  if (s.part.type !== "text" && s.part.type !== "reasoning") return null;
  return s.part;
});

See the migration guide.

const useMessagePartText: () => (TextMessagePart & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; }) | (ReasoningMessagePart & { readonly status: MessagePartStatus | ToolCallMessagePartStatus; });

useMessageRuntime

Deprecated. Use useAui with aui.message() instead. See the migration guide.

Hook to access the MessageRuntime from the current context.

The MessageRuntime provides access to message-level state and actions, including message content, status, editing capabilities, and branching.

// Before:
function MessageActions() {
  const runtime = useMessageRuntime();
  const handleReload = () => {
    runtime.reload();
  };
  const handleEdit = () => {
    runtime.startEdit();
  };
  return (
    <div>
      <button onClick={handleReload}>Reload</button>
      <button onClick={handleEdit}>Edit</button>
    </div>
  );
}

// After:
function MessageActions() {
  const aui = useAui();
  const handleReload = () => {
    aui.message().reload();
  };
  const handleEdit = () => {
    aui.message().startEdit();
  };
  return (
    <div>
      <button onClick={handleReload}>Reload</button>
      <button onClick={handleEdit}>Edit</button>
    </div>
  );
}
useMessageRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useThread

Deprecated. Use useAuiState: useAuiState((s) => s.thread). See the migration guide.

Hook to access the current thread state.

This hook provides reactive access to the thread's state, including messages, running status, capabilities, and other thread-level properties.

// Before:
function ThreadStatus() {
  const isRunning = useThread((state) => state.isRunning);
  const messageCount = useThread((state) => state.messages.length);
  return <div>Running: {isRunning}, Messages: {messageCount}</div>;
}

// After:
function ThreadStatus() {
  const isRunning = useAuiState((s) => s.thread.isRunning);
  const messageCount = useAuiState((s) => s.thread.messages.length);
  return <div>Running: {isRunning}, Messages: {messageCount}</div>;
}
const useThread: { (): ThreadState; <TSelected>(selector: (state: ThreadState) => TSelected): TSelected; <TSelected>(selector: ((state: ThreadState) => TSelected) | undefined): ThreadState | TSelected; (options: { optional?: false | undefined; }): ThreadState; (options: { optional?: boolean | undefined; }): ThreadState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ThreadState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ThreadState) => TSelected) | undefined; }): ThreadState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ThreadState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ThreadState) => TSelected) | undefined; }): ThreadState | TSelected | null; };

useThreadComposer

Deprecated. Use useAuiState: useAuiState((s) => s.thread.composer). See the migration guide.

const useThreadComposer: { (): ThreadComposerState; <TSelected>(selector: (state: ThreadComposerState) => TSelected): TSelected; <TSelected>(selector: ((state: ThreadComposerState) => TSelected) | undefined): ThreadComposerState | TSelected; (options: { optional?: false | undefined; }): ThreadComposerState; (options: { optional?: boolean | undefined; }): ThreadComposerState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ThreadComposerState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ThreadComposerState) => TSelected) | undefined; }): ThreadComposerState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ThreadComposerState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ThreadComposerState) => TSelected) | undefined; }): ThreadComposerState | TSelected | null; };

useThreadList

Deprecated. Use useAuiState: useAuiState((s) => s.threads). See the migration guide.

const useThreadList: { (): ThreadListState; <TSelected>(selector: (state: ThreadListState) => TSelected): TSelected; <TSelected>(selector: ((state: ThreadListState) => TSelected) | undefined): ThreadListState | TSelected; (options: { optional?: false | undefined; }): ThreadListState; (options: { optional?: boolean | undefined; }): ThreadListState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ThreadListState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ThreadListState) => TSelected) | undefined; }): ThreadListState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ThreadListState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ThreadListState) => TSelected) | undefined; }): ThreadListState | TSelected | null; };

useThreadListItem

Deprecated. Use useAuiState: useAuiState((s) => s.threadListItem). See the migration guide.

const useThreadListItem: { (): ThreadListItemState; <TSelected>(selector: (state: ThreadListItemState) => TSelected): TSelected; <TSelected>(selector: ((state: ThreadListItemState) => TSelected) | undefined): ThreadListItemState | TSelected; (options: { optional?: false | undefined; }): ThreadListItemState; (options: { optional?: boolean | undefined; }): ThreadListItemState | null; <TSelected>(options: { optional?: false | undefined; selector: (state: ThreadListItemState) => TSelected; }): TSelected; <TSelected>(options: { optional?: false | undefined; selector: ((state: ThreadListItemState) => TSelected) | undefined; }): ThreadListItemState | TSelected; <TSelected>(options: { optional?: boolean | undefined; selector: (state: ThreadListItemState) => TSelected; }): TSelected | null; <TSelected>(options: { optional?: boolean | undefined; selector: ((state: ThreadListItemState) => TSelected) | undefined; }): ThreadListItemState | TSelected | null; };

useThreadListItemRuntime

Deprecated. Use useAui with aui.threadListItem() instead. See the migration guide.

useThreadListItemRuntime
options ?: { optional?: false | undefined; }

optional ?: false

useThreadRuntime

Deprecated. Use useAui with aui.thread() instead. See the migration guide.

Hook to access the ThreadRuntime from the current context.

The ThreadRuntime provides access to thread-level state and actions, including message management, thread state, and composer functionality.

// Before:
function MyComponent() {
  const runtime = useThreadRuntime();
  const handleSendMessage = (text: string) => {
    runtime.append({ role: "user", content: [{ type: "text", text }] });
  };
  return <button onClick={() => handleSendMessage("Hello!")}>Send</button>;
}

// After:
function MyComponent() {
  const aui = useAui();
  const handleSendMessage = (text: string) => {
    aui.thread().append({ role: "user", content: [{ type: "text", text }] });
  };
  return <button onClick={() => handleSendMessage("Hello!")}>Send</button>;
}
useThreadRuntime
options ?: { optional?: false | undefined; }

optional ?: false