assistant-ui/skills

runtime

Guide to the assistant-ui runtime system, the state and action layer behind every chat surface, in @assistant-ui/react.

Ver código-fonte
Documento original do Skill

Renderizado do repositório de origem, preservando títulos, exemplos, código, tabelas, links e imagens.

assistant-ui Runtime

Always consult [assistant-ui.com/llms.txt](https://www.assistant-ui.com/llms.txt) for the latest API.

A runtime is the state and action layer behind a chat UI: it owns messages, threads, branching, and run lifecycle, and every primitive and hook reads from it through a uniform AssistantClient. There are two ways to build one. LocalRuntime (useLocalRuntime) owns the message store for you; you implement one ChatModelAdapter.run function and branching, editing, and regeneration come for free. ExternalStoreRuntime (useExternalStoreRuntime) is the inverse: you own the messages, and UI features turn on based on which callbacks you supply. Framework adapters such as useChatRuntime (@assistant-ui/ai-sdk) and protocol runtimes such as useAssistantTransportRuntime are built on one of these two. Once mounted under AssistantRuntimeProvider, every runtime is read and driven the same way, through useAui, useAuiState, and useAuiEvent.

References

Runtime hierarchy

AssistantRuntime
├── ThreadListRuntime (threads)
│   └── ThreadListItemRuntime[] (threadListItem)
└── ThreadRuntime (thread)
    ├── ComposerRuntime (composer)        new-message input
    ├── SuggestionRuntime[] (suggestion)  follow-up prompts, via thread.suggestions
    └── MessageRuntime[] (message)
        ├── ComposerRuntime (composer)    edit-message input
        ├── MessagePartRuntime[] (part)
        ├── ChainOfThoughtRuntime (chainOfThought)
        │   └── MessagePartRuntime[] (part)
        └── AttachmentRuntime[] (attachment)

modelContext and tools resolve independently of the thread scope; see runtime-concepts.md and the tools skill. Every node in the tree is reachable from aui.<scope> (imperative) or s.<scope> inside a useAuiState selector (reactive), scoped automatically to where the calling component is rendered.

useAui, useAuiState, useAuiEvent

useAui() returns the current AssistantClient. It does not subscribe to state, so its identity only changes on a structural change (for example switching threads); use it in event handlers and imperative code.

tsx
import { useAui } from "@assistant-ui/react";

const aui = useAui();
aui.thread.append({ role: "user", content: [{ type: "text", text: "Hello!" }] });
aui.thread.cancelRun();

useAuiState(selector) subscribes to a slice of AssistantState and re-renders only when the selected value changes (compared with Object.is). The selector runs on every store update, so it must return a primitive or a stable reference, never a fresh object or array literal, and never the whole state (that throws).

tsx
import { useAuiState } from "@assistant-ui/react";

const isRunning = useAuiState((s) => s.thread.isRunning);   // primitive: correct
const messages = useAuiState((s) => s.thread.messages);      // stable array reference: correct

// Wrong: a new object literal every call re-renders on every store update
const bad = useAuiState((s) => ({ isRunning: s.thread.isRunning, text: s.composer.text }));

Call useAuiState once per value (or compose several calls); do not spread a scope into a new object to bundle values together.

useAuiEvent(nameOrSelector, callback) subscribes for the component's lifetime. The callback runs inside an effect-event shim, so the latest closure fires without a memoized reference.

tsx
import { useAuiEvent } from "@assistant-ui/react";

useAuiEvent("thread.modelContextUpdate", ({ threadId }) => {
  console.log("Model context updated", threadId);
});

Scope accessors are properties

As of 0.15, aui.<scope> is a property, not a call; calling it (aui.thread()) still works but is deprecated. Methods on a scope keep their parentheses.

tsx
aui.thread.getState();             // property accessor
aui.threads.switchToNewThread();
aui.thread.composer().send();      // composer() is a method of the thread scope
aui.thread.message({ index: 0 });  // selector object, not a bare index

Selecting an unavailable scope no longer throws; aui.message is always truthy. Check availability before use with source, which is null when the scope is not mounted:

tsx
if (aui.message.source != null) {
  aui.message.reload();
}

source, query, and name are reserved accessor properties and never resolve to scope methods.

AuiConfig and AuiProvider

AuiProvider mounts an AssistantClient for a subtree. Its config prop must be built with AuiConfig({...}), imported from @assistant-ui/react (raw object literals are a type error). At the top level config alone creates the subtree's client. Nested under a parent provider, extends is mandatory: extends={aui} extends the parent client, extends={null} isolates a fresh root (dev enforced). AssistantRuntimeProvider installs an AuiProvider internally and additionally accepts config to attach extra scopes (such as a toolkit) alongside the runtime's own scopes; use it instead of wiring AuiProvider by hand around a runtime.

tsx
import { AssistantRuntimeProvider, AuiConfig, AuiProvider, Tools, useAui } from "@assistant-ui/react";

// Runtime root: config attaches extra scopes next to the runtime's own
const config = AuiConfig({ tools: Tools({ toolkit }) });
<AssistantRuntimeProvider runtime={runtime} config={config}>{children}</AssistantRuntimeProvider>;

// Nested scope: extend the parent client
function MessageScope({ children }: { children: React.ReactNode }) {
  const aui = useAui();
  const nested = AuiConfig({ tools: Tools({ toolkit: extraToolkit }) });
  return <AuiProvider extends={aui} config={nested}>{children}</AuiProvider>;
}

// Isolated root: detach from any parent client
const isolated = AuiConfig({});
<AuiProvider extends={null} config={isolated}>{children}</AuiProvider>;

A config is plain data: hoist it to module scope, build it inline per render, or memoize it, the provider never relies on config identity. ref on AuiProvider receives the resulting client after mount.

Thread and message operations

tsx
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();   // { messages, isRunning, capabilities, composer, ... }

const message = thread.message({ index: 0 });   // or { id: messageId }
message.reload();
message.switchToBranch({ position: "next" });
message.submitFeedback({ type: "positive" });

const editComposer = message.composer();
editComposer.beginEdit();
editComposer.setText("Updated");
editComposer.send();

Events

Most events are deprecated in favor of deriving the same transition from useAuiState, which is correct on first render and replay in a way an event handler is not.

EventStatus
threads.selectionChangedCurrent: fires once per main-thread switch with { threadId, previousThreadId }. Does not fire for the initially selected thread on mount
thread.modelContextUpdateCurrent: the model context lives in a provider, not in state, so there is no state-derivable equivalent
composer.attachmentAddErrorCurrent: reason is "no-adapter" \"not-accepted" \"adapter-error"
composer.send, composer.attachmentAddDeprecated: observe composer text / attachments
thread.runStart, thread.runEndDeprecated: observe s.thread.isRunning flipping
thread.initializeDeprecated: observe s.thread.messages becoming non-empty
threadListItem.switchedTo, threadListItem.switchedAwayDeprecated: use threads.selectionChanged, filtering by id inside a per-item scope if needed

The deprecated pair still fires and keeps working until the next major. threads.selectionChanged also fires in situations the old pair did not, such as switchToNewThread() and a deep-linked initial thread resolving after mount.

Optional scopes

s.optional.<scope> resolves to undefined instead of throwing when a scope is not mounted, the safe way to read a scope from a component that renders both inside and outside it.

tsx
const partType = useAuiState((s) => s.optional.part?.type);

The imperative equivalent is aui.<scope>.source != null.

Capabilities

tsx
const capabilities = useAuiState((s) => s.thread.capabilities);

RuntimeCapabilities (from @assistant-ui/core): switchToBranch, switchBranchDuringRun, edit, reload, delete, cancel, refetchThread, unstable_copy, speech, dictation, voice, attachments, feedback, queue. Runtimes derive nearly all of these from what you supply (a callback, an adapter) rather than an explicit option; unstable_copy is the one flag ExternalStoreRuntime lets you force off via unstable_capabilities. refetchThread reports whether aui.threads.reloadMainThread() will refresh the open thread in place or fall back to remounting the runtime; see runtime-concepts.md.

Common Gotchas

"Cannot read property of undefined"

  • Ensure hooks are called inside AssistantRuntimeProvider (or an AuiProvider whose config supplies the scope).
  • For a scope that may not be mounted, read s.optional.<scope> or guard on aui.<scope>.source != null.

Infinite re-renders from `useAuiState`

  • The selector returned a new object or array literal, including spreading a scope into one. Select primitives with separate calls, or return a memoized reference.

A legacy hook import fails to resolve

  • useAssistantRuntime, useThreadRuntime, useThread, useMessage, useComposer, useMessagePart, useAttachment, useThreadListItem and friends were removed in 0.15. See state-hooks.md for the full mapping, or the update skill.

State not updating

  • Use a selector with useAuiState rather than reading getState() in render; getState() is a one-time snapshot, not a subscription.

Multiple threads or a conversation sidebar

  • This skill covers a single thread's state and the runtime hooks. For creating, switching, archiving, and rendering a list of threads, use the thread-list skill instead.

Related Skills

  • thread-list -- multi-thread UI, thread CRUD, threads.selectionChanged consumers
  • tools -- toolkit authoring, tool call UI, approval gates and human tools in depth
  • elements -- the styled Thread and composer components that read this state for you
  • cloud -- AssistantCloud, managed persistence, and useChatRuntime({ cloud })
  • streaming -- the DataStream and AssistantTransport wire protocols underneath the protocol runtimes
do mesmo repositório

Mais Skills

Todos os Skills
assistant-ui
Comunidade

assistant-ui

Overview and router for assistant-ui, the React library for building AI chat interfaces from composable primitives and a styled elements catalog. Use for high-level, cross-cutting, or architecture questions: choosing packages, picking a runtime, or understanding the layers (elements, primitives, the aui client with AuiConfig and AuiProvider, the runtime, adapters) and the message model. Covers @assistant-ui/react 0.15.x, the framework-neutral @assistant-ui/ai-sdk integration for AI SDK v7 (useChatRuntime, AssistantChatTransport; @assistant-ui/react-ai-sdk re-exports it), @assistant-ui/core, @assistant-ui/store, assistant-stream, assistant-cloud, the adapters for LangGraph, LangChain, Google ADK, A2A, AG-UI, Eve, OpenCode, and Pi, and the platform bindings @assistant-ui/react-native and @assistant-ui/react-ink; AssistantRuntimeProvider; the primitives ThreadPrimitive, MessagePrimitive, ComposerPrimitive; the hooks useAui, useAuiState, useAuiEvent; and runtime selection across useChatRuntime, useExternalStoreRuntime, useLangGraphRuntime, useLocalRuntime. For a specific area route to a focused sibling instead: setup, elements, primitives, runtime, tools, generative-ui, streaming, cloud, thread-list, copilots, markdown, react-mcp, observability, react-native, ink, or update.

instalações
1
GitHub Stars
26
Atualizado
4 de set.
assistant-ui
Comunidade

cloud

Adds AssistantCloud backed persistence, authorization, and telemetry to assistant-ui apps. Use when wiring cross-session thread and message history, multi-device chat, message feedback, file uploads, or auth: passing cloud to useChatRuntime from @assistant-ui/ai-sdk, AISDKThreads({ cloud }) for AuiConfig hosts, the standalone useCloudChat/useThreads hooks from @assistant-ui/cloud-ai-sdk, or cloud on useLangGraphRuntime. Covers constructing AssistantCloud with authToken (JWT), apiKey plus userId/workspaceId (server-side), or anonymous; direct provider integrations (Clerk, Auth0, Supabase, Firebase) and a backend token endpoint; and the client surface verified against source: cloud.threads.{list,get,create,update,delete}, cloud.threads.messages.{list,create,update,feedback}, cloud.files.{generatePresignedUploadUrl,pdfToImages}, cloud.runs.{stream,report}, cloud.projects.threads, cloud.auth.tokens.create, and cloud.telemetry. Also covers a custom ThreadHistoryAdapter built on CloudMessagePersistence/createFormattedPersistence, run telemetry (beforeReport, sub-agent tracking with wrapSamplingHandler), and the NEXTPUBLICASSISTANTBASEURL/ASSISTANTAPIKEY env vars. Route here for threads that do not persist, 401s against the cloud API, or feedback buttons that do not save. For the sidebar UI itself use thread-list; for the general RemoteThreadListAdapter/ThreadHistoryAdapter contract use runtime.

instalações
1
GitHub Stars
26
Atualizado
4 de set.
assistant-ui
Comunidade

copilots

Grounding an assistant in your app with assistant-ui copilots (@assistant-ui/react). Use when steering assistant behavior with useAssistantInstructions, feeding lazy send-time app state through useAssistantContext({ getContext }), exposing rendered components to the assistant with makeAssistantVisible(Component, { clickable, editable }), giving the model two-way component state through interactables (unstableuseInteractable for app-scoped panels, unstableinteractableTool inside defineToolkit for thread-scoped artifacts, both mounted via AuiConfig({ unstableinteractables: unstableInteractables() })), registering instructions and tools together imperatively with aui.modelContext.register({ getModelContext }), or bridging model context across an iframe boundary with AssistantFrameProvider and useAssistantFrameHost. The legacy Interactables() scope, useAssistantInteractable, and useInteractableState are deprecated since 2026-06-14 and scheduled for removal on or after 2026-09-14; new code uses the unstable API. Reach for this when the assistant should read the current page, click or edit UI, read and update component state through auto-generated update{name} tools, or receive tools and instructions from a sandboxed iframe. For LLM tools and tool-call UI use the tools skill; for runtime and thread state use the runtime skill.

instalações
1
GitHub Stars
26
Atualizado
4 de set.
assistant-ui
Comunidade

elements

Installs and customizes assistant-ui elements, the styled shadcn-style component catalog at assistant-ui.com/elements served from the r.assistant-ui.com registry through npx assistant-ui@latest add . Use when adding a prebuilt chat surface or widget (Thread, ThreadList, AssistantModal, AssistantSidebar, ToolFallback, ToolGroup, MarkdownText, Reasoning, Sources, Attachment, ModelSelector, Voice orb, McpConfig) or one of the 120 standalone elements (approval card, agent plan, code diff, data table, chart, trace waterfall, message queue, composer variants, and so on), choosing between runtime-connected .aui.tsx files and props-driven standalone files, overriding Thread slots through the components prop, editing the copied source under components/assistant-ui/elements/, using the shared surfaces.tsx tokens, or picking the Radix versus Base UI flavor through the style-aware registry URL in components.json. Route here when an import from @/components/assistant-ui/... fails, an element renders unstyled, or the CLI installs the wrong flavor. For unstyled building blocks use primitives; for the CLI scaffold itself use setup.

instalações
1
GitHub Stars
26
Atualizado
4 de set.