@inlayai/react
The React layer — the chat handle, history, the provider shell, and the store underneath.
The React bindings: one hook owning the wasm client, a history hook, the
agent-agnostic provider shell (<InlayProvider>), and the
framework-agnostic store underneath. Everything re-exported from
@inlayai/sdk (row guards, timeline helpers, all data types, and the error
classes) is documented under the data model and
@inlayai/sdk — import it all from here, one front
door.
Two builds, one import. Bundlers resolve this package via the
react-server export condition: in a Server Component's module graph every
hook returns the frozen idle surface (safe in sync and async components,
zero React hooks called); in client graphs you get the live implementations
below. See Server and shared components.
useInlayChat
useInlayChat<TBindings, TTables, THandlers>(opts): InlayChatHandle<TBindings, TTables>
useInlayChat drives one conversation from React: owns the wasm client +
transport and subscribes the component. TBindings narrows binding reads
and output-row names to the agent's declared outputs and TTables narrows
useTable to the agent's tables (the generated hook supplies both);
THandlers types the tool-handler map.
UseInlayChatOptions
Prop
Type
Option semantics:
sessionis read LIVE (an inline() => …is fine) and re-called to refresh after a 401 — the session stays valid with no wiring.toolsis read live at each dispatch — no memoization needed.conversationIdreopens on mount AND on change; a failed reopen surfaces reactively onreopenError(and still warns in the console) — render it beside the composer instead of an inexplicably empty chat. The imperativehandle.resume(id)keeps its throw.versionpins every run to the generated version and warns once on skew (a resumed conversation from an older pin). The staleness gate isinlay codegen --check, not this warning. With a feed config, the separate identity/version guard below also controls whether rows are composited.failures— the failure contract. Default"snapshot":send/retryRESOLVE on a run failure and the reason lands onsnapshot.error(what every UI consumer hand-wrote as.catch(() => {}))."reject"keeps theInlayRunErrorrejection. Misuse errors — a send mid-turn, a retry with nothing retryable — reject in EVERY mode; they are programming errors, not run outcomes.smoothpaces the reveal of streaming text (assistant content, reasoning, and building artifacts — three channels) and snaps to full on completion. Honorsprefers-reduced-motion.
The handle — InlayChatHandle<TBindings, TTables>
23 members in four groups. The snapshot and flags are DATA; the per-slice reads are BOUND HOOKS (invoke as hooks, during render, unconditionally).
Data
| Member | Type | Notes |
|---|---|---|
snapshot | ChatSnapshot<TName> | re-renders on every commit; SMOOTHED (see the option). Output-row names narrowed to TName. Raw useInlayChat without generated graph config stays flat, with no composite kinds. |
conversationId | string | undefined | the flat alias for snapshot.conversation?.conversationId — URL writes, effect keys |
A generated hook replaces this flat snapshot type with an
agent-specific feed snapshot
when the graph has composite shapes. Its row union includes
FanOutRow,
SubagentRow, or
RepeatRow as appropriate, including
repeat-only graphs with no fan-out or subgraphs. These are possible shapes,
not a guarantee that every run produces each card; the generated hook
supplies the feed config automatically from NODES, authoritative
REGIONS, and subagent
metadata. The SDK does not infer fan-out regions from wiring; regenerate
older generated modules rather than relying on a runtime fallback.
Composition is identity/version-gated. The conversation's actual agentId
must match the expected agent, and its agentVersion must equal an explicit
expected version greater than zero. Mismatches, allowed foreign-agent reopens
(even with a matching version number), draft version 0, and unknown expected
versions disable all composites. With a feed config, the hook still subscribes
to and paces the full row array: inner rows stay visible flat, not dropped
by a switch to the top-level snapshot. Without a feed config, the raw hook's
top-level-only contract is unchanged. Config changes or a failed identity check
also retire previous composite references; matching feeds keep reference
threading after pacing.
Flags — canSend, canCancel, canRetry, plus reopenError: the last
AUTO-reopen failure (the conversationId option's mount effect), reactive —
render it beside the composer instead of a console.warn + an inexplicably
empty chat; it clears on the next attempt (resume / send / reset /
delete). The imperative resume(id) keeps its throw. Drive buttons off the flags,
never off status math. canSend ANDs the status map with the synchronous
latches (starting, a reopen in flight) that the map can't see; canRetry
checks them too; canCancel (STOP) ANDs its live-run map with the same
latches — something live to stop (streaming, or the send/reopen startup
windows). An idle row is continuable, not stoppable.
Actions — send(text, opts?), cancel(), delete(), retry(), reset(),
resume(id), submitToolResult(toolCallId, content),
loadEarlier(limit?). Identical semantics to the
client methods, plus the failures mode
above. loadEarlier resolves to the number of rows added (0 at the log's
start; snapshot.hasEarlier says whether there are any). delete() removes
the current conversation from the caller's history (a soft-delete tombstone —
the chat-side twin of useInlayHistory's deleteConversation) and returns to
the idle "new chat" state; a mounted history sidebar refetches on it. A no-op
when there's no conversation yet; a failed delete rejects without resetting.
Bound hooks — each subscribes to exactly its slice; nothing re-renders on an unrelated delta:
| Hook | Returns | Subscription |
|---|---|---|
useBinding(name) | TypedBinding<T> | undefined | just that binding (schema-validated at settle when declared) |
useTable(name) | TableRowView<T>[] | undefined | just that table's rows, including a row mid-stream |
useRun() | RunSnapshot | topology channel only — lifecycle, not tokens |
useSubagents() | Subagent[] | same channel |
useSubagent(path, outputSchemas?) | SubagentView | the run + that subagent's output slice |
useNode(path) | NodeView | BOTH channels (status + its subtree's streamed content) |
useToolCall(toolCallId) | { call; result?; delivery? } | undefined | exactly that call's pair + delivery |
useTimeline() | Row[] | the full path-tagged transcript |
useRowsAt(path) | Row[] | one path's slice |
useTranscript() | (UserRow | AssistantRow)[] | the flat chat view |
useBranches(path, edges) | BranchView[] | topology channel only; the latest firing's port set |
TypedBinding<T>
{ value: T; status: BindingStatus; format?: ArtifactFormat } — a binding
read through useBinding, with the value narrowed to the caller's T.
sessionStoreKey(session: SessionSource): string | null — the
store-identity key the memoization keys on (a static credential's value, or
null for a provider function, which is read live).
Branch labelling — selectBranches
selectBranches(run, path, edges): BranchView[] — the pure selector behind
useBranches: one node's outgoing wires, labelled against its latest
firing's port set in run.edgesFired, grouped by the source's
activation. A Collect router can fire several ports in one activation,
so multiple ports, and every wire on each, can read taken.
The generated client folds this into every node view
(nodes.<name>().branches), so the usual consumer never calls it directly.
Reach for it when driving something custom off the generated EDGES const.
Loop caveat: only the latest observed firing is reflected; a port used only
on an earlier visit reads untaken after a later firing through different
ports.
Do not reconstruct this set from scalar exitedVia: that field is only
the last matching edge's fromPort, not the full set of fired ports. For
older edges without activation stamps, selectBranches cannot group a
firing and falls back to the last matching port as well.
BranchEdge
{ fromPort: string; to: string } — one outgoing wire as the generated
EDGES const carries it: the exit port it leaves through and the generated
name of its target.
BranchStatus
"taken" | "untaken" | "pending" — "taken": this wire's port belongs to
the latest firing's observed port set (fan-out marks every wire of a taken
port); "untaken": a matching firing was observed but this port is not in
its set; "pending": no matching firing has been observed.
BranchView
A BranchEdge plus its status: BranchStatus — the element type of
selectBranches/useBranches results and of NodeView.branches in a
generated client.
useInlayHistory
useInlayHistory(opts): UseInlayHistoryResult — the calling end-user's
conversation list, for a "reopen a past chat" sidebar. Separate from the
chat handle because history isn't tied to one live conversation (its
async load/error/refetch shape doesn't fit the synchronous snapshot).
Refetches on option change; stale/unmounted loads are dropped (monotonic
request id).
UseInlayHistoryOptions
Prop
Type
UseInlayHistoryResult
Prop
Type
isLoadingis the first-load skeleton flag: no settled result yet for the CURRENT narrowing (agentId/status/limit/offset) — it ends when the first load settles (successfully OR with an error, in which caseerrorrenders instead) and returns when the narrowing changes.isFetchingis any fetch in flight (the initial load,refetch(),loadMore(), the post-delete re-sync) — grey out over rows already on screen. The SWR/RTK Query split;isLoadingis NOT "the list is empty" (an empty history is loaded, not loading — don't reach forloading && conversations.length === 0).erroris scoped the same way: a narrowing change clears the old view's error; a settled failure keeps it until a retry or a view change settles.hasMoreis a limit+1 probe (the extra row's EXISTENCE is the answer) and describes the served page, not fetch health. Without an explicitlimitthe hook pages atHISTORY_DEFAULT_LIMIT(100 — the server's own cap, so the probe can see PAST it):hasMore/loadMoreare honest out of the box, and a >100-conversation history is reachable.loadMore()appends the next page, deduped by id. Refused while a load is in flight OR a page request hasn't been consumed by its effect — the two windows that used to skip a page permanently (the offset advancing past a discarded fetch; the dedupe guards duplicates, never holes).refetch()resets to the base page and REPLACES. An explicitoffsetbase load replaces too (a page-jump, not an append).
Retrieval — useRetrieval
Query a retrieval index from a component (the built-in-RAG surface, rag.md
§7). A query is an action (call query), not a subscription — the hook
manages the fetching/error/hits state. The ACL + per-user-namespace
enforcement is server-side — the session's identity drives both.
const { query, hits, isFetching, error } = useRetrieval({
baseUrl,
session,
index: "kb",
});
// <input onSubmit={(q) => query(q)} /> … hits.map(…)UseRetrievalOptions
Prop
Type
UseRetrievalResult
Prop
Type
Citations — <Citations>
Render a grounded answer's citations as footnote markers (rag.md §7). Each
marker is a <sup>[n]</sup> linking the claim to its source; a drifted
citation (the corpus re-published since the answer) gets a drift marker.
Style-free — the customer styles via className / renderMarker.
const { hits } = useRetrieval(...);
<Citations citations={row.citations ?? []} currentGeneration={hits[0]?.generation} />CitationsProps
Prop
Type
The provider shell
The agent-agnostic connection provider — mount it once and it serves every agent's hooks (the connection + the history channel, nothing per-agent).
| Export | Kind | Notes |
|---|---|---|
INLAY_PRODUCTION_URL | const | "https://app.inlayai.com" — the default baseUrl of every hook, facade, and the provider (defined in @inlayai/sdk, re-exported here) |
InlayProvider | component | { session, baseUrl?, fetch?, children? } — the agent-agnostic connection + history channel |
InlayContextValue | type | { connection: { baseUrl, session, fetch? }; notifyHistory(); subscribeHistory(cb): unsub } |
useInlayContext() | hook | the context; throws a clear error outside a provider |
useInlayContextOptional() | hook | InlayContextValue | null — the generated hooks read the connection from it |
useHistoryChannel() | hook | { notify, subscribeHistory } — the invalidation channel: a mounted history hook refetches itself when a conversation starts or settles |
Standalone smoothing
useSmoothedContent(content, streaming, artifactActive?): string — the
lower-level paced reveal of a single string (the snapshot smoothing covers
the common case; this is for hand-rolled consumers). Returns the full
content when not streaming, under reduced motion, or on the artifact
handoff.
The store
createChatStore(opts: ChatStoreOptions): ChatStore — the
framework-agnostic, useSyncExternalStore-shaped core under useInlayChat:
subscribe/getSnapshot (flat), getFullSnapshot, getTimeline (the
commit-time index), subscribeRun/getRun, the action methods, and the
flag getters. Use it for a non-React host or a custom binding layer; apps
should prefer the hook.
ChatStoreOptions
Prop
Type
IDLE_SNAPSHOT: ChatSnapshot — the stable idle snapshot, also the
server-render value.