All exports from @assistant-ui/store.
Hooks
useAui()
function useAui(): AssistantClient;Returns the store from the nearest AuiProvider. The client is immutable: state updates keep its identity, while a structural change (a scope resolving to a different instance) produces a new client and re-renders consumers.
AuiConfig
function AuiConfig(config: AuiConfig.Input): AuiConfig;
type AuiConfig.Input = {
[K in ClientNames]?: ClientElement<K> | DerivedElement<K>;
};Builds the branded config object accepted by AuiProvider's config prop. Raw object literals are a type error.
The useAui(scopes) extension overload is deprecated; hoist const aui = useAui() and const config = AuiConfig(scopes) and use <AuiProvider extends={aui} config={config}> instead.
useAuiState
function useAuiState<T>(selector: (state: AssistantState) => T): T;Subscribes to a slice of state. Re-renders only when the selected value changes (compared by Object.is). The selector must return a specific value — not the entire state object. Scopes that may be unavailable can be read via s.optional.<scope>, which resolves to undefined instead of throwing.
useAuiEvent
function useAuiEvent<TEvent extends AssistantEventName>(
selector: AssistantEventSelector<TEvent>,
callback: AssistantEventCallback<TEvent>,
): void;Subscribes to events. The selector can be a string ("scope.event") or an object ({ scope, event }). Unsubscribes on unmount.
Components
AuiProvider
const aui = useAui();
const config = AuiConfig({ counter: CounterResource() });
// Top-level root
<AuiProvider config={config}>{children}</AuiProvider>
// Nested: extend the parent (or extends={null} to isolate)
<AuiProvider extends={aui} config={config}>{children}</AuiProvider>Provides an AssistantClient to the React tree, created from a config built with AuiConfig(...). Nested under a parent provider, extends is mandatory: extends={aui} extends the parent, extends={null} isolates. An empty config creates a client extending the extends client. ref receives the resulting client after mount. Child components access the client via useAui().
AuiIf
<AuiIf condition={(s) => s.counter.count > 0}>
<ResetButton />
</AuiIf>Renders children only when the condition returns true. Uses useAuiState internally.
RenderChildrenWithAccessor
<RenderChildrenWithAccessor
getItemState={(aui) => aui.todoList.todo({ index }).getState()}
>
{(getItem) =>
children({
get todo() {
return getItem();
},
})
}
</RenderChildrenWithAccessor>Sets up a lazy item accessor for list rendering. The getItem function defers reading state until the consumer accesses it — if the children render function never reads the item, no subscription is created. When children returns a propless component (e.g. {() => <Todo />}), the output is automatically memoized.
| Prop | Type |
|---|---|
getItemState | (aui: AssistantClient) => T |
children | (getItem: () => T) => ReactNode |
See Rendering Lists for the full pattern.
Resource utilities
Derived
function Derived<K extends ClientNames>(config: Derived.Props<K>): DerivedElement<K>;Creates a derived scope that points to data in a parent scope.
Derived({
source: "thread",
query: { index: 0 },
get: (aui) => aui.thread.message({ index: 0 }),
});The get function receives the current AssistantClient and must return a client created via useClientResource (or useClientLookup/useClientList) — typically the result of calling a parent scope method. The meta (source, query) acts as identity: when query changes between renders, a new accessor is returned in the same render pass — useful for keying child consumers like MessageByIndex by index.
attachTransformScopes
function attachTransformScopes(
hook: (...args: any[]) => any,
transform: (scopes: ScopesConfig, parent: AssistantClient) => void,
): void;Attaches a transform function to a resource hook — pass the hook function you gave to resource(), not the resource wrapper. When the resource is mounted via AuiProvider, the transform runs and can add or modify sibling scopes by mutating scopes. Transforms are applied iteratively — new root scopes trigger their own transforms.
One transform per hook. Throws on duplicate.
forwardTransformScopes
function forwardTransformScopes(
target: (...args: any[]) => any,
source: (...args: any[]) => any,
): void;Copies source's transform onto target, composing with any transform target already has. Use when one resource hook wraps another.
ScopesConfig
type ScopesConfig = {
[K in ClientNames]?: ClientElement<K> | DerivedElement<K>;
};The scopes object passed to AuiConfig and mutated by transforms.
Resource hooks
These are used inside Tap resources to integrate with Store.
useClientResource
function useClientResource<TMethods extends ClientMethods>(
element: ResourceElement<TMethods>,
): {
state: InferClientState<TMethods>;
methods: TMethods;
key: string | number | undefined;
};Wraps a single resource element into a client. Adds the client to the internal client stack for event scoping.
state is inferred from the element's getState() return type. If getState is not defined, state is undefined.
useClientLookup
function useClientLookup<TMethods extends ClientMethods>(
elements: readonly ResourceElement<TMethods>[],
): {
state: InferClientState<TMethods>[];
get: (lookup: { index: number } | { key: string }) => TMethods;
};Wraps a list of resource elements into clients. Each element must have a key (via withKey). Uses useClientResource internally for each element.
get resolves a client by index or key. Throws if the lookup doesn't match.
useClientList
function useClientList<TData, TMethods extends ClientMethods>(
props: useClientList.Props<TData, TMethods>,
): {
state: InferClientState<TMethods>[];
get: (lookup: { index: number } | { key: string }) => TMethods;
add: (data: TData) => void;
};Manages a dynamic list of clients with add/remove. Built on useClientLookup.
type useClientList.Props<TData, TMethods> = {
initialValues: TData[];
getKey: (data: TData) => string;
resource: Resource<TMethods, [useClientList.ResourceProps<TData>]>;
};
type useClientList.ResourceProps<TData> = {
key: string;
getInitialData: () => TData;
remove: () => void;
};getInitialData() is called once on mount. remove() removes the item from the list. Throws on duplicate key.
useAssistantClientRef
function useAssistantClientRef(): {
parent: AssistantClient;
current: AssistantClient | null;
};Returns a ref to the store being built. current is null during resource creation and populated after all sibling scopes are mounted. Use in useEffect to access sibling scopes at runtime.
useAssistantEmit
function useAssistantEmit(): <TEvent extends Exclude<AssistantEventName, "*">>(
event: TEvent,
payload: AssistantEventPayload[TEvent],
) => void;Returns a stable emit function. Events are delivered via microtask — listeners fire after the current state update settles.
getClientId
function getClientId(client: object): getClientId.ClientId;Returns the opaque identity of a bound client instance. Stable for the lifetime of the bound client regardless of accessor wrapping — a reliable WeakMap key for per-client caches. Throws for an unavailable scope's accessor.
Types
ScopeRegistry
interface ScopeRegistry {}Module augmentation point. Augment this interface to register scopes:
declare module "@assistant-ui/store" {
interface ScopeRegistry {
counter: {
methods: {
getState: () => { count: number };
increment: () => void;
};
meta?: { source: ClientNames; query: Record<string, unknown> };
events?: { "counter.incremented": { newCount: number } };
};
}
}methods is required. meta and events are optional.
ClientOutput
type ClientOutput<K extends ClientNames> = ClientSchemas[K]["methods"] & ClientMethods;The return type for a resource implementing scope K. Use as the return type annotation on your resource function.
ClientNames
type ClientNames = keyof ClientSchemas;Union of all registered scope names.
AssistantClient
type AssistantClient = {
[K in ClientNames]: AssistantClientAccessor<K>;
} & {
readonly optional: {
readonly [K in ClientNames]: AssistantClientAccessor<K> | undefined;
};
subscribe(listener: () => void): Unsubscribe;
on<TEvent extends AssistantEventName>(
selector: AssistantEventSelector<TEvent>,
callback: AssistantEventCallback<TEvent>,
): Unsubscribe;
};The store object returned by useAui(). Each scope is an accessor. optional exposes the same scopes, resolving an unavailable one to undefined instead of a throwing accessor: aui.optional.counter?.increment(). subscribe fires on any state change. on subscribes to typed events.
AssistantClientAccessor
type AssistantClientAccessor<K extends ClientNames> =
ClientSchemas[K]["methods"] & {
/** @deprecated Access the scope as a property instead. */
(): ClientSchemas[K]["methods"];
} & (
| ClientMeta<K>
| { source: "root"; query: Record<string, never> }
| { source: null; query: null }
) & { name: K };A scope accessor exposing the scope's methods as properties (aui.counter.increment()). Read .source, .query, and .name for metadata; check availability via aui.optional.counter. The call signature (aui.counter()) is deprecated and returns the same accessor.
AssistantState
type AssistantState = ScopeStates & {
readonly optional: {
readonly [K in keyof ScopeStates]: ScopeStates[K] | undefined;
};
};
type ScopeStates = {
[K in ClientNames]: ClientSchemas[K]["methods"] extends {
getState: () => infer S;
}
? S
: never;
};The state object passed to useAuiState selectors. Each key is the return type of that scope's getState(). optional exposes the same scopes, but an unavailable scope resolves to undefined instead of throwing: s.optional.counter?.count.
ClientMeta
type ClientMeta<K extends ClientNames> =
"meta" extends keyof ClientSchemas[K]
? Pick<ClientSchemas[K]["meta"], "source" | "query">
: never;The source and query shape for scope K, if meta is declared in ScopeRegistry.
ClientElement
type ClientElement<K extends ClientNames> = ResourceElement<ClientOutput<K>>;A resource element that implements scope K.
DerivedElement
type DerivedElement<K extends ClientNames> =
ResourceElement<ReturnType<AssistantClientAccessor<K>>>;The element returned by Derived(...) for scope K.
ClientMethods
interface ClientMethods {
[key: string | symbol]: (...args: any[]) => any;
}Base constraint for a scope's methods.
ClientSchema
type ClientSchema<
TMethods extends ClientMethods = ClientMethods,
TMeta extends { source: ClientNames; query: Record<string, unknown> } = never,
TEvents extends Record<string, unknown> = never,
> = {
methods: TMethods;
meta?: TMeta;
events?: TEvents;
};Helper for declaring a ScopeRegistry entry.
ClientEvents
type ClientEvents<K extends ClientNames> =
"events" extends keyof ClientSchemas[K]
? ClientSchemas[K]["events"] extends Record<`${K}.${string}`, unknown>
? ClientSchemas[K]["events"]
: never
: never;The event map declared by scope K, if any.
Unsubscribe
type Unsubscribe = () => void;Event types
AssistantEventName
type AssistantEventName = keyof AssistantEventPayload;Union of all registered event names, plus "*".
AssistantEventPayload
type AssistantEventPayload = ClientEventMap & {
"*": { [K in keyof ClientEventMap]: { event: K; payload: ClientEventMap[K] } }[keyof ClientEventMap];
};Maps event names to their payload types. The "*" key receives a wrapped { event, payload } object.
AssistantEventSelector
type AssistantEventSelector<TEvent extends AssistantEventName> =
| TEvent
| { scope: AssistantEventScope<TEvent>; event: TEvent };A string ("scope.event") or object ({ scope, event }). Strings default to scope matching the event's source.
AssistantEventScope
type AssistantEventScope<TEvent extends AssistantEventName> =
| "*"
| EventSource<TEvent>
| AncestorsOf<EventSource<TEvent>>;Valid scopes to listen at: the event's source scope, any ancestor of that scope, or "*" for all.
AssistantEventCallback
type AssistantEventCallback<TEvent extends AssistantEventName> = (
payload: AssistantEventPayload[TEvent],
) => void;normalizeEventSelector
function normalizeEventSelector<TEvent extends AssistantEventName>(
selector: AssistantEventSelector<TEvent>,
): { scope: AssistantEventScope<TEvent>; event: TEvent };Converts a string selector to { scope, event } form. Strings like "counter.incremented" become { scope: "counter", event: "counter.incremented" }.