Architecture

How AuiProvider, the assistant client, its scopes, and the Vue composables fit together.

A Vue chat built with assistant-ui has three layers. A runtime holds the messages and talks to your backend. The assistant client exposes the runtime as named scopes with state, methods, and events. The Vue layer, made of AuiProvider, the composables, and the primitives, reads those scopes and calls their methods.

This component, from the @assistant-ui/vue README, shows all three:

app/components/Assistant.client.vue
<script setup lang="ts">
import {
  AuiConfig,
  AuiProvider,
  ComposerPrimitiveInput,
  ComposerPrimitiveSend,
  MessagePrimitiveParts,
  ThreadPrimitiveMessages,
  ThreadPrimitiveViewport,
} from "@assistant-ui/vue";
import { AISDKChat } from "@assistant-ui/ai-sdk";

const config = AuiConfig({ threads: AISDKChat() });
</script>

<template>
  <AuiProvider :config="config">
    <ThreadPrimitiveViewport class="h-dvh overflow-y-auto">
      <ThreadPrimitiveMessages>
        <MessagePrimitiveParts />
      </ThreadPrimitiveMessages>
    </ThreadPrimitiveViewport>
    <ComposerPrimitiveInput placeholder="Message..." />
    <ComposerPrimitiveSend>Send</ComposerPrimitiveSend>
  </AuiProvider>
</template>

AISDKChat() is the runtime, AuiConfig and AuiProvider create the client, and the primitives render it.

The runtime

A runtime fills the threads key of AuiConfig. AISDKChat and AISDKThreads run AI SDK chats against a streaming route, and RuntimeAdapter connects a runtime you build over your own store. The runtime decides where messages live, how a run starts and stops, and which actions are available. Runtimes compares them.

The runtimes are the same code that runs @assistant-ui/react. Both bindings sit on @assistant-ui/core, @assistant-ui/store, and @assistant-ui/tap, where runtimes are written as tap resources. The AI SDK runtime calls React hooks from @ai-sdk/react inside such a resource, and tap runs them without rendering anything. That is why the AI SDK path needs react installed in a Vue app.

The assistant client

AuiProvider passes its config to createAssistantClient and provides the resulting client to its slot. The client is a set of named scopes. Each scope has state, read as s.<scope> in a useAuiState selector, methods called as aui.<scope>.<method>(), and events named <scope>.<event>.

ScopeHolds
threadsThe thread list: thread ids, the selected thread, loading state.
threadThe selected thread: messages, run state, capabilities.
threadListItemOne thread's list entry: title, status, archive and delete.
composerA composer: draft text, attachments, send and cancel.
messageOne message: role, parts, status, branches.
partOne message part: text, reasoning, tool call, or data.
attachmentOne composer or message attachment.
suggestions, suggestionThe suggestion list and one suggestion.
tools, dataRenderers, modelContextRegistered tool UIs, data renderers, and the context sent to the model.
chainOfThoughtA group of reasoning and tool-call parts, when the app provides one.

State and methods lists every field and method, and Events lists every event.

Scopes follow the component tree

The runtime installs the thread-level scopes. The iterator primitives add item scopes: each row they render is wrapped in a nested provider that binds that row's item. The same component therefore reads a different message, part, or attachment in each row.

AuiProvider                     threads, thread, threadListItem, composer, suggestions, tools, modelContext
└─ ThreadPrimitiveMessages      one row per message: message, and its edit composer as composer
   └─ MessagePrimitiveParts     one entry per part: part
      └─ your part component    reads s.part, s.message, and s.thread

A component outside the rows reads the thread composer as s.composer; inside a message row, s.composer is that message's edit composer. State and actions has the full placement table.

Reading and acting

useAuiState(selector) returns a computed ref that updates when the selected value changes. useAui() returns a stable client for event handlers. useAuiEvent(name, callback) subscribes to events for the life of the component. AuiIf renders its slot while a selector returns true. The primitives use the same composables internally, so anything a primitive does can also be built from them. See State and actions.

A live config

AuiProvider watches its config prop. A new config object updates each scope's arguments in place, and a scope keeps its state as long as its key stays. Adding a key mounts that scope and removing it unmounts it. Pass a computed config to drive it from reactive state; Assistant client has an example.

A provider rendered inside another one must pass extends: the parent's client to inherit the scopes it does not define, or null to stay independent. Components and composables covers both.

Lifetime and server rendering

A provider creates its client when it mounts and keeps it while mounted. Remounting the provider, for example with a new key, creates a new client. Vue's server renderer never disposes a component's effect scope, so render the provider only in the browser. In Nuxt, put it in a .client.vue component; Server rendering explains the boundary.

Compared with React

ConcernReactVue
Provide a runtimeA runtime from a hook such as useChatRuntime(), passed to a providerAuiProvider with AuiConfig({ threads: AISDKChat() })
Read stateuseAuiState(selector) returns the valueuseAuiState(selector) returns a computed ref
Call actionsuseAui()useAui()
Primitive namesNamespaces such as ThreadPrimitive.ViewportNamed exports such as ThreadPrimitiveViewport
Custom elementsasChildAttributes and listeners pass to the rendered element
Per-item renderingRender functions and components propsDefault and named slots scoped to each item
Tool UI propsPart fields spread as propsOne tool prop of type ToolUIProps
Styled componentsThe shadcn registry for ReactThe shadcn-vue registry items thread and thread-list