Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

@amodalai/react

React bindings for a deployed Amodal runtime:

  1. Provider + hooks: AmodalProvider gives every hook a configured client; hooks read stores, invoke tools and skills, follow sessions, and subscribe to live events.
  2. Chat UI: ChatWidget (and its preset AmodalChat) renders a full streaming chat with tool calls, confirmations, and widgets. Detailed on the Chat page.
  3. Headless clients: SdkClient, RuntimeClient, and the SSE/chat-api utilities, for when a hook doesn't fit (imperative code, non-React state layers).

Installation

npm install @amodalai/react

AmodalProvider

Wrap your app to configure the clients used by provider-based hooks:

import { AmodalProvider } from '@amodalai/react';
 
<AmodalProvider runtimeUrl={window.location.origin}>{children}</AmodalProvider>;
PropRequiredMeaning
runtimeUrlyesBase URL of the agent's runtime server.
getTokennoReturns a bearer token for authenticated requests (sync or async).
getDevUsernoLocal development only: returns a user id sent as x-amodal-dev-user; honored only when the runtime's dev-auth shim is enabled.
metadatanoObservability-only values sent with every chat request; a map, or a function read on each send. See Request metadata.

For an app served by Amodal at the agent's own origin, use a same-origin runtimeUrl such as window.location.origin and omit getToken: hosted login is cookie-backed and the bearer is injected at the edge. For direct bearer authentication by callers trusted across the agent's scopes, return a runtime-compatible token from getToken. Multi-tenant apps use an application proxy as runtimeUrl and return an application token instead. AmodalChat only inherits the provider's URL: pass its getToken separately. See Authentication for supported token types.

useAmodalContext() returns the provider's internals (client: RuntimeClient, sdk: SdkClient, runtimeUrl, getToken) for anything the hooks don't cover; it throws outside a provider.

Request metadata

Every chat request from the SDK carries metadata that tells Amodal where the message came from. It is recorded on the run's trace and the session and is never sent to the model. Amodal shows it on the trace, session, and feedback pages, and filters runs, sessions, and feedback by userId and screen.

The SDK collects these keys from the browser:

KeyValue
screenPage path, without the query string or fragment.
pageTitledocument.title, when set.
referrerReferrer origin and path, without query or fragment.
userAgentnavigator.userAgent.
localenavigator.language.
timezoneThe browser's IANA time zone.
viewportWindow size as <width>x<height>.

Add what only your app knows through the metadata prop of AmodalProvider or ChatWidget. Your values replace the collected ones with the same key, so a readable screen name can replace the path. Pass a function to read current values on each message:

<AmodalProvider
  runtimeUrl={window.location.origin}
  metadata={() => ({ userId: currentUser.id, screen: currentScreenName(), plan: account.plan })}
>
  {children}
</AmodalProvider>

Values must be strings. A request accepts at most 50 keys, keys up to 64 characters, and values up to 1,024 characters. The runtime rejects a request over these limits, so the SDK drops any of your entries that breaks them, logs a console warning naming each one, and past 50 keys keeps yours over the collected ones. Any caller can send any value, so Amodal labels metadata unverified and shows the authenticated user beside it when the request carries one. Do not put secrets or sensitive personal data in metadata. The runtime reference describes the recorded attributes.

Reading and writing stores

These hooks call the runtime's direct store API. That API uses the global (empty-string) partition. The hooks do not inherit ChatWidget.scopeId; they read the same rows as agent store tools only when those tools use that partition. For tenant-scoped data, use scoped tools or an application backend that enforces scope. See Embedding.

useStoreQuery

const { data, error, isLoading, refetch } = useStoreQuery<Submission>('submissions', {
  where: { status: 'received' },
  orderBy: 'updated_at',
  order: 'desc',
  limit: 50,
});
// data: Array<{ key: string; value: Submission; updatedAt: string }>

where is an equality filter on payload fields; orderBy/order sort server-side; limit caps the result (requests above the server's 500-row page size are paged transparently). Call refetch() after an action to pull in new rows.

useStoreEntry

Read one document by key and update its payload:

const { data, isLoading, mutate, refetch } = useStoreEntry<Submission>('submissions', 'sub_bistro_ember');

mutate(next) POSTs the payload and refetches. The runtime derives the document key from the payload's key template, so next must include its key field(s).

useStoreActions

Use get and set in event handlers:

const submissions = useStoreActions<Submission>('submissions');
await submissions.set('sub_x', row);

set(key, value) accepts the key for compatibility; the runtime derives the stored key from value. The exported remove(key) calls a DELETE endpoint that the hosted runtime store router does not implement.

Running work

useToolRun

Invoke a durable tool ("execution": "durable" with an invoke trigger) from a button or form. run resolves with { sessionId, outcome }. Read the run status through useSession; tools can also write results to stores.

const analyze = useToolRun('analyze-submission');
 
await analyze.run({ submission_id });
// analyze.status: 'idle' | 'running' | 'succeeded' | 'failed'
// analyze.output.outcome, analyze.error, analyze.reset()

run resolves when the session reaches a terminal or operator-facing state: if the runtime answers synchronously with an outcome it is used directly; a queued start is watched (polled) to its outcome. Pass scopeId and scopeContext in the second argument to useToolRun when the tool operates on a scoped resource.

useToolTrigger enqueues through POST /api/enqueue, then watches the session. It has the same launcher interface as useToolRun. See Tools.

useSkillAction

Send a chat prompt that asks the agent to use a declared skill, then collect its text response. The model chooses how to follow the prompt. Use a durable tool when the action requires a defined execution path.

const namer = useSkillAction('subject-line-writer');
namer.execute({ topic: 'renewal reminder', count: 3 }); // params are serialized into the generated prompt
// namer.result (string | null), namer.loading, namer.error

Worked example

This example displays global store rows and refetches them after a durable tool runs:

function SubmissionsScreen() {
  const submissions = useStoreQuery<Submission>('submissions');
  const findings = useStoreQuery<RiskFinding>('risk_findings');
  const analyze = useToolRun('analyze-submission');
 
  async function onAnalyze(id: string) {
    await analyze.run({ submission_id: id });
    await Promise.all([submissions.refetch(), findings.refetch()]);
  }
 
  if (!submissions.data?.length) return <EmptyState hint="send `seed` in chat first" />;
  return <SubmissionsTable rows={submissions.data} findings={findings.data} onAnalyze={onAnalyze} />;
}

Sessions

Use session hooks to display durable tool runs, scheduled work, and pending user input. The runtime history API does not restrict results to the token's scope or user. For a multi-tenant UI, point the provider at an application proxy that authorizes session access and retains runtime credentials.

HookWhat it gives you
useSessions(query?)List the agent's sessions.
useSession(id)Full detail for one session: status, metadata (surface, current step, pending input request, progress), messages. Poll via refetch, or pair with useAgentEvents for live updates.
useSessionActions(id)Operator actions: answer(value, edits?) resolves a pending requestInput (the runtime enforces eligible answerers), cancel(), restart().
useScheduledTools()Sessions fired by schedules (cron), for a "scheduled runs" list.
useToolCallLog({ sessionId })Per-session tool-call audit log (options also accept since and limit).

Connections

useConnections() reads connection names and their connected or disconnected status. It returns data, error, isLoading, and refetch(). Configure and authorize OAuth connections through Studio or the CLI.

Live events

useAgentEvents subscribes to the runtime's event bus over SSE (/api/events):

const { events, latest, isConnected } = useAgentEvents({
  include: ["tool_calls"],   // server-side filter: categories or event-type names
  sessionId,                 // optional client-side narrowing to one session
  onEvent: (e) => { ... },
});

Type predicates (isToolCallStarted, isToolCallCompleted, isToolCallFailed) narrow the discriminated AgentEvent union. Reconnects resume from the last delivered event id.

Chat components

AmodalChat and ChatWidget

ChatWidget supports positions, history, theming, callbacks, and custom transports. AmodalChat wraps it with position="inline" and defaults serverUrl from the nearest AmodalProvider.

import { AmodalChat } from '@amodalai/react';
 
<AmodalChat />;

Props, positions, theming, and the widget's building blocks are documented on the Chat page.

AmodalAction

Fires a chat prompt from a button or other UI element:

import { AmodalAction } from '@amodalai/react';
 
<AmodalAction prompt="Summarize today's alerts" label="Get Summary" />;

AmodalAction asks the model to follow a prompt. useToolRun invokes a named durable handler directly. The handler may itself call a model; determinism depends on its implementation.

Confirmation and review cards

Chat renders approval controls for confirmation_required events. These components are also exported for standalone approval UIs:

  • ConfirmCard: endpoint, method, reason, approve/deny.
  • ReviewCard: the structured variant; full parameters, escalation badge, connection name.

Both take { confirmation, onApprove, onDeny }.

FormattedMarkdown (a prose-styled markdown renderer) is exported too, for rendering agent output outside the chat surfaces; inside ChatWidget, chat messages render with FormattedText.

Chat hooks

For a custom chat surface without ChatWidget:

HookDescription
useAmodalChatProvider-based chat session: send messages, streamed state, confirmation callbacks.
useChatThe widget's chat state machine over /chat/stream, serverUrl-based.
useChatStreamThe transport-agnostic reducer underneath useChat; you supply streamFn.
useSessionHistoryList and load previous chat sessions.

useAmodalChat and useChat prepare a session through POST /sessions when they mount and send its ID with the first message, so the first reply skips the session setup. A hook that resumes a session prepares nothing, and reset prepares the next session. If the runtime cannot prepare one, the first message starts its session as before. Pass prepareSession: false to useAmodalChat where a mount rarely leads to a message; AmodalAction does.

useAmodalBrief, useAmodalInsight, and useAmodalQuery each wrap one chat exchange through RuntimeClient.chatStream.

RuntimeClient.startTask and useAmodalTask require a custom host that mounts createTaskRouter from @amodalai/runtime and provides a createTaskSession factory. The hosted runtime does not expose their /task endpoints; use useToolTrigger with a durable tool for hosted background work. On a configured custom host, pass the task_id returned by startTask to useAmodalTask({ taskId: task_id }) to stream progress.

Headless clients

Everything the hooks do is available imperatively:

  • SdkClient (via useAmodalContext().sdk or constructed directly): runTool, triggerTool, watchSession, session actions, store access via .runtime, files (readFile, writeFile, listDir), automations.
  • RuntimeClient: chatStream, store document reads.
  • ChatClient / ChatStream: headless chat with typed events, no React.
  • streamChat, createSession, listSessions, getSessionHistory (chat API), streamSSE / parseSSELine (SSE utilities): the lowest-level pieces, used by the clients above.

Unsupported endpoints

useAuth, useTeamMembers, and useMemory call endpoints absent from the hosted runtime. They return a NOT_IMPLEMENTED SdkError through their error field. Use a supported application or platform API for these features.