API Reference

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.

PropType
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" }.