Hooks#
useAui()#
function useAui(): AssistantClient;
Returns the store from the nearest AuiProvider. Does not re-render when scopes change — returns a stable reference.
useAui(scopes)#
function useAui(scopes: useAui.Props): AssistantClient;
Takes the store from the nearest AuiProvider and extends it by filling the provided scopes. Returns a new AssistantClient that includes both the parent's scopes and the newly provided ones.
type useAui.Props = {
[K in ClientNames]?: ClientElement<K> | DerivedElement<K>;
};
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.
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#
<AuiProvider value={aui}>{children}</AuiProvider>
Provides an AssistantClient to the React tree. Child components can access it 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.
// static meta
Derived({
source: "thread",
query: { index: 0 },
get: (aui) => aui.thread().message({ index: 0 }),
});
// dynamic meta
Derived({
getMeta: (aui) => ({ source: "thread", query: { index } }),
get: (aui) => aui.thread().message({ index }),
});
The get function receives the current AssistantClient and must return the result of calling a parent scope method. It uses tapEffectEvent internally — always calls the latest closure.
attachTransformScopes#
function attachTransformScopes<T extends (...args: any[]) => ResourceElement<any>>(
resource: T,
transform: (scopes: ScopesConfig, parent: AssistantClient) => ScopesConfig,
): void;
Attaches a transform function to a resource. When the resource is mounted via useAui, the transform runs and can add or modify sibling scopes. Transforms are applied iteratively — new root scopes trigger their own transforms.
One transform per resource. Throws on duplicate.
Tap utilities#
These are used inside Tap resources to integrate with Store.
tapClientResource#
function tapClientResource<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.
tapClientLookup#
function tapClientLookup<TMethods extends ClientMethods>(
getElements: () => readonly ResourceElement<TMethods>[],
getElementsDeps: readonly unknown[],
): {
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 tapClientResource internally for each element.
get resolves a client by index or key. Throws if the lookup doesn't match.
tapClientList#
function tapClientList<TData, TMethods extends ClientMethods>(
props: tapClientList.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 tapClientLookup.
type tapClientList.Props<TData, TMethods> = {
initialValues: TData[];
getKey: (data: TData) => string;
resource: ContravariantResource<TMethods, tapClientList.ResourceProps<TData>>;
};
type tapClientList.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.
tapAssistantClientRef#
function tapAssistantClientRef(): {
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 tapEffect to access sibling scopes at runtime.
tapAssistantEmit#
function tapAssistantEmit(): <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.
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>;
} & {
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. subscribe fires on any state change. on subscribes to typed events.
AssistantClientAccessor#
type AssistantClientAccessor<K extends ClientNames> =
(() => ClientSchemas[K]["methods"]) &
(
| ClientMeta<K>
| { source: "root"; query: Record<string, never> }
| { source: null; query: null }
) &
{ name: K };
A scope accessor. Call it (aui.counter()) to resolve the scope's methods. Read .source, .query, and .name for metadata.
AssistantState#
type AssistantState = {
[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().
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.
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" }.