All hooks exported by @aomi-labs/react. Every hook must be used inside AomiRuntimeProvider.
useAomiRuntime
The unified hook that combines all runtime APIs into a single interface. This is the primary way to interact with the Aomi runtime.
import { useAomiRuntime } from "@aomi-labs/react";
function MyComponent() {
const aomi = useAomiRuntime();
// User API — `user` is a nested UserState; read it with accessors
aomi.user; // UserState (nested: connection, evm, svm)
UserState.address(aomi.user); // "0x..." | undefined
UserState.isConnected(aomi.user); // boolean | undefined
aomi.setUser({ evm: { address: "0x..." } });
aomi.getUserState();
aomi.onUserStateChange((user) => { ... });
// Thread API
aomi.currentThreadId; // string
aomi.threadViewKey; // number (use as React key)
aomi.threadMetadata; // Map<string, ThreadMetadata>
aomi.getThreadMetadata(id);
await aomi.createThread();
await aomi.deleteThread(id);
await aomi.renameThread(id, "New Title");
await aomi.archiveThread(id);
aomi.selectThread(id);
// Chat API
aomi.isRunning; // boolean
aomi.getMessages(threadId?); // ThreadMessageLike[]
await aomi.sendMessage("Hello!");
aomi.cancelGeneration();
// Notification API
aomi.notifications; // Notification[]
aomi.showNotification({ type, title });
aomi.dismissNotification(id);
aomi.clearAllNotifications();
// Wallet API
aomi.pendingWalletRequests; // WalletRequest[]
aomi.startWalletRequest(id);
aomi.resolveWalletRequest(id, { txHash });
aomi.rejectWalletRequest(id, "reason");
// Event API
aomi.subscribe("event_type", callback);
await aomi.sendSystemCommand({ type, sessionId, payload });
aomi.sseStatus; // "connected" | "connecting" | "disconnected"
}
Return Type
type AomiRuntimeApi = {
user: UserState;
getUserState: () => UserState;
setUser: (data: Partial<UserState>) => void;
onUserStateChange: (cb: (user: UserState) => void) => () => void;
currentThreadId: string;
threadViewKey: number;
threadMetadata: Map<string, ThreadMetadata>;
getThreadMetadata: (id: string) => ThreadMetadata | undefined;
createThread: () => Promise<string>;
deleteThread: (id: string) => Promise<void>;
renameThread: (id: string, title: string) => Promise<void>;
archiveThread: (id: string) => Promise<void>;
selectThread: (id: string) => void;
isRunning: boolean;
getMessages: (threadId?: string) => ThreadMessageLike[];
sendMessage: (text: string) => Promise<void>;
cancelGeneration: () => void;
notifications: Notification[];
showNotification: (params: NotificationData) => string;
dismissNotification: (id: string) => void;
clearAllNotifications: () => void;
pendingWalletRequests: WalletRequest[];
startWalletRequest: (id: string) => void;
resolveWalletRequest: (id: string, result: WalletRequestResult) => void;
rejectWalletRequest: (id: string, error?: string) => void;
subscribe: (type: string, cb: EventSubscriber) => () => void;
sendSystemCommand: (event: {
type: string;
sessionId: string;
payload: unknown;
}) => Promise<void>;
sseStatus: SSEStatus;
};
useUser
Wallet and user state management. UserState is a nested object: EVM identity lives under evm, Solana under svm, and session-level connection facts under connection. Read fields with the UserState accessor functions rather than reaching into the object directly. useUser returns { user, setUser, addExtValue, removeExtValue, getUserState, onUserStateChange }.
import { useUser, UserState } from "@aomi-labs/react";
function WalletStatus() {
const { user, setUser, getUserState, onUserStateChange } = useUser();
UserState.address(user); // "0xABC..." | undefined
UserState.chainId(user); // 1 | undefined
UserState.isConnected(user); // boolean | undefined
UserState.ensName(user); // "vitalik.eth" | undefined
setUser({ connection: { is_connected: true }, evm: { address: "0x...", chain_id: 1 } });
const current = getUserState();
const unsubscribe = onUserStateChange((newUser) => {
console.log("User changed:", newUser);
});
}
UserState
UserState is canonicalized to the backend’s nested snake_case shape. The fields you read most often:
type UserState = {
connection?: {
is_connected?: boolean | null;
// ...auth and provider facts
} | null;
evm?: {
address?: string | null;
chain_id?: number | string | null;
ens_name?: string | null;
// ...aa, sponsorship
} | null;
svm?: {
address?: string | null;
// ...cluster, capabilities
} | null;
ext?: Record<string, unknown> | null;
};
Accessor functions normalize the nested shape for you: UserState.address, UserState.chainId, UserState.ensName, UserState.isConnected, UserState.svmAddress, and UserState.preferredPublicKey.
useThreadContext
Low-level thread state management. Use useAomiRuntime for most cases.
import { useThreadContext } from "@aomi-labs/react";
function ThreadManager() {
const {
currentThreadId,
setCurrentThreadId,
threadViewKey,
bumpThreadViewKey,
allThreads,
allThreadsMetadata,
getThreadMessages,
setThreadMessages,
getThreadMetadata,
updateThreadMetadata,
} = useThreadContext();
const messages = getThreadMessages(currentThreadId);
const meta = getThreadMetadata(currentThreadId);
}
Convenience Hooks
import {
useCurrentThreadMessages,
useCurrentThreadMetadata,
} from "@aomi-labs/react";
const messages = useCurrentThreadMessages();
const metadata = useCurrentThreadMetadata();
useControl
Model, app, and API key state management.
import { useControl } from "@aomi-labs/react";
function ControlPanel() {
const {
state,
setApiKey,
getAvailableModels,
getAuthorizedApps,
onModelSelect,
onAppSelect,
getCurrentThreadControl,
isProcessing,
getControlState,
onControlStateChange,
} = useControl();
state.apiKey;
state.availableModels;
state.authorizedApps;
state.defaultModel;
state.defaultApp;
const tc = getCurrentThreadControl();
tc.model;
tc.app;
}
ControlState
type ControlState = {
apiKey: string | null;
clientId: string | null;
availableModels: string[];
authorizedApps: string[];
appDescriptors: AomiAppDescriptor[];
defaultModel: string | null;
defaultApp: string | null;
byokKeys: Record<string, StoredByokKey>;
};
useNotification
Toast notification management.
import { useNotification } from "@aomi-labs/react";
function NotificationExample() {
const { notifications, showNotification, dismissNotification, clearAll } =
useNotification();
const id = showNotification({
type: "success",
title: "Transaction sent",
message: "0x123...abc",
duration: 5000,
});
dismissNotification(id);
}
Notification Type
type Notification = {
id: string;
type: "notice" | "success" | "error" | "wallet";
title: string;
message?: string;
duration?: number;
timestamp: number;
};
useEventContext
SSE subscription and outbound event dispatch.
import { useEventContext } from "@aomi-labs/react";
function EventListener() {
const { subscribe, sendOutboundSystem, sseStatus } = useEventContext();
useEffect(() => {
const unsubscribe = subscribe("wallet_tx_request", (event) => {
console.log("Wallet request received:", event.payload);
});
return unsubscribe;
}, [subscribe]);
await sendOutboundSystem({
type: "wallet:tx_complete",
sessionId: "thread-123",
payload: { txHash: "0x...", status: "success" },
});
}
useWalletHandler
Handles wallet transaction requests and EIP-712 signing requests from the AI backend.
useWalletHandler takes a { getSession } config that returns the ClientSession for the current thread, and returns { pendingRequests, setRequests, startRequest, resolveRequest, rejectRequest }. Each request’s kind is "transaction", "eip712_sign", or "solana_sign". When you resolve a request, the result.kind must match the request’s kind.
import { useWalletHandler } from "@aomi-labs/react";
function WalletHandler() {
const { pendingRequests, startRequest, resolveRequest, rejectRequest } =
useWalletHandler({
getSession: () => getCurrentSession(),
});
for (const req of pendingRequests) {
if (req.kind === "transaction") {
startRequest(req.id);
await resolveRequest(req.id, { kind: "transaction", txHash: "0x..." });
}
if (req.kind === "eip712_sign") {
startRequest(req.id);
await resolveRequest(req.id, { kind: "eip712_sign", signature: "0x..." });
}
if (req.kind === "solana_sign") {
startRequest(req.id);
await resolveRequest(req.id, { kind: "solana_sign", signedTx: "..." });
}
}
}
Next Steps
Last modified on June 4, 2026