Skip to main content
@aomi-labs/react provides runtime logic, state management, hooks, and an API client with zero UI opinions. Use it to build completely custom interfaces or integrate Aomi logic into existing applications. Use the headless React library when you need a custom layout, branded components, or a non-chat interaction model.

Installation

npm install @aomi-labs/react
Or using pnpm:
pnpm install @aomi-labs/react

Peer Dependencies

{
  "@assistant-ui/react": "^0.11.0",
  "react": "^18.0.0 || ^19.0.0",
  "react-dom": "^18.0.0 || ^19.0.0",
  "wagmi": "^2.0.0",
  "viem": "^2.0.0"
}
wagmi and viem are optional peer dependencies. Install them only if your app uses wallet connection; skip them for read only or non-wallet UIs.

What’s Included

Providers

ExportDescription
AomiRuntimeProviderTop-level provider that connects to the Aomi backend and sets up all contexts
ThreadContextProviderThread state management (messages, metadata, thread switching)
ExtUserProviderWallet/user state (address, chain, connection status)
NotificationContextProviderToast notification state
EventContextProviderSSE subscription and system event dispatch
ControlContextProviderModel/app/API key selection state

Hooks

HookDescription
useAomiRuntimeUnified API combining all sub-contexts (threads, messages, user, events, notifications, wallet)
useUserWallet/user state and setters
useThreadContextCurrent thread ID, messages map, metadata map
useCurrentThreadMessagesMessages for the active thread
useCurrentThreadMetadataMetadata (title, status) for the active thread
useControlModel/app/API key state and selection
useNotificationShow, dismiss, and list notifications
useEventContextSSE subscription and outbound event dispatch
useWalletHandlerHandle wallet transaction and signing requests
useNotificationHandlerProcess notification events from the backend

API Client

ExportDescription
AomiClientDirect HTTP client for the Aomi backend (usable outside React), re-exported from @aomi-labs/client

Utilities

ExportDescription
cnClass name merger (clsx + tailwind-merge)
formatAddressTruncates wallet addresses for display (0xABC...12)
getNetworkNameResolves chain ID to human-readable network name
getChainInfoReturns chain metadata (name, ticker, explorer URL)
SUPPORTED_CHAINSArray of supported chain configurations

Types

import type {
  AomiRuntimeApi,
  AomiMessage,
  ThreadMetadata,
  ThreadControlState,
  UserState,
  ControlState,
  Notification,
  NotificationType,
  WalletRequest,
  WalletTxPayload,
  WalletEip712Payload,
  InboundEvent,
  SSEStatus,
} from "@aomi-labs/react";

Basic Setup

Wrap your app with AomiRuntimeProvider:
import { AomiRuntimeProvider } from "@aomi-labs/react";

function App() {
  return (
    <AomiRuntimeProvider backendUrl="https://api.aomi.dev">
      <YourCustomUI />
    </AomiRuntimeProvider>
  );
}
AomiRuntimeProvider internally sets up all nested providers (ThreadContext, UserContext, NotificationContext, EventContext, ControlContext), so you get the full runtime with a single wrapper.

Provider Hierarchy

AomiRuntimeProvider wraps your app with a hierarchy of context providers:
AomiRuntimeProvider
└── ThreadContextProvider             ← Thread state (messages, metadata, switching)
    └── NotificationContextProvider   ← Toast notifications
        └── ExtUserProvider           ← Wallet/user state
            └── ControlContextProvider    ← Model/app/API key
                └── EventContextProvider  ← SSE + system events
                    └── {children}
Each layer provides specific functionality through its own hook:
ContextHookProvides
ThreaduseThreadContextThread ID, messages, metadata, thread operations
UseruseUserWallet address, chain ID, connection status
NotificationuseNotificationShow/dismiss notifications
ControluseControlModel selection, app, API key
EventuseEventContextSSE subscription, outbound events
UnifieduseAomiRuntimeAll of the above in one hook

Runtime Provider

AomiRuntimeProvider is the top-level provider that connects your React app to the Aomi backend. It sets up all contexts needed for chat, threads, user state, notifications, and events.

Props

PropTypeDefaultDescription
backendUrlstring"http://localhost:8080"URL of the Aomi backend API
childrenReactNodeYour application components

Typical Provider Hierarchy

In a Next.js App Router project, place AomiRuntimeProvider inside your wagmi and query providers:
// app/providers.tsx
"use client";

import { WagmiProvider } from "wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AomiRuntimeProvider } from "@aomi-labs/react";
import { wagmiConfig } from "./wagmi-config";

const queryClient = new QueryClient();

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WagmiProvider config={wagmiConfig}>
      <QueryClientProvider client={queryClient}>
        <AomiRuntimeProvider
          backendUrl={process.env.NEXT_PUBLIC_BACKEND_URL}
        >
          {children}
        </AomiRuntimeProvider>
      </QueryClientProvider>
    </WagmiProvider>
  );
}
// app/layout.tsx
import { Providers } from "./providers";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

Without Wallet Support

If your application does not need wallet functionality, skip the wagmi providers entirely:
import { AomiRuntimeProvider } from "@aomi-labs/react";

export function Providers({ children }) {
  return (
    <AomiRuntimeProvider backendUrl="https://api.aomi.dev">
      {children}
    </AomiRuntimeProvider>
  );
}
The ConnectButton control and runtime transaction handler will simply be inactive.

Using AomiClient Directly

For non-React code or server-side use, you can use the AomiClient class without any providers. Construct it with an options object, not a bare URL:
import { AomiClient } from "@aomi-labs/react";

const client = new AomiClient({ baseUrl: "https://api.aomi.dev" });

// List threads for a wallet address
const threads = await client.listThreads(sessionId, publicKey);

// Send a message
await client.sendMessage(sessionId, message, { app, apiKey });

// Get available models
const models = await client.getModels(sessionId);

When to Use Headless vs Widget

ScenarioRecommendation
Standard chat interface needed quicklyWidget
Existing design system, custom lookHeadless
Non-Next.js React appHeadless
Server-side API calls (no React)Headless (AomiClient class directly)
Want to start fast, customize laterWidget (edit source files)
Embedding in an existing complex layoutHeadless

Custom UI Tutorial

Build a complete chat interface from scratch:
  1. Create a MessageList component that renders messages using useThreadContext
  2. Create a ChatInput component that sends messages via useAomiRuntime
  3. Create a ThreadSwitcher to manage multiple conversations
  4. Compose them into your page layout
The key design principle: state lives in the library, components are pure presentational.

Handler Utilities

Wallet Handler

The useWalletHandler hook processes wallet transaction and signing requests from the backend:
import { useWalletHandler } from "@aomi-labs/react";

function WalletHandler() {
  const { pendingRequests, startRequest, resolveRequest, rejectRequest } =
    useWalletHandler({ getSession: () => getCurrentSession() });

  // Each request has a `kind`: "transaction" | "eip712_sign" | "solana_sign".
  // Resolve with a matching result kind, or call rejectRequest(id).

  return null; // Renders nothing — drive the requests programmatically
}
See the Hooks Reference for the full useWalletHandler API and result shapes.

Notification Handler

The useNotificationHandler hook processes notification events from the backend:
import { useNotificationHandler } from "@aomi-labs/react";

function NotificationListener() {
  const { notifications, unhandledCount, markDone } = useNotificationHandler({
    onNotification: (n) => {
      // Trigger your own toast or banner UI when a notification arrives
    },
  });

  // notifications: every notification received, newest first
  // unhandledCount: how many are not yet marked done
  // markDone(id): mark one as handled

  return null;
}

Event System Details

Event Types

The event context handles inbound events streamed from the backend over SSE, and lets you send outbound system messages back to the backend:
DirectionHowDescription
Server → Clientsubscribe(type, handler) delivers an InboundEventSystem notifications, wallet requests, status updates
Client → ServersendOutboundSystem({ type, sessionId, payload })UI state and system commands sent to the backend

Subscription

import { useEffect } from "react";
import { useEventContext } from "@aomi-labs/react";

function EventSubscriber() {
  const { subscribe, sseStatus } = useEventContext();

  useEffect(() => {
    // subscribe(eventType, handler) returns an unsubscribe function
    const unsubscribe = subscribe("wallet_tx_request", (event) => {
      console.log(event.payload);
    });
    return unsubscribe;
  }, [subscribe]);

  // sseStatus — "connected" | "connecting" | "disconnected"
  return (
    <div className="text-xs text-white/40">
      SSE: {sseStatus}
    </div>
  );
}

Sending Outbound System Messages

To send a system message back to the backend, use sendOutboundSystem. It takes an object with type, sessionId, and payload:
import { useEventContext } from "@aomi-labs/react";

function useTxComplete(sessionId: string) {
  const { sendOutboundSystem } = useEventContext();

  const notifyTxComplete = async (txHash: string) => {
    await sendOutboundSystem({
      type: "wallet:tx_complete",
      sessionId,
      payload: { txHash, status: "success" },
    });
  };

  return { notifyTxComplete };
}

Next Steps

Last modified on June 4, 2026