@assistant-ui/store - Agent Guide#
Low-level, tap-based state management for assistant-ui. Generic primitives only - no chat/assistant domain logic.
Architecture#
Built on @assistant-ui/tap. Client-based state containers with type-safe definitions via module augmentation. React Context integration. Event system for cross-client communication.
File Map#
src/
├── index.ts # Public exports
├── types/client.ts # ScopeRegistry, ClientOutput, AssistantClient
├── types/events.ts # Event types
├── useAui.tsx # Main hook
├── useAuiState.tsx # State subscription
├── useAuiEvent.ts # Event subscription
├── AuiIf.tsx # Conditional render
├── Derived.ts # Derived client marker
├── attachTransformScopes.ts # Scope transforms (attachTransformScopes)
├── tapClientResource.ts # Client proxy wrapper for event scoping
├── tapClientLookup.ts # Index/key lookup: {state[], get()}
├── tapClientList.ts # Dynamic lists: {state[], get(), add()}
└── utils/ # Internal implementation
API by Audience#
End Users#
useAui() useAuiState(selector)
useAuiEvent(event,cb) AuiProvider AuiIf Derived()
Library Authors#
Above plus:
tapAssistantClientRef() tapAssistantEmit()
tapClientResource(element) tapClientLookup(getElements, deps)
tapClientList({ initialValues, getKey, resource })
attachTransformScopes() ClientOutput<K> ScopeRegistry
Patterns#
Client Definition#
declare module "@assistant-ui/store" {
interface ScopeRegistry {
foo: { methods: { getState: () => { bar: string }; update: (b: string) => void } };
}
}
Resource#
const FooClient = resource((): ClientOutput<"foo"> => {
const [state, setState] = tapState({ bar: "" });
return { getState: () => state, update: (b) => setState({ bar: b }) };
});
List Resource#
// Item resource receives { key, getInitialData, remove }
const ItemClient = resource((props: tapClientList.ResourceProps<Data>): ClientOutput<"item"> => {
const data = props.getInitialData();
const [state, setState] = tapState({ id: props.key, value: data.value });
return { getState: () => state, update, remove: props.remove };
});
// List using tapClientList
const list = tapClientList({
initialValues: [{ id: "1", value: "foo" }],
getKey: (d) => d.id,
resource: ItemClient,
});
// Returns: { state: State[], get: (lookup) => Methods, add: (data) => void }
useAui Flow#
splitClients → apply transformScopes → mount root clients → create derived accessors → merge
Invariants#
- Resources return methods (including
getState()) asClientOutput<K> useAuiStaterequires selector (throws if returning whole state)- Event names:
"clientName.eventName" - Derived needs
source,query,get(orgetMeta) - Single transformScopes per resource; transform receives
(scopes, parent)to inspect parent context
Design#
Progressive disclosure: Simple hooks for users, tap utilities for library authors, internals in utils/.
Terminology: "Client" (like React Query), "methods" (not "actions"), "meta" (optional source/query), "events" (optional).