React hooks

Headless React hooks for auth, progress, chat, approvals, and entities.

@lunnoa/client/react ships headless React hooks for Pattern B portals. There are no UI components: you render with your own design system. All hooks (except auth bootstrap) need a surrounding LunnoaAuthProvider.

These examples assume a Pattern B app from the Authentication guide.

Peers

PackageRequired when
react ≥ 18Any @lunnoa/client/react import
@ai-sdk/reactOnly for useAgentChat (pairs with the ai dependency already on @lunnoa/client)
bash
npm install @lunnoa/client react
npm install @ai-sdk/react   # only if you use useAgentChat

Auth (provider)

See Authentication for LunnoaAuthProvider, useLunnoaAuth, useLunnoaClient, and useAccessToken.

Execution progress

useExecutionProgress(executionId | null) subscribes via executions.watchProgress, keeps the latest snapshot, and aborts on unmount or id change.

progress.tsx
import { useExecutionProgress } from '@lunnoa/client/react';const { progress, status, error, refresh } = useExecutionProgress(executionId);// status: idle | loading | live | error | finished// refresh() → one-shot executions.getProgress

Needs input

useNeedsInput(executionOrProgress) derives the waiting NEEDS_INPUT step and optional pendingInput field schema, then exposes submitInput(values).

needs-input.tsx
import { useExecutionProgress, useNeedsInput } from '@lunnoa/client/react';const { progress } = useExecutionProgress(executionId);const { waiting, pendingInput, submitInput } = useNeedsInput(progress, {  onSubmitted: () => { /* refresh timeline */ },});if (waiting && pendingInput) {  // render form from pendingInput.fields, then:  // await submitInput({ [fieldId]: value });}

Null or non-waiting sources return waiting: false without throwing.

Agent chat

useAgentChat(agentId, taskId?) is a thin wrapper around AI SDK useChat + DefaultChatTransport, pointed at:

${baseUrl}/api/agents/${agentId}/tasks/${taskId}/stream-message

with a Bearer token from the auth provider. If taskId is omitted, the hook generates one with crypto.randomUUID() and keeps it for the hook lifetime.

chat.tsx
import { useAgentChat } from '@lunnoa/client/react';const { messages, send, stop, status, taskId, error } = useAgentChat(agentId);// await send('Hello');// await stop(); // aborts HTTP + best-effort server cancel

For lower-level streaming without React, use lunnoa.agentChat.streamMessage from the Agent chat guide.

Approvals inbox

useApprovalsInbox(options?) loads approvals.inbox() for the current user. Polling is off by default; pass pollIntervalMs: 15_000 for a live inbox.

approvals.tsx
import { useApprovalsInbox } from '@lunnoa/client/react';const { items, status, error, refresh, decide } = useApprovalsInbox({  pollIntervalMs: 15_000,});// await decide(item.approvalId, { decision: 'approved', comment: 'OK' });

After decide, the hook refreshes the inbox.

Entity list

useEntityList({ objectTypeSlug, ...listOptions }) wraps entities.list with page state and light filter helpers.

entities.tsx
import { useEntityList } from '@lunnoa/client/react';const {  data,  pagination,  page,  setPage,  setSearch,  setState,  status,  refresh,} = useEntityList({  objectTypeSlug: 'invoice',  expansion: ['attributes'],  pageSize: 25,});

Options match ListEntitiesOptions from the core SDK. setSearch / setState reset to page 1.

Related