| name | runtime |
| description | Guide to the assistant-ui runtime system, single-thread state, and the imperative aui client API in @assistant-ui/react. Use when creating a runtime (useLocalRuntime with a ChatModelAdapter, useExternalStoreRuntime for Redux/Zustand, useRemoteThreadListRuntime, useAssistantTransportRuntime), wiring AssistantRuntimeProvider, or reading/mutating thread, message, composer, and attachment state. Covers the unified hooks useAui, useAuiState, useAuiEvent and the v0.15 property accessors (aui.thread, aui.threads, aui.message, aui.composer, aui.part; availability via aui.thread.source != null), the s.optional.<scope> state view, scope methods (append, cancelRun, startRun, resumeRun, message({index}), part({index}), composer().send), capabilities, adapters (attachment, speech, dictation, suggestion, feedback, history), realtime voice, and types (ThreadMessage, MessageState, MessagePart, MessageStatus, ChatModelRunResult). The v0.12-era legacy context hooks (useAssistantRuntime, useThreadRuntime, useThread, useMessage, useComposer, useMessagePart, useAttachment) were removed in 0.15; route those to update. Use for provider "Cannot read property of undefined" errors or state not updating. For multi-thread list UI and switching between conversations use thread-list instead. |
| license | MIT |
assistant-ui Runtime
Always consult assistant-ui.com/llms.txt for the latest API.
References
Runtime Hierarchy
AssistantRuntime
├── ThreadListRuntime (thread management)
│ ├── ThreadListItemRuntime (per-thread item)
│ └── ...
└── ThreadRuntime (current thread)
├── ComposerRuntime (input state)
└── MessageRuntime[] (per-message)
└── MessagePartRuntime[] (per-content-part)
State Access (Modern API)
import { useAui, useAuiState, useAuiEvent } from "@assistant-ui/react";
function ChatControls() {
const api = useAui();
const messages = useAuiState(s => s.thread.messages);
const isRunning = useAuiState(s => s.thread.isRunning);
useAuiEvent("composer.send", (e) => {
console.log("Sent in thread:", e.threadId);
});
return (
<div>
<button onClick={() => api.thread.append({
role: "user",
content: [{ type: "text", text: "Hello!" }],
})}>
Send
</button>
{isRunning && (
<button onClick={() => api.thread.cancelRun()}>Cancel</button>
)}
</div>
);
}
Scope Accessors Are Properties
As of 0.15, aui.<scope> is a property, not a call. Calling it still works but is deprecated. Methods on a scope keep their parentheses.
const aui = useAui();
aui.thread.getState();
aui.threads.switchToNewThread();
aui.thread.composer().send();
aui.thread.message({ index: 0 });
Selecting an unavailable scope no longer throws; aui.message is always truthy. Check availability before use:
if (aui.message.source != null) {
aui.message.reload();
}
source, query, and name are reserved accessor properties and never resolve to scope methods.
Thread Operations
const aui = useAui();
const thread = aui.thread;
thread.append({ role: "user", content: [{ type: "text", text: "Hello" }] });
thread.startRun({ parentId: null });
thread.cancelRun();
const state = thread.getState();
Message Operations
message() takes a selector object of { index } or { id }.
const message = aui.thread.message({ index: 0 });
message.reload();
message.switchToBranch({ position: "next" });
message.submitFeedback({ type: "positive" });
const editComposer = message.composer();
editComposer.beginEdit();
editComposer.setText("Updated");
editComposer.send();
Events
Almost every event is now deprecated as state-derivable: derive from useAuiState instead of listening.
const isRunning = useAuiState((s) => s.thread.isRunning);
useAuiEvent("thread.modelContextUpdate", ({ threadId }) => {});
useAuiEvent("composer.attachmentAddError", (e) => {});
| Event | Status |
|---|
thread.modelContextUpdate | Current (model context lives in a provider, not in state) |
composer.attachmentAddError | Current |
composer.send, composer.attachmentAdd | Deprecated: observe composer text / attachments |
thread.runStart, thread.runEnd | Deprecated: observe s.thread.isRunning |
thread.initialize | Deprecated: observe s.thread.messages becoming non-empty |
threadListItem.switchedTo, threadListItem.switchedAway | Deprecated: compare s.threads.mainThreadId |
Optional Scopes
s.optional.<scope> resolves to undefined instead of throwing when a scope is not mounted, which is the safe way to read a scope from a component that renders both inside and outside it:
const partType = useAuiState((s) => s.optional.part?.type);
Capabilities
const caps = useAuiState(s => s.thread.capabilities);
RuntimeCapabilities: switchToBranch, switchBranchDuringRun, edit, reload, delete, cancel, unstable_copy, speech, dictation, voice, attachments, feedback, queue. Note unstable_copy and speech, not copy and speak.
Runtimes derive most of these from what you supply (a callback, an adapter) rather than from an explicit option.
Common Gotchas
"Cannot read property of undefined"
- Ensure hooks are called inside
AssistantRuntimeProvider
- For a scope that may not be mounted, read
s.optional.<scope> or guard on aui.<scope>.source != null
A legacy hook import fails to resolve
useAssistantRuntime, useThreadRuntime, useThread, useMessage, useComposer, useMessagePart, useAttachment, useThreadListItem, useThreadList and friends were removed in 0.15. See the /update skill for the mapping table.
State not updating
- Use selectors with
useAuiState to prevent unnecessary re-renders
Messages array empty
- Check runtime is configured
- Verify API response format