Migration to v0.14

Drops APIs deprecated since v0.11/v0.12, and primitives migrate from components prop to children render functions.

Version 0.14 finishes deprecation cycles started in v0.11 and v0.12, and replaces the components prop on primitives with a children render function pattern. If your app is on v0.13 and free of deprecation warnings, the only required change is the children API migration.

Automatic Migration

The codemod that ships with the CLI handles the v0.12 hook renames (useAssistantApiuseAui, etc.) which are also removed in v0.14:

npx assistant-ui@latest upgrade

The other removals listed below are simple renames you can apply with find-and-replace.

Hook Aliases Removed

These thin aliases existed only for backwards compatibility:

RemovedReplacement
useAssistantApiuseAui
useAssistantStateuseAuiState
useAssistantEventuseAuiEvent
AssistantIfAuiIf
useLocalThreadRuntimeuseLocalRuntime
unstable_useRemoteThreadListRuntimeuseRemoteThreadListRuntime
unstable_useCloudThreadListAdapteruseCloudThreadListAdapter
unstable_RemoteThreadListAdapterRemoteThreadListAdapter
unstable_InMemoryThreadListAdapterInMemoryThreadListAdapter

Runtime API Cleanups

AssistantRuntime

RemovedReplacement
runtime.threadListruntime.threads
runtime.switchToNewThread()runtime.threads.switchToNewThread()
runtime.switchToThread(id)runtime.threads.switchToThread(id)
runtime.registerModelConfigProvider(p)runtime.registerModelContextProvider(p)
runtime.reset({ initialMessages })runtime.thread.reset(initialMessages)

ThreadRuntime

RemovedReplacement
thread.startRun(parentId) (positional)thread.startRun({ parentId })
thread.unstable_resumeRun(config)thread.resumeRun(config)
thread.unstable_loadExternalState(state)thread.importExternalState(state)
thread.getModelConfig()thread.getModelContext()

MessageState / MessageRuntime

The duplicate top-level submittedFeedback field has been removed. It has always been mirrored from message.metadata.submittedFeedback, which remains the source of truth:

// Before
useAuiState((s) => s.message.submittedFeedback);

// After
useAuiState((s) => s.message.metadata.submittedFeedback);

ChatModelRunOptions

If you wrote a custom ChatModelAdapter, the legacy alias options.config (which mirrored options.context) is gone:

// Before
const adapter: ChatModelAdapter = {
  async run({ messages, config }) {
    /* config.tools, config.config, ... */
  },
};

// After
const adapter: ChatModelAdapter = {
  async run({ messages, context }) {
    /* context.tools, context.config, ... */
  },
};

External Store Helpers

getExternalStoreMessage (singular) has been removed. Use getExternalStoreMessages (plural), which always returns an array — even when only one source message is bound:

// Before
const original = getExternalStoreMessage<MyMessage>(threadMessage);

// After
const [original] = getExternalStoreMessages<MyMessage>(threadMessage);

Assistant Transport Helpers

toAISDKTools and getEnabledTools (re-exported from @assistant-ui/react) have been removed. Both wrapped functionality that's now exposed directly by assistant-stream:

// Before
import { toAISDKTools } from "@assistant-ui/react";
const schemas = toAISDKTools(tools);

// After
import { toToolsJSONSchema } from "assistant-stream";
const schemas = toToolsJSONSchema(tools);

toToolsJSONSchema filters out disabled and backend tools by default. To replicate the old toAISDKTools behavior exactly (include all tools regardless), pass { filter: () => true }.

react-langgraph

useLangGraphRuntime's onSwitchToThread option has been removed. Use load, which has the same signature plus an optional AbortSignal:

// Before
useLangGraphRuntime({
  stream,
  onSwitchToThread: async (threadId) => {
    return await fetchThread(threadId);
  },
});

// After
useLangGraphRuntime({
  stream,
  load: async (threadId, { signal } = {}) => {
    return await fetchThread(threadId, { signal });
  },
});

Children API for Primitives

Version 0.14 replaces the components prop on primitives with a children render function pattern. This gives you full control over rendering with simple inline logic.

ThreadPrimitive.Messages

Before:

<ThreadPrimitive.Messages
  components={{
    UserMessage,
    EditComposer,
    AssistantMessage,
  }}
/>

After:

<ThreadPrimitive.Messages>
  {({ message }) => {
    if (message.composer.isEditing) return <EditComposer />;
    if (message.role === "user") return <UserMessage />;
    return <AssistantMessage />;
  }}
</ThreadPrimitive.Messages>

MessagePrimitive.Parts

Before:

<MessagePrimitive.Parts
  components={{
    Text: MarkdownText,
    Reasoning,
    tools: { Fallback: ToolFallback },
  }}
/>

After:

<MessagePrimitive.Parts>
  {({ part }) => {
    if (part.type === "text") return <MarkdownText />;
    if (part.type === "reasoning") return <Reasoning {...part} />;
    if (part.type === "tool-call")
      return part.toolUI ?? <ToolFallback {...part} />;
    return null;
  }}
</MessagePrimitive.Parts>

part.toolUI and part.dataRendererUI

Tool-call parts now expose a toolUI property that resolves to the registered tool UI (via useAssistantToolUI / makeAssistantToolUI) or null if none is registered. Data parts similarly expose dataRendererUI.

// Renders the registered tool UI if available, otherwise ToolFallback
if (part.type === "tool-call")
  return part.toolUI ?? <ToolFallback {...part} />;

// Renders the registered data renderer if available
if (part.type === "data")
  return part.dataRendererUI ?? null;

Returning null

Returning null from the children function renders registered tool UIs and data renderer UIs automatically via the registry. To explicitly render nothing (suppressing registered UIs), return <></>.

<MessagePrimitive.Parts>
  {({ part }) => {
    if (part.type === "text") return <MarkdownText />;
    return null; // registered tool/data UIs still render
  }}
</MessagePrimitive.Parts>

ThreadPrimitive.Suggestions

Before:

<ThreadPrimitive.Suggestions
  components={{ Suggestion: SuggestionItem }}
/>

After:

<ThreadPrimitive.Suggestions>
  {() => <SuggestionItem />}
</ThreadPrimitive.Suggestions>

ThreadListPrimitive.Items

Before:

<ThreadListPrimitive.Items
  components={{ ThreadListItem: MyThreadListItem }}
/>

After:

<ThreadListPrimitive.Items>
  {() => <MyThreadListItem />}
</ThreadListPrimitive.Items>

Attachments

Before:

<ComposerPrimitive.Attachments
  components={{ Attachment: MyAttachment }}
/>

After:

<ComposerPrimitive.Attachments>
  {() => <MyAttachment />}
</ComposerPrimitive.Attachments>

addResult / resume on enriched part state

When using the children API, tool-call parts include addResult and resume methods directly on the part object. This means <ToolFallback {...part} /> works without needing a wrapper component to provide these methods.

Backwards Compatibility

The components prop is still supported on all primitives but is deprecated and will be removed in a future version.

Still Deprecated (not removed)

Some APIs marked @deprecated are intentionally kept for one more release to give downstream runtimes time to migrate:

  • ThreadState.threadId and ThreadState.metadata — use useThreadListItem() (see v0.12 guide). Will be removed in a future minor release.
  • Legacy context hooks under legacy-runtime/hooks/* (useThreadRuntime, useMessageRuntime, etc.) — replaced by useAui() / useAuiState() in v0.12. The codemod migrates these.
  • Primitive If components (ThreadIf, MessageIf, ComposerIf, ThreadEmpty) — replaced by AuiIf. The codemod migrates these.

You'll continue to see deprecation warnings on these; expect removal in a future minor release.

Getting Help