@aomi-labs/react 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,12 +10,16 @@ npm install @aomi-labs/react @assistant-ui/react react react-dom
10
10
  pnpm add @aomi-labs/react @assistant-ui/react react react-dom
11
11
  ```
12
12
 
13
- Optional peer dependencies for wallet features:
13
+ Optional dependencies for advanced custom wallet adapters:
14
14
 
15
15
  ```bash
16
16
  pnpm add wagmi viem
17
17
  ```
18
18
 
19
+ If you use the registry-installed `AomiFrame`, the default Para-backed
20
+ `AomiAdapterProvider` is already wired for you. Add `wagmi` only when you want
21
+ to bring your own adapter implementation.
22
+
19
23
  ## Quick Start
20
24
 
21
25
  Wrap your app with `AomiRuntimeProvider`, then use `useAomiRuntime()` anywhere inside:
@@ -117,7 +121,7 @@ Returns an `AomiRuntimeApi` object with:
117
121
  | `useControl()` | Model/namespace/API key state |
118
122
  | `useNotification()` | Toast notification context |
119
123
  | `useEventContext()` | Raw event system access |
120
- | `useWalletHandler()` | Wallet request handler (auto-sign with wagmi) |
124
+ | `useWalletHandler()` | Wallet request handler for custom adapter implementations |
121
125
  | `useNotificationHandler()` | Notification event handler |
122
126
 
123
127
  ## Utilities
package/dist/index.cjs CHANGED
@@ -37,7 +37,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
37
37
  // packages/react/src/index.ts
38
38
  var index_exports = {};
39
39
  __export(index_exports, {
40
- AomiClient: () => import_client3.AomiClient,
40
+ AomiClient: () => import_client4.AomiClient,
41
41
  AomiRuntimeProvider: () => AomiRuntimeProvider,
42
42
  ControlContextProvider: () => ControlContextProvider,
43
43
  EventContextProvider: () => EventContextProvider,
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  getChainInfo: () => getChainInfo,
51
51
  getNetworkName: () => getNetworkName,
52
52
  initThreadControl: () => initThreadControl,
53
+ toViemSignTypedDataArgs: () => import_client5.toViemSignTypedDataArgs,
53
54
  useAomiRuntime: () => useAomiRuntime,
54
55
  useControl: () => useControl,
55
56
  useCurrentThreadMessages: () => useCurrentThreadMessages,
@@ -62,11 +63,12 @@ __export(index_exports, {
62
63
  useWalletHandler: () => useWalletHandler
63
64
  });
64
65
  module.exports = __toCommonJS(index_exports);
65
- var import_client3 = require("@aomi-labs/client");
66
+ var import_client4 = require("@aomi-labs/client");
67
+ var import_client5 = require("@aomi-labs/client");
66
68
 
67
69
  // packages/react/src/runtime/aomi-runtime.tsx
68
70
  var import_react11 = require("react");
69
- var import_client2 = require("@aomi-labs/client");
71
+ var import_client3 = require("@aomi-labs/client");
70
72
 
71
73
  // packages/react/src/contexts/control-context.tsx
72
74
  var import_react = require("react");
@@ -245,6 +247,16 @@ var ThreadStore = class {
245
247
  // packages/react/src/contexts/control-context.tsx
246
248
  var import_jsx_runtime = require("react/jsx-runtime");
247
249
  var API_KEY_STORAGE_KEY = "aomi_api_key";
250
+ function getDefaultApp(apps) {
251
+ var _a;
252
+ return apps.includes("default") ? "default" : (_a = apps[0]) != null ? _a : null;
253
+ }
254
+ function resolveAuthorizedApp(app, authorizedApps, defaultApp) {
255
+ if (app && authorizedApps.includes(app)) {
256
+ return app;
257
+ }
258
+ return defaultApp;
259
+ }
248
260
  var ControlContext = (0, import_react.createContext)(null);
249
261
  function useControl() {
250
262
  const ctx = (0, import_react.useContext)(ControlContext);
@@ -307,13 +319,16 @@ function ControlContextProvider({
307
319
  }, [state.apiKey]);
308
320
  (0, import_react.useEffect)(() => {
309
321
  const fetchApps = async () => {
310
- var _a2, _b2;
322
+ var _a2;
311
323
  try {
312
324
  const apps = await aomiClientRef.current.getApps(
313
325
  sessionIdRef.current,
314
- { publicKey: publicKeyRef.current, apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0 }
326
+ {
327
+ publicKey: publicKeyRef.current,
328
+ apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
329
+ }
315
330
  );
316
- const defaultApp = apps.includes("default") ? "default" : (_b2 = apps[0]) != null ? _b2 : null;
331
+ const defaultApp = getDefaultApp(apps);
317
332
  setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
318
333
  authorizedApps: apps,
319
334
  defaultApp
@@ -327,7 +342,7 @@ function ControlContextProvider({
327
342
  }
328
343
  };
329
344
  void fetchApps();
330
- }, [state.apiKey]);
345
+ }, [state.apiKey, publicKey, sessionId]);
331
346
  (0, import_react.useEffect)(() => {
332
347
  const fetchModels = async () => {
333
348
  try {
@@ -373,13 +388,16 @@ function ControlContextProvider({
373
388
  }
374
389
  }, []);
375
390
  const getAuthorizedApps = (0, import_react.useCallback)(async () => {
376
- var _a2, _b2;
391
+ var _a2;
377
392
  try {
378
393
  const apps = await aomiClientRef.current.getApps(
379
394
  sessionIdRef.current,
380
- { publicKey: publicKeyRef.current, apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0 }
395
+ {
396
+ publicKey: publicKeyRef.current,
397
+ apiKey: (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
398
+ }
381
399
  );
382
- const defaultApp = apps.includes("default") ? "default" : (_b2 = apps[0]) != null ? _b2 : null;
400
+ const defaultApp = getDefaultApp(apps);
383
401
  setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
384
402
  authorizedApps: apps,
385
403
  defaultApp
@@ -399,8 +417,17 @@ function ControlContextProvider({
399
417
  const metadata = getThreadMetadataRef.current(sessionIdRef.current);
400
418
  return (_a2 = metadata == null ? void 0 : metadata.control) != null ? _a2 : initThreadControl();
401
419
  }, []);
420
+ const getCurrentThreadApp = (0, import_react.useCallback)(() => {
421
+ var _a2, _b2, _c;
422
+ const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(sessionIdRef.current)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
423
+ return (_c = resolveAuthorizedApp(
424
+ currentControl.app,
425
+ stateRef.current.authorizedApps,
426
+ stateRef.current.defaultApp
427
+ )) != null ? _c : "default";
428
+ }, []);
402
429
  const onModelSelect = (0, import_react.useCallback)(async (model) => {
403
- var _a2, _b2, _c, _d, _e;
430
+ var _a2, _b2, _c, _d;
404
431
  const threadId = sessionIdRef.current;
405
432
  const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
406
433
  const isProcessing2 = currentControl.isProcessing;
@@ -413,7 +440,11 @@ function ControlContextProvider({
413
440
  console.warn("[control-context] Cannot switch model while processing");
414
441
  return;
415
442
  }
416
- const app = (_d = (_c = currentControl.app) != null ? _c : stateRef.current.defaultApp) != null ? _d : "default";
443
+ const app = (_c = resolveAuthorizedApp(
444
+ currentControl.app,
445
+ stateRef.current.authorizedApps,
446
+ stateRef.current.defaultApp
447
+ )) != null ? _c : "default";
417
448
  console.log("[control-context] onModelSelect updating metadata", {
418
449
  threadId,
419
450
  model,
@@ -437,7 +468,7 @@ function ControlContextProvider({
437
468
  const result = await aomiClientRef.current.setModel(
438
469
  threadId,
439
470
  model,
440
- { app, apiKey: (_e = stateRef.current.apiKey) != null ? _e : void 0 }
471
+ { app, apiKey: (_d = stateRef.current.apiKey) != null ? _d : void 0 }
441
472
  );
442
473
  console.log("[control-context] onModelSelect backend result", result);
443
474
  } catch (err) {
@@ -461,6 +492,10 @@ function ControlContextProvider({
461
492
  );
462
493
  return;
463
494
  }
495
+ if (stateRef.current.authorizedApps.length > 0 && !stateRef.current.authorizedApps.includes(app)) {
496
+ console.warn("[control-context] Cannot select unauthorized app", { app });
497
+ return;
498
+ }
464
499
  console.log("[control-context] onAppSelect updating metadata", {
465
500
  threadId,
466
501
  app,
@@ -517,6 +552,7 @@ function ControlContextProvider({
517
552
  getAvailableModels,
518
553
  getAuthorizedApps,
519
554
  getCurrentThreadControl,
555
+ getCurrentThreadApp,
520
556
  onModelSelect,
521
557
  onAppSelect,
522
558
  isProcessing,
@@ -804,6 +840,8 @@ function useUser() {
804
840
  return {
805
841
  user: context.user,
806
842
  setUser: context.setUser,
843
+ addExtValue: context.addExtValue,
844
+ removeExtValue: context.removeExtValue,
807
845
  getUserState: context.getUserState,
808
846
  onUserStateChange: context.onUserStateChange
809
847
  };
@@ -813,7 +851,8 @@ function UserContextProvider({ children }) {
813
851
  isConnected: false,
814
852
  address: void 0,
815
853
  chainId: void 0,
816
- ensName: void 0
854
+ ensName: void 0,
855
+ ext: void 0
817
856
  });
818
857
  const userRef = (0, import_react5.useRef)(user);
819
858
  userRef.current = user;
@@ -829,6 +868,36 @@ function UserContextProvider({ children }) {
829
868
  return next;
830
869
  });
831
870
  }, []);
871
+ const addExtValue = (0, import_react5.useCallback)((key, value) => {
872
+ setUserState((prev) => {
873
+ var _a;
874
+ const next = __spreadProps(__spreadValues({}, prev), {
875
+ ext: __spreadProps(__spreadValues({}, (_a = prev.ext) != null ? _a : {}), {
876
+ [key]: value
877
+ })
878
+ });
879
+ StateChangeCallbacks.current.forEach((callback) => {
880
+ callback(next);
881
+ });
882
+ return next;
883
+ });
884
+ }, []);
885
+ const removeExtValue = (0, import_react5.useCallback)((key) => {
886
+ setUserState((prev) => {
887
+ if (!prev.ext || !(key in prev.ext)) {
888
+ return prev;
889
+ }
890
+ const nextExt = __spreadValues({}, prev.ext);
891
+ delete nextExt[key];
892
+ const next = __spreadProps(__spreadValues({}, prev), {
893
+ ext: Object.keys(nextExt).length > 0 ? nextExt : void 0
894
+ });
895
+ StateChangeCallbacks.current.forEach((callback) => {
896
+ callback(next);
897
+ });
898
+ return next;
899
+ });
900
+ }, []);
832
901
  const getUserState = (0, import_react5.useCallback)(() => userRef.current, []);
833
902
  const onUserStateChange = (0, import_react5.useCallback)(
834
903
  (callback) => {
@@ -845,6 +914,8 @@ function UserContextProvider({ children }) {
845
914
  value: {
846
915
  user,
847
916
  setUser,
917
+ addExtValue,
918
+ removeExtValue,
848
919
  getUserState,
849
920
  onUserStateChange
850
921
  },
@@ -1386,6 +1457,7 @@ function useAomiRuntime() {
1386
1457
 
1387
1458
  // packages/react/src/handlers/wallet-handler.ts
1388
1459
  var import_react8 = require("react");
1460
+ var import_client2 = require("@aomi-labs/client");
1389
1461
 
1390
1462
  // packages/react/src/state/wallet-buffer.ts
1391
1463
  function createWalletBuffer() {
@@ -1418,68 +1490,6 @@ function getAll(buffer) {
1418
1490
  }
1419
1491
 
1420
1492
  // packages/react/src/handlers/wallet-handler.ts
1421
- function asRecord(value) {
1422
- if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
1423
- return value;
1424
- }
1425
- function getToolArgs(payload) {
1426
- const root = asRecord(payload);
1427
- const nestedArgs = asRecord(root == null ? void 0 : root.args);
1428
- return nestedArgs != null ? nestedArgs : root != null ? root : {};
1429
- }
1430
- function parseChainId(value) {
1431
- if (typeof value === "number" && Number.isFinite(value)) return value;
1432
- if (typeof value !== "string") return void 0;
1433
- const trimmed = value.trim();
1434
- if (!trimmed) return void 0;
1435
- if (trimmed.startsWith("0x")) {
1436
- const parsedHex = Number.parseInt(trimmed.slice(2), 16);
1437
- return Number.isFinite(parsedHex) ? parsedHex : void 0;
1438
- }
1439
- const parsed = Number.parseInt(trimmed, 10);
1440
- return Number.isFinite(parsed) ? parsed : void 0;
1441
- }
1442
- function normalizeTxPayload(payload) {
1443
- var _a, _b, _c;
1444
- const root = asRecord(payload);
1445
- const args = getToolArgs(payload);
1446
- const ctx = asRecord(root == null ? void 0 : root.ctx);
1447
- const to = typeof args.to === "string" ? args.to : void 0;
1448
- if (!to) return null;
1449
- const valueRaw = args.value;
1450
- const value = typeof valueRaw === "string" ? valueRaw : typeof valueRaw === "number" && Number.isFinite(valueRaw) ? String(Math.trunc(valueRaw)) : void 0;
1451
- const data = typeof args.data === "string" ? args.data : void 0;
1452
- const chainId = (_c = (_b = (_a = parseChainId(args.chainId)) != null ? _a : parseChainId(args.chain_id)) != null ? _b : parseChainId(ctx == null ? void 0 : ctx.user_chain_id)) != null ? _c : parseChainId(ctx == null ? void 0 : ctx.userChainId);
1453
- return {
1454
- to,
1455
- value,
1456
- data,
1457
- chainId
1458
- };
1459
- }
1460
- function normalizeEip712Payload(payload) {
1461
- var _a;
1462
- const args = getToolArgs(payload);
1463
- const typedDataRaw = (_a = args.typed_data) != null ? _a : args.typedData;
1464
- let typedData;
1465
- if (typeof typedDataRaw === "string") {
1466
- try {
1467
- const parsed = JSON.parse(typedDataRaw);
1468
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1469
- typedData = parsed;
1470
- }
1471
- } catch (e) {
1472
- typedData = void 0;
1473
- }
1474
- } else if (typedDataRaw && typeof typedDataRaw === "object" && !Array.isArray(typedDataRaw)) {
1475
- typedData = typedDataRaw;
1476
- }
1477
- const description = typeof args.description === "string" ? args.description : void 0;
1478
- return {
1479
- typed_data: typedData,
1480
- description
1481
- };
1482
- }
1483
1493
  function useWalletHandler({
1484
1494
  sessionId,
1485
1495
  onRequestComplete
@@ -1494,7 +1504,7 @@ function useWalletHandler({
1494
1504
  const unsubscribe = subscribe2(
1495
1505
  "wallet_tx_request",
1496
1506
  (event) => {
1497
- const payload = normalizeTxPayload(event.payload);
1507
+ const payload = (0, import_client2.normalizeTxPayload)(event.payload);
1498
1508
  if (!payload) {
1499
1509
  console.warn("[aomi][wallet] Ignoring tx request with invalid payload", event.payload);
1500
1510
  return;
@@ -1510,7 +1520,7 @@ function useWalletHandler({
1510
1520
  "wallet_eip712_request",
1511
1521
  (event) => {
1512
1522
  var _a;
1513
- const payload = normalizeEip712Payload((_a = event.payload) != null ? _a : {});
1523
+ const payload = (0, import_client2.normalizeEip712Payload)((_a = event.payload) != null ? _a : {});
1514
1524
  enqueue(bufferRef.current, "eip712_sign", payload);
1515
1525
  syncState();
1516
1526
  }
@@ -1607,7 +1617,7 @@ function AomiRuntimeCore({
1607
1617
  const notificationContext = useNotification();
1608
1618
  const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
1609
1619
  const { user, onUserStateChange, getUserState } = useUser();
1610
- const { getControlState, getCurrentThreadControl } = useControl();
1620
+ const { getControlState, getCurrentThreadApp } = useControl();
1611
1621
  const {
1612
1622
  backendStateRef,
1613
1623
  polling,
@@ -1620,28 +1630,43 @@ function AomiRuntimeCore({
1620
1630
  onSyncEvents: dispatchSystemEvents,
1621
1631
  getPublicKey: () => getUserState().address,
1622
1632
  getUserState,
1623
- getApp: () => {
1624
- var _a, _b;
1625
- return (_b = (_a = getCurrentThreadControl().app) != null ? _a : getControlState().defaultApp) != null ? _b : "default";
1626
- },
1633
+ getApp: getCurrentThreadApp,
1627
1634
  getApiKey: () => getControlState().apiKey
1628
1635
  });
1636
+ const walletSnapshot = (0, import_react9.useCallback)(
1637
+ (nextUser) => ({
1638
+ address: nextUser.address,
1639
+ chainId: nextUser.chainId,
1640
+ isConnected: nextUser.isConnected,
1641
+ ensName: nextUser.ensName
1642
+ }),
1643
+ [getUserState]
1644
+ );
1645
+ const lastWalletStateRef = (0, import_react9.useRef)(walletSnapshot(getUserState()));
1629
1646
  (0, import_react9.useEffect)(() => {
1647
+ lastWalletStateRef.current = walletSnapshot(getUserState());
1630
1648
  const unsubscribe = onUserStateChange(async (newUser) => {
1649
+ const nextWalletState = walletSnapshot(newUser);
1650
+ const prevWalletState = lastWalletStateRef.current;
1651
+ if (prevWalletState.address === nextWalletState.address && prevWalletState.chainId === nextWalletState.chainId && prevWalletState.isConnected === nextWalletState.isConnected && prevWalletState.ensName === nextWalletState.ensName) {
1652
+ return;
1653
+ }
1654
+ lastWalletStateRef.current = nextWalletState;
1631
1655
  const sessionId = threadContext.currentThreadId;
1632
1656
  const message = JSON.stringify({
1633
1657
  type: "wallet:state_changed",
1634
- payload: {
1635
- address: newUser.address,
1636
- chainId: newUser.chainId,
1637
- isConnected: newUser.isConnected,
1638
- ensName: newUser.ensName
1639
- }
1658
+ payload: nextWalletState
1640
1659
  });
1641
1660
  await aomiClientRef.current.sendSystemMessage(sessionId, message);
1642
1661
  });
1643
1662
  return unsubscribe;
1644
- }, [onUserStateChange, aomiClientRef, threadContext.currentThreadId]);
1663
+ }, [
1664
+ onUserStateChange,
1665
+ aomiClientRef,
1666
+ threadContext.currentThreadId,
1667
+ getUserState,
1668
+ walletSnapshot
1669
+ ]);
1645
1670
  const threadContextRef = (0, import_react9.useRef)(threadContext);
1646
1671
  threadContextRef.current = threadContext;
1647
1672
  const currentThreadIdRef = (0, import_react9.useRef)(threadContext.currentThreadId);
@@ -1737,10 +1762,7 @@ function AomiRuntimeCore({
1737
1762
  polling,
1738
1763
  userAddress: user.address,
1739
1764
  setIsRunning,
1740
- getApp: () => {
1741
- var _a, _b;
1742
- return (_b = (_a = getCurrentThreadControl().app) != null ? _a : getControlState().defaultApp) != null ? _b : "default";
1743
- },
1765
+ getApp: getCurrentThreadApp,
1744
1766
  getApiKey: () => getControlState().apiKey,
1745
1767
  getUserState
1746
1768
  }),
@@ -1754,6 +1776,7 @@ function AomiRuntimeCore({
1754
1776
  threadContext.currentThreadId,
1755
1777
  threadContext.allThreadsMetadata,
1756
1778
  getControlState,
1779
+ getCurrentThreadApp,
1757
1780
  getUserState
1758
1781
  ]
1759
1782
  );
@@ -1899,6 +1922,8 @@ function AomiRuntimeCore({
1899
1922
  user: userContext.user,
1900
1923
  getUserState: userContext.getUserState,
1901
1924
  setUser: userContext.setUser,
1925
+ addExtValue: userContext.addExtValue,
1926
+ removeExtValue: userContext.removeExtValue,
1902
1927
  onUserStateChange: userContext.onUserStateChange,
1903
1928
  // Thread API
1904
1929
  currentThreadId: threadContext.currentThreadId,
@@ -1959,7 +1984,7 @@ function AomiRuntimeProvider({
1959
1984
  children,
1960
1985
  backendUrl = "http://localhost:8080"
1961
1986
  }) {
1962
- const aomiClient = (0, import_react11.useMemo)(() => new import_client2.AomiClient({ baseUrl: backendUrl }), [backendUrl]);
1987
+ const aomiClient = (0, import_react11.useMemo)(() => new import_client3.AomiClient({ baseUrl: backendUrl }), [backendUrl]);
1963
1988
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(ThreadContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(NotificationContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(UserContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(AomiRuntimeInner, { aomiClient, children }) }) }) });
1964
1989
  }
1965
1990
  function AomiRuntimeInner({
@@ -2045,6 +2070,7 @@ function useNotificationHandler({
2045
2070
  getChainInfo,
2046
2071
  getNetworkName,
2047
2072
  initThreadControl,
2073
+ toViemSignTypedDataArgs,
2048
2074
  useAomiRuntime,
2049
2075
  useControl,
2050
2076
  useCurrentThreadMessages,