@aomi-labs/react 0.3.5 → 0.3.6

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/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_client4.AomiClient,
40
+ AomiClient: () => import_client3.AomiClient,
41
41
  AomiRuntimeProvider: () => AomiRuntimeProvider,
42
42
  ControlContextProvider: () => ControlContextProvider,
43
43
  EventContextProvider: () => EventContextProvider,
@@ -50,7 +50,7 @@ __export(index_exports, {
50
50
  getChainInfo: () => getChainInfo,
51
51
  getNetworkName: () => getNetworkName,
52
52
  initThreadControl: () => initThreadControl,
53
- toViemSignTypedDataArgs: () => import_client5.toViemSignTypedDataArgs,
53
+ toViemSignTypedDataArgs: () => import_client4.toViemSignTypedDataArgs,
54
54
  useAomiRuntime: () => useAomiRuntime,
55
55
  useControl: () => useControl,
56
56
  useCurrentThreadMessages: () => useCurrentThreadMessages,
@@ -63,12 +63,12 @@ __export(index_exports, {
63
63
  useWalletHandler: () => useWalletHandler
64
64
  });
65
65
  module.exports = __toCommonJS(index_exports);
66
+ var import_client3 = require("@aomi-labs/client");
66
67
  var import_client4 = require("@aomi-labs/client");
67
- var import_client5 = require("@aomi-labs/client");
68
68
 
69
69
  // packages/react/src/runtime/aomi-runtime.tsx
70
70
  var import_react11 = require("react");
71
- var import_client3 = require("@aomi-labs/client");
71
+ var import_client2 = require("@aomi-labs/client");
72
72
 
73
73
  // packages/react/src/contexts/control-context.tsx
74
74
  var import_react = require("react");
@@ -594,53 +594,6 @@ function ControlContextProvider({
594
594
 
595
595
  // packages/react/src/contexts/event-context.tsx
596
596
  var import_react2 = require("react");
597
- var import_client = require("@aomi-labs/client");
598
-
599
- // packages/react/src/state/event-buffer.ts
600
- function createEventBuffer() {
601
- return {
602
- inboundQueue: [],
603
- outboundQueue: [],
604
- sseStatus: "disconnected",
605
- lastEventId: null,
606
- subscribers: /* @__PURE__ */ new Map()
607
- };
608
- }
609
- function enqueueInbound(state, event) {
610
- state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
611
- status: "pending",
612
- timestamp: Date.now()
613
- }));
614
- }
615
- function subscribe(state, type, callback) {
616
- if (!state.subscribers.has(type)) {
617
- state.subscribers.set(type, /* @__PURE__ */ new Set());
618
- }
619
- state.subscribers.get(type).add(callback);
620
- return () => {
621
- var _a;
622
- (_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
623
- };
624
- }
625
- function dispatch(state, event) {
626
- const typeSubscribers = state.subscribers.get(event.type);
627
- if (typeSubscribers) {
628
- for (const callback of typeSubscribers) {
629
- callback(event);
630
- }
631
- }
632
- const allSubscribers = state.subscribers.get("*");
633
- if (allSubscribers) {
634
- for (const callback of allSubscribers) {
635
- callback(event);
636
- }
637
- }
638
- }
639
- function setSSEStatus(state, status) {
640
- state.sseStatus = status;
641
- }
642
-
643
- // packages/react/src/contexts/event-context.tsx
644
597
  var import_jsx_runtime2 = require("react/jsx-runtime");
645
598
  var EventContextState = (0, import_react2.createContext)(null);
646
599
  function useEventContext() {
@@ -657,52 +610,32 @@ function EventContextProvider({
657
610
  aomiClient,
658
611
  sessionId
659
612
  }) {
660
- const bufferRef = (0, import_react2.useRef)(null);
661
- if (!bufferRef.current) {
662
- bufferRef.current = createEventBuffer();
663
- }
664
- const buffer = bufferRef.current;
665
- const [sseStatus, setSseStatus] = (0, import_react2.useState)("disconnected");
666
- (0, import_react2.useEffect)(() => {
667
- setSSEStatus(buffer, "connecting");
668
- setSseStatus("connecting");
669
- const unsubscribe = aomiClient.subscribeSSE(
670
- sessionId,
671
- (event) => {
672
- enqueueInbound(buffer, {
673
- type: event.type,
674
- sessionId: event.session_id,
675
- payload: event
676
- });
677
- const inboundEvent = {
678
- type: event.type,
679
- sessionId: event.session_id,
680
- payload: event,
681
- status: "fetched",
682
- timestamp: Date.now()
683
- };
684
- dispatch(buffer, inboundEvent);
685
- },
686
- (error) => {
687
- console.error("SSE error:", error);
688
- setSSEStatus(buffer, "disconnected");
689
- setSseStatus("disconnected");
690
- }
691
- );
692
- setSSEStatus(buffer, "connected");
693
- setSseStatus("connected");
694
- return () => {
695
- unsubscribe();
696
- setSSEStatus(buffer, "disconnected");
697
- setSseStatus("disconnected");
698
- };
699
- }, [aomiClient, sessionId, buffer]);
700
- const subscribeCallback = (0, import_react2.useCallback)(
613
+ const subscribersRef = (0, import_react2.useRef)(/* @__PURE__ */ new Map());
614
+ const subscribe = (0, import_react2.useCallback)(
701
615
  (type, callback) => {
702
- return subscribe(buffer, type, callback);
616
+ const subs = subscribersRef.current;
617
+ if (!subs.has(type)) {
618
+ subs.set(type, /* @__PURE__ */ new Set());
619
+ }
620
+ subs.get(type).add(callback);
621
+ return () => {
622
+ var _a;
623
+ (_a = subs.get(type)) == null ? void 0 : _a.delete(callback);
624
+ };
703
625
  },
704
- [buffer]
626
+ []
705
627
  );
628
+ const dispatchEvent = (0, import_react2.useCallback)((event) => {
629
+ const subs = subscribersRef.current;
630
+ const typeSubs = subs.get(event.type);
631
+ if (typeSubs) {
632
+ for (const cb of typeSubs) cb(event);
633
+ }
634
+ const wildcardSubs = subs.get("*");
635
+ if (wildcardSubs) {
636
+ for (const cb of wildcardSubs) cb(event);
637
+ }
638
+ }, []);
706
639
  const sendOutbound = (0, import_react2.useCallback)(
707
640
  async (event) => {
708
641
  try {
@@ -717,50 +650,14 @@ function EventContextProvider({
717
650
  },
718
651
  [aomiClient]
719
652
  );
720
- const dispatchSystemEvents = (0, import_react2.useCallback)(
721
- (sessionId2, events) => {
722
- var _a;
723
- for (const event of events) {
724
- let eventType;
725
- let payload;
726
- if ((0, import_client.isInlineCall)(event)) {
727
- eventType = event.InlineCall.type;
728
- payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
729
- } else if ((0, import_client.isSystemNotice)(event)) {
730
- eventType = "system_notice";
731
- payload = { message: event.SystemNotice };
732
- } else if ((0, import_client.isSystemError)(event)) {
733
- eventType = "system_error";
734
- payload = { message: event.SystemError };
735
- } else if ((0, import_client.isAsyncCallback)(event)) {
736
- eventType = "async_callback";
737
- payload = event.AsyncCallback;
738
- } else {
739
- console.warn("Unknown system event type:", event);
740
- continue;
741
- }
742
- const inboundEvent = {
743
- type: eventType,
744
- sessionId: sessionId2,
745
- payload,
746
- status: "fetched",
747
- timestamp: Date.now()
748
- };
749
- enqueueInbound(buffer, {
750
- type: eventType,
751
- sessionId: sessionId2,
752
- payload
753
- });
754
- dispatch(buffer, inboundEvent);
755
- }
756
- },
757
- [buffer]
758
- );
759
653
  const contextValue = {
760
- subscribe: subscribeCallback,
654
+ subscribe,
655
+ dispatch: dispatchEvent,
761
656
  sendOutboundSystem: sendOutbound,
762
- dispatchInboundSystem: dispatchSystemEvents,
763
- sseStatus
657
+ // SSE is managed by ClientSession now — status is always "connected"
658
+ // when sessions are active. Individual session status can be queried
659
+ // from the session manager if needed.
660
+ sseStatus: "connected"
764
661
  };
765
662
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EventContextState.Provider, { value: contextValue, children });
766
663
  }
@@ -957,6 +854,40 @@ var import_react10 = require("@assistant-ui/react");
957
854
  // packages/react/src/runtime/orchestrator.ts
958
855
  var import_react6 = require("react");
959
856
 
857
+ // packages/react/src/runtime/session-manager.ts
858
+ var import_client = require("@aomi-labs/client");
859
+ var SessionManager = class {
860
+ constructor(clientFactory) {
861
+ this.clientFactory = clientFactory;
862
+ this.sessions = /* @__PURE__ */ new Map();
863
+ }
864
+ getOrCreate(threadId, opts) {
865
+ let session = this.sessions.get(threadId);
866
+ if (session) return session;
867
+ session = new import_client.Session(this.clientFactory(), __spreadProps(__spreadValues({}, opts), {
868
+ sessionId: threadId
869
+ }));
870
+ this.sessions.set(threadId, session);
871
+ return session;
872
+ }
873
+ get(threadId) {
874
+ return this.sessions.get(threadId);
875
+ }
876
+ close(threadId) {
877
+ const session = this.sessions.get(threadId);
878
+ if (session) {
879
+ session.close();
880
+ this.sessions.delete(threadId);
881
+ }
882
+ }
883
+ closeAll() {
884
+ for (const [threadId, session] of this.sessions) {
885
+ session.close();
886
+ }
887
+ this.sessions.clear();
888
+ }
889
+ };
890
+
960
891
  // packages/react/src/runtime/utils.ts
961
892
  var import_clsx = require("clsx");
962
893
  var import_tailwind_merge = require("tailwind-merge");
@@ -1059,194 +990,6 @@ var SUPPORTED_CHAINS = [
1059
990
  ];
1060
991
  var getChainInfo = (chainId) => chainId === void 0 ? void 0 : SUPPORTED_CHAINS.find((c) => c.id === chainId);
1061
992
 
1062
- // packages/react/src/state/backend-state.ts
1063
- function createBackendState() {
1064
- return {
1065
- runningThreads: /* @__PURE__ */ new Set()
1066
- };
1067
- }
1068
- function resolveThreadId(_state, threadId) {
1069
- return threadId;
1070
- }
1071
- function setThreadRunning(state, threadId, running) {
1072
- if (running) {
1073
- state.runningThreads.add(threadId);
1074
- } else {
1075
- state.runningThreads.delete(threadId);
1076
- }
1077
- }
1078
- function isThreadRunning(state, threadId) {
1079
- return state.runningThreads.has(threadId);
1080
- }
1081
-
1082
- // packages/react/src/runtime/message-controller.ts
1083
- var MessageController = class {
1084
- constructor(config) {
1085
- this.config = config;
1086
- }
1087
- inbound(threadId, msgs) {
1088
- if (!msgs) return;
1089
- const threadMessages = [];
1090
- for (const msg of msgs) {
1091
- const threadMessage = toInboundMessage(msg);
1092
- if (threadMessage) {
1093
- threadMessages.push(threadMessage);
1094
- }
1095
- }
1096
- this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
1097
- }
1098
- async outbound(message, threadId) {
1099
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
1100
- const backendState = this.config.backendStateRef.current;
1101
- const text = message.content.filter(
1102
- (part) => part.type === "text"
1103
- ).map(
1104
- (part) => part.text
1105
- ).join("\n");
1106
- if (!text) return;
1107
- const threadState = this.getThreadContextApi();
1108
- const existingMessages = threadState.getThreadMessages(threadId);
1109
- const userMessage = {
1110
- role: "user",
1111
- content: [{ type: "text", text }],
1112
- createdAt: /* @__PURE__ */ new Date()
1113
- };
1114
- threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
1115
- threadState.updateThreadMetadata(threadId, {
1116
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1117
- });
1118
- const backendThreadId = resolveThreadId(backendState, threadId);
1119
- const app = this.config.getApp();
1120
- const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1121
- const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
1122
- const clientId = (_g = (_f = this.config).getClientId) == null ? void 0 : _g.call(_f);
1123
- const userState = (_i = (_h = this.config).getUserState) == null ? void 0 : _i.call(_h);
1124
- try {
1125
- this.markRunning(threadId, true);
1126
- const response = await this.config.aomiClientRef.current.sendMessage(
1127
- backendThreadId,
1128
- text,
1129
- { app, publicKey, apiKey, userState, clientId }
1130
- );
1131
- if (response == null ? void 0 : response.messages) {
1132
- this.inbound(threadId, response.messages);
1133
- }
1134
- if (((_j = response == null ? void 0 : response.system_events) == null ? void 0 : _j.length) && this.config.onSyncEvents) {
1135
- this.config.onSyncEvents(backendThreadId, response.system_events);
1136
- }
1137
- if (response == null ? void 0 : response.is_processing) {
1138
- this.config.polling.start(threadId);
1139
- } else if (!this.config.polling.isPolling(threadId)) {
1140
- this.markRunning(threadId, false);
1141
- }
1142
- } catch (error) {
1143
- console.error("Failed to send message:", error);
1144
- this.markRunning(threadId, false);
1145
- }
1146
- }
1147
- async cancel(threadId) {
1148
- var _a;
1149
- this.config.polling.stop(threadId);
1150
- const backendState = this.config.backendStateRef.current;
1151
- const backendThreadId = resolveThreadId(backendState, threadId);
1152
- try {
1153
- const response = await this.config.aomiClientRef.current.interrupt(backendThreadId);
1154
- if (response == null ? void 0 : response.messages) {
1155
- this.inbound(threadId, response.messages);
1156
- }
1157
- if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1158
- this.config.onSyncEvents(backendThreadId, response.system_events);
1159
- }
1160
- this.markRunning(threadId, false);
1161
- } catch (error) {
1162
- console.error("Failed to cancel:", error);
1163
- }
1164
- }
1165
- markRunning(threadId, running) {
1166
- var _a, _b;
1167
- setThreadRunning(this.config.backendStateRef.current, threadId, running);
1168
- if (this.config.threadContextRef.current.currentThreadId === threadId) {
1169
- (_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
1170
- }
1171
- }
1172
- getThreadContextApi() {
1173
- const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
1174
- return { getThreadMessages, setThreadMessages, updateThreadMetadata };
1175
- }
1176
- };
1177
-
1178
- // packages/react/src/runtime/polling-controller.ts
1179
- var PollingController = class {
1180
- constructor(config) {
1181
- this.config = config;
1182
- this.intervals = /* @__PURE__ */ new Map();
1183
- var _a;
1184
- this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
1185
- }
1186
- start(threadId) {
1187
- var _a, _b;
1188
- const backendState = this.config.backendStateRef.current;
1189
- if (this.intervals.has(threadId)) return;
1190
- const backendThreadId = resolveThreadId(backendState, threadId);
1191
- setThreadRunning(backendState, threadId, true);
1192
- const tick = async () => {
1193
- var _a2, _b2, _c, _d;
1194
- if (!this.intervals.has(threadId)) return;
1195
- try {
1196
- console.log(
1197
- "[PollingController] Fetching state for threadId:",
1198
- threadId
1199
- );
1200
- const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
1201
- const clientId = (_d = (_c = this.config).getClientId) == null ? void 0 : _d.call(_c);
1202
- const state = await this.config.aomiClientRef.current.fetchState(
1203
- backendThreadId,
1204
- userState,
1205
- clientId
1206
- );
1207
- if (!this.intervals.has(threadId)) return;
1208
- this.handleState(threadId, state);
1209
- } catch (error) {
1210
- console.error("Polling error:", error);
1211
- this.stop(threadId);
1212
- }
1213
- };
1214
- const intervalId = setInterval(tick, this.intervalMs);
1215
- this.intervals.set(threadId, intervalId);
1216
- (_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
1217
- }
1218
- stop(threadId) {
1219
- var _a, _b;
1220
- const intervalId = this.intervals.get(threadId);
1221
- if (intervalId) {
1222
- clearInterval(intervalId);
1223
- this.intervals.delete(threadId);
1224
- }
1225
- setThreadRunning(this.config.backendStateRef.current, threadId, false);
1226
- (_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
1227
- }
1228
- isPolling(threadId) {
1229
- return this.intervals.has(threadId);
1230
- }
1231
- stopAll() {
1232
- for (const threadId of this.intervals.keys()) {
1233
- this.stop(threadId);
1234
- }
1235
- }
1236
- handleState(threadId, state) {
1237
- var _a;
1238
- if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1239
- const backendState = this.config.backendStateRef.current;
1240
- const sessionId = resolveThreadId(backendState, threadId);
1241
- this.config.onSyncEvents(sessionId, state.system_events);
1242
- }
1243
- this.config.applyMessages(threadId, state.messages);
1244
- if (!state.is_processing) {
1245
- this.stop(threadId);
1246
- }
1247
- }
1248
- };
1249
-
1250
993
  // packages/react/src/runtime/orchestrator.ts
1251
994
  function useRuntimeOrchestrator(aomiClient, options) {
1252
995
  const threadContext = useThreadContext();
@@ -1254,90 +997,160 @@ function useRuntimeOrchestrator(aomiClient, options) {
1254
997
  threadContextRef.current = threadContext;
1255
998
  const aomiClientRef = (0, import_react6.useRef)(aomiClient);
1256
999
  aomiClientRef.current = aomiClient;
1257
- const backendStateRef = (0, import_react6.useRef)(createBackendState());
1258
1000
  const [isRunning, setIsRunning] = (0, import_react6.useState)(false);
1259
- const messageControllerRef = (0, import_react6.useRef)(null);
1260
- const pollingRef = (0, import_react6.useRef)(null);
1001
+ const sessionManagerRef = (0, import_react6.useRef)(null);
1002
+ if (!sessionManagerRef.current) {
1003
+ sessionManagerRef.current = new SessionManager(() => aomiClientRef.current);
1004
+ }
1261
1005
  const pendingFetches = (0, import_react6.useRef)(/* @__PURE__ */ new Set());
1262
- if (!pollingRef.current) {
1263
- pollingRef.current = new PollingController({
1264
- aomiClientRef,
1265
- backendStateRef,
1266
- applyMessages: (threadId, msgs) => {
1267
- var _a;
1268
- (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
1269
- },
1270
- onSyncEvents: options.onSyncEvents,
1271
- getUserState: options.getUserState,
1272
- getClientId: options.getClientId,
1273
- onStart: (threadId) => {
1006
+ const listenerCleanups = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
1007
+ const getSession = (0, import_react6.useCallback)(
1008
+ (threadId) => {
1009
+ var _a, _b, _c, _d, _e;
1010
+ const manager = sessionManagerRef.current;
1011
+ const existing = manager.get(threadId);
1012
+ if (existing) return existing;
1013
+ const session = manager.getOrCreate(threadId, {
1014
+ app: options.getApp(),
1015
+ publicKey: (_a = options.getPublicKey) == null ? void 0 : _a.call(options),
1016
+ apiKey: (_c = (_b = options.getApiKey) == null ? void 0 : _b.call(options)) != null ? _c : void 0,
1017
+ clientId: (_d = options.getClientId) == null ? void 0 : _d.call(options),
1018
+ userState: (_e = options.getUserState) == null ? void 0 : _e.call(options)
1019
+ });
1020
+ const cleanups = [];
1021
+ cleanups.push(
1022
+ session.on("messages", (msgs) => {
1023
+ const threadMessages = [];
1024
+ for (const msg of msgs) {
1025
+ const converted = toInboundMessage(msg);
1026
+ if (converted) threadMessages.push(converted);
1027
+ }
1028
+ threadContextRef.current.setThreadMessages(threadId, threadMessages);
1029
+ })
1030
+ );
1031
+ cleanups.push(
1032
+ session.on("processing_start", () => {
1033
+ if (threadContextRef.current.currentThreadId === threadId) {
1034
+ setIsRunning(true);
1035
+ }
1036
+ })
1037
+ );
1038
+ cleanups.push(
1039
+ session.on("processing_end", () => {
1040
+ if (threadContextRef.current.currentThreadId === threadId) {
1041
+ setIsRunning(false);
1042
+ }
1043
+ })
1044
+ );
1045
+ cleanups.push(
1046
+ session.on("wallet_tx_request", (req) => {
1047
+ var _a2;
1048
+ return (_a2 = options.onWalletRequest) == null ? void 0 : _a2.call(options, req);
1049
+ })
1050
+ );
1051
+ cleanups.push(
1052
+ session.on("wallet_eip712_request", (req) => {
1053
+ var _a2;
1054
+ return (_a2 = options.onWalletRequest) == null ? void 0 : _a2.call(options, req);
1055
+ })
1056
+ );
1057
+ cleanups.push(
1058
+ session.on("title_changed", ({ title }) => {
1059
+ threadContextRef.current.updateThreadMetadata(threadId, { title });
1060
+ })
1061
+ );
1062
+ const forwardEvent = (type) => session.on(type, (payload) => {
1063
+ var _a2;
1064
+ (_a2 = options.onEvent) == null ? void 0 : _a2.call(options, { type, payload, sessionId: threadId });
1065
+ });
1066
+ cleanups.push(forwardEvent("tool_update"));
1067
+ cleanups.push(forwardEvent("tool_complete"));
1068
+ cleanups.push(forwardEvent("system_notice"));
1069
+ cleanups.push(forwardEvent("system_error"));
1070
+ cleanups.push(forwardEvent("async_callback"));
1071
+ listenerCleanups.current.set(threadId, () => {
1072
+ for (const cleanup of cleanups) cleanup();
1073
+ });
1074
+ return session;
1075
+ },
1076
+ // Stable deps — option getters are refs
1077
+ []
1078
+ );
1079
+ const ensureInitialState = (0, import_react6.useCallback)(
1080
+ async (threadId) => {
1081
+ var _a;
1082
+ if (pendingFetches.current.has(threadId)) return;
1083
+ pendingFetches.current.add(threadId);
1084
+ try {
1085
+ const session = getSession(threadId);
1086
+ const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1087
+ if (userState) session.resolveUserState(userState);
1088
+ await session.fetchCurrentState();
1274
1089
  if (threadContextRef.current.currentThreadId === threadId) {
1275
- setIsRunning(true);
1090
+ setIsRunning(session.getIsProcessing());
1276
1091
  }
1277
- },
1278
- onStop: (threadId) => {
1092
+ } catch (error) {
1093
+ console.error("Failed to fetch initial state:", error);
1279
1094
  if (threadContextRef.current.currentThreadId === threadId) {
1280
1095
  setIsRunning(false);
1281
1096
  }
1097
+ } finally {
1098
+ pendingFetches.current.delete(threadId);
1282
1099
  }
1283
- });
1284
- }
1285
- if (!messageControllerRef.current) {
1286
- messageControllerRef.current = new MessageController({
1287
- aomiClientRef,
1288
- backendStateRef,
1289
- threadContextRef,
1290
- polling: pollingRef.current,
1291
- setGlobalIsRunning: setIsRunning,
1292
- getPublicKey: options.getPublicKey,
1293
- getApp: options.getApp,
1294
- getApiKey: options.getApiKey,
1295
- getClientId: options.getClientId,
1296
- getUserState: options.getUserState,
1297
- onSyncEvents: options.onSyncEvents
1298
- });
1299
- }
1300
- const ensureInitialState = (0, import_react6.useCallback)(async (threadId) => {
1301
- var _a, _b, _c, _d, _e;
1302
- if (pendingFetches.current.has(threadId)) return;
1303
- const backendThreadId = resolveThreadId(backendStateRef.current, threadId);
1304
- pendingFetches.current.add(threadId);
1305
- try {
1100
+ },
1101
+ [getSession]
1102
+ );
1103
+ const sendMessage = (0, import_react6.useCallback)(
1104
+ async (text, threadId) => {
1105
+ var _a;
1106
+ const session = getSession(threadId);
1306
1107
  const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1307
- const clientId = (_b = options.getClientId) == null ? void 0 : _b.call(options);
1308
- const state = await aomiClientRef.current.fetchState(
1309
- backendThreadId,
1310
- userState,
1311
- clientId
1312
- );
1313
- (_c = messageControllerRef.current) == null ? void 0 : _c.inbound(threadId, state.messages);
1314
- if (((_d = state.system_events) == null ? void 0 : _d.length) && options.onSyncEvents) {
1315
- options.onSyncEvents(backendThreadId, state.system_events);
1316
- }
1317
- if (threadContextRef.current.currentThreadId === threadId) {
1318
- if (state.is_processing) {
1319
- setIsRunning(true);
1320
- (_e = pollingRef.current) == null ? void 0 : _e.start(threadId);
1321
- } else {
1322
- setIsRunning(false);
1323
- }
1108
+ if (userState) session.resolveUserState(userState);
1109
+ const existingMessages = threadContextRef.current.getThreadMessages(threadId);
1110
+ const userMessage = {
1111
+ role: "user",
1112
+ content: [{ type: "text", text }],
1113
+ createdAt: /* @__PURE__ */ new Date()
1114
+ };
1115
+ threadContextRef.current.setThreadMessages(threadId, [
1116
+ ...existingMessages,
1117
+ userMessage
1118
+ ]);
1119
+ threadContextRef.current.updateThreadMetadata(threadId, {
1120
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1121
+ });
1122
+ await session.sendAsync(text);
1123
+ },
1124
+ [getSession]
1125
+ );
1126
+ const cancelGeneration = (0, import_react6.useCallback)(
1127
+ async (threadId) => {
1128
+ var _a;
1129
+ const session = (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadId);
1130
+ if (session) {
1131
+ await session.interrupt();
1324
1132
  }
1325
- } catch (error) {
1326
- console.error("Failed to fetch initial state:", error);
1327
- if (threadContextRef.current.currentThreadId === threadId) {
1328
- setIsRunning(false);
1133
+ },
1134
+ []
1135
+ );
1136
+ (0, import_react6.useEffect)(() => {
1137
+ return () => {
1138
+ var _a;
1139
+ (_a = sessionManagerRef.current) == null ? void 0 : _a.closeAll();
1140
+ for (const cleanup of listenerCleanups.current.values()) {
1141
+ cleanup();
1329
1142
  }
1330
- } finally {
1331
- pendingFetches.current.delete(threadId);
1332
- }
1143
+ listenerCleanups.current.clear();
1144
+ };
1333
1145
  }, []);
1334
1146
  return {
1335
- backendStateRef,
1336
- polling: pollingRef.current,
1337
- messageController: messageControllerRef.current,
1147
+ sessionManager: sessionManagerRef.current,
1148
+ getSession,
1338
1149
  isRunning,
1339
1150
  setIsRunning,
1340
1151
  ensureInitialState,
1152
+ sendMessage,
1153
+ cancelGeneration,
1341
1154
  aomiClientRef
1342
1155
  };
1343
1156
  }
@@ -1490,150 +1303,48 @@ function useAomiRuntime() {
1490
1303
 
1491
1304
  // packages/react/src/handlers/wallet-handler.ts
1492
1305
  var import_react8 = require("react");
1493
- var import_client2 = require("@aomi-labs/client");
1494
-
1495
- // packages/react/src/state/wallet-buffer.ts
1496
- function createWalletBuffer() {
1497
- return { queue: [], nextId: 1 };
1498
- }
1499
- function enqueue(buffer, kind, payload) {
1500
- const request = {
1501
- id: `wreq-${buffer.nextId++}`,
1502
- kind,
1503
- payload,
1504
- status: "pending",
1505
- timestamp: Date.now()
1506
- };
1507
- buffer.queue.push(request);
1508
- return request;
1509
- }
1510
- function dequeue(buffer, id) {
1511
- const index = buffer.queue.findIndex((r) => r.id === id);
1512
- if (index === -1) return null;
1513
- return buffer.queue.splice(index, 1)[0];
1514
- }
1515
- function markProcessing(buffer, id) {
1516
- const request = buffer.queue.find((r) => r.id === id);
1517
- if (!request || request.status !== "pending") return false;
1518
- request.status = "processing";
1519
- return true;
1520
- }
1521
- function getAll(buffer) {
1522
- return [...buffer.queue];
1523
- }
1524
-
1525
- // packages/react/src/handlers/wallet-handler.ts
1526
1306
  function useWalletHandler({
1527
- sessionId,
1528
- onRequestComplete
1307
+ getSession
1529
1308
  }) {
1530
- const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
1531
- const bufferRef = (0, import_react8.useRef)(createWalletBuffer());
1532
1309
  const [pendingRequests, setPendingRequests] = (0, import_react8.useState)([]);
1533
- const syncState = (0, import_react8.useCallback)(() => {
1534
- setPendingRequests(getAll(bufferRef.current));
1310
+ const requestsRef = (0, import_react8.useRef)([]);
1311
+ const enqueueRequest = (0, import_react8.useCallback)((request) => {
1312
+ requestsRef.current = [...requestsRef.current, request];
1313
+ setPendingRequests(requestsRef.current);
1535
1314
  }, []);
1536
- (0, import_react8.useEffect)(() => {
1537
- const unsubscribe = subscribe2(
1538
- "wallet_tx_request",
1539
- (event) => {
1540
- const payload = (0, import_client2.normalizeTxPayload)(event.payload);
1541
- if (!payload) {
1542
- console.warn("[aomi][wallet] Ignoring tx request with invalid payload", event.payload);
1543
- return;
1544
- }
1545
- enqueue(bufferRef.current, "transaction", payload);
1546
- syncState();
1547
- }
1548
- );
1549
- return unsubscribe;
1550
- }, [subscribe2, syncState]);
1551
- (0, import_react8.useEffect)(() => {
1552
- const unsubscribe = subscribe2(
1553
- "wallet_eip712_request",
1554
- (event) => {
1555
- var _a;
1556
- const payload = (0, import_client2.normalizeEip712Payload)((_a = event.payload) != null ? _a : {});
1557
- enqueue(bufferRef.current, "eip712_sign", payload);
1558
- syncState();
1559
- }
1560
- );
1561
- return unsubscribe;
1562
- }, [subscribe2, syncState]);
1563
- const startProcessingCb = (0, import_react8.useCallback)(
1564
- (id) => {
1565
- markProcessing(bufferRef.current, id);
1566
- syncState();
1567
- },
1568
- [syncState]
1569
- );
1570
1315
  const resolveRequest = (0, import_react8.useCallback)(
1571
1316
  (id, result) => {
1572
- var _a;
1573
- const removed = dequeue(bufferRef.current, id);
1574
- if (!removed) return;
1575
- let outbound;
1576
- if (removed.kind === "transaction") {
1577
- outbound = sendOutbound({
1578
- type: "wallet:tx_complete",
1579
- sessionId,
1580
- payload: {
1581
- txHash: (_a = result.txHash) != null ? _a : "",
1582
- status: "success",
1583
- amount: result.amount
1584
- }
1585
- });
1586
- } else {
1587
- const eip712Payload = removed.payload;
1588
- outbound = sendOutbound({
1589
- type: "wallet_eip712_response",
1590
- sessionId,
1591
- payload: {
1592
- status: "success",
1593
- signature: result.signature,
1594
- description: eip712Payload.description
1595
- }
1596
- });
1317
+ const session = getSession();
1318
+ if (!session) {
1319
+ console.error("[wallet-handler] No session available to resolve request");
1320
+ return;
1597
1321
  }
1598
- outbound.then(() => onRequestComplete == null ? void 0 : onRequestComplete());
1599
- syncState();
1322
+ requestsRef.current = requestsRef.current.filter((r) => r.id !== id);
1323
+ setPendingRequests(requestsRef.current);
1324
+ void session.resolve(id, result).catch((err) => {
1325
+ console.error("[wallet-handler] Failed to resolve request:", err);
1326
+ });
1600
1327
  },
1601
- [sendOutbound, sessionId, syncState, onRequestComplete]
1328
+ [getSession]
1602
1329
  );
1603
1330
  const rejectRequest = (0, import_react8.useCallback)(
1604
1331
  (id, error) => {
1605
- const removed = dequeue(bufferRef.current, id);
1606
- if (!removed) return;
1607
- let outbound;
1608
- if (removed.kind === "transaction") {
1609
- outbound = sendOutbound({
1610
- type: "wallet:tx_complete",
1611
- sessionId,
1612
- payload: {
1613
- txHash: "",
1614
- status: "failed"
1615
- }
1616
- });
1617
- } else {
1618
- const eip712Payload = removed.payload;
1619
- outbound = sendOutbound({
1620
- type: "wallet_eip712_response",
1621
- sessionId,
1622
- payload: {
1623
- status: "failed",
1624
- error: error != null ? error : "EIP-712 signing failed",
1625
- description: eip712Payload.description
1626
- }
1627
- });
1332
+ const session = getSession();
1333
+ if (!session) {
1334
+ console.error("[wallet-handler] No session available to reject request");
1335
+ return;
1628
1336
  }
1629
- outbound.then(() => onRequestComplete == null ? void 0 : onRequestComplete());
1630
- syncState();
1337
+ requestsRef.current = requestsRef.current.filter((r) => r.id !== id);
1338
+ setPendingRequests(requestsRef.current);
1339
+ void session.reject(id, error).catch((err) => {
1340
+ console.error("[wallet-handler] Failed to reject request:", err);
1341
+ });
1631
1342
  },
1632
- [sendOutbound, sessionId, syncState, onRequestComplete]
1343
+ [getSession]
1633
1344
  );
1634
1345
  return {
1635
1346
  pendingRequests,
1636
- startProcessing: startProcessingCb,
1347
+ enqueueRequest,
1637
1348
  resolveRequest,
1638
1349
  rejectRequest
1639
1350
  };
@@ -1648,19 +1359,25 @@ function AomiRuntimeCore({
1648
1359
  const threadContext = useThreadContext();
1649
1360
  const eventContext = useEventContext();
1650
1361
  const notificationContext = useNotification();
1651
- const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
1652
1362
  const { user, onUserStateChange, getUserState } = useUser();
1653
1363
  const { getControlState, getCurrentThreadApp, clearSecrets } = useControl();
1364
+ const sessionManagerRef = (0, import_react9.useRef)(null);
1365
+ const walletHandler = useWalletHandler({
1366
+ getSession: () => {
1367
+ var _a;
1368
+ return (_a = sessionManagerRef.current) == null ? void 0 : _a.get(threadContext.currentThreadId);
1369
+ }
1370
+ });
1654
1371
  const {
1655
- backendStateRef,
1656
- polling,
1657
- messageController,
1372
+ sessionManager,
1373
+ getSession,
1658
1374
  isRunning,
1659
1375
  setIsRunning,
1660
1376
  ensureInitialState,
1377
+ sendMessage: orchestratorSendMessage,
1378
+ cancelGeneration: orchestratorCancel,
1661
1379
  aomiClientRef
1662
1380
  } = useRuntimeOrchestrator(aomiClient, {
1663
- onSyncEvents: dispatchSystemEvents,
1664
1381
  getPublicKey: () => getUserState().address,
1665
1382
  getUserState,
1666
1383
  getApp: getCurrentThreadApp,
@@ -1668,8 +1385,11 @@ function AomiRuntimeCore({
1668
1385
  getClientId: () => {
1669
1386
  var _a;
1670
1387
  return (_a = getControlState().clientId) != null ? _a : void 0;
1671
- }
1388
+ },
1389
+ onWalletRequest: (request) => walletHandler.enqueueRequest(request),
1390
+ onEvent: (event) => eventContext.dispatch(event)
1672
1391
  });
1392
+ sessionManagerRef.current = sessionManager;
1673
1393
  const walletSnapshot = (0, import_react9.useCallback)(
1674
1394
  (nextUser) => ({
1675
1395
  address: nextUser.address,
@@ -1710,13 +1430,6 @@ function AomiRuntimeCore({
1710
1430
  (0, import_react9.useEffect)(() => {
1711
1431
  currentThreadIdRef.current = threadContext.currentThreadId;
1712
1432
  }, [threadContext.currentThreadId]);
1713
- const onWalletRequestComplete = (0, import_react9.useCallback)(() => {
1714
- polling.start(currentThreadIdRef.current);
1715
- }, [polling]);
1716
- const walletHandler = useWalletHandler({
1717
- sessionId: threadContext.currentThreadId,
1718
- onRequestComplete: onWalletRequestComplete
1719
- });
1720
1433
  (0, import_react9.useEffect)(() => {
1721
1434
  const unsubscribe = eventContext.subscribe(
1722
1435
  "user_state_request",
@@ -1733,10 +1446,6 @@ function AomiRuntimeCore({
1733
1446
  (0, import_react9.useEffect)(() => {
1734
1447
  void ensureInitialState(threadContext.currentThreadId);
1735
1448
  }, [ensureInitialState, threadContext.currentThreadId]);
1736
- (0, import_react9.useEffect)(() => {
1737
- const threadId = threadContext.currentThreadId;
1738
- setIsRunning(isThreadRunning(backendStateRef.current, threadId));
1739
- }, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
1740
1449
  (0, import_react9.useEffect)(() => {
1741
1450
  const threadId = threadContext.currentThreadId;
1742
1451
  const currentMeta = threadContext.getThreadMetadata(threadId);
@@ -1792,65 +1501,18 @@ function AomiRuntimeCore({
1792
1501
  }, [user.address, aomiClientRef]);
1793
1502
  const threadListAdapter = (0, import_react9.useMemo)(
1794
1503
  () => buildThreadListAdapter({
1795
- backendStateRef,
1796
1504
  aomiClientRef,
1797
1505
  threadContext,
1798
- currentThreadIdRef,
1799
- polling,
1800
- userAddress: user.address,
1801
- setIsRunning,
1802
- getApp: getCurrentThreadApp,
1803
- getApiKey: () => getControlState().apiKey,
1804
- getUserState
1506
+ setIsRunning
1805
1507
  }),
1806
1508
  [
1807
1509
  aomiClientRef,
1808
- polling,
1809
- user.address,
1810
- backendStateRef,
1811
1510
  setIsRunning,
1812
1511
  threadContext,
1813
1512
  threadContext.currentThreadId,
1814
- threadContext.allThreadsMetadata,
1815
- getControlState,
1816
- getCurrentThreadApp,
1817
- getUserState
1513
+ threadContext.allThreadsMetadata
1818
1514
  ]
1819
1515
  );
1820
- (0, import_react9.useEffect)(() => {
1821
- const backendState = backendStateRef.current;
1822
- const unsubscribe = eventContext.subscribe("title_changed", (event) => {
1823
- const sessionId = event.sessionId;
1824
- const payload = event.payload;
1825
- const newTitle = payload == null ? void 0 : payload.new_title;
1826
- if (typeof newTitle !== "string") return;
1827
- const targetThreadId = resolveThreadId(backendState, sessionId);
1828
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1829
- if (process.env.NODE_ENV !== "production") {
1830
- console.debug("[aomi][sse] title_changed", {
1831
- sessionId,
1832
- newTitle,
1833
- normalizedTitle,
1834
- currentThreadId: threadContextRef.current.currentThreadId,
1835
- targetThreadId
1836
- });
1837
- }
1838
- threadContextRef.current.setThreadMetadata((prev) => {
1839
- var _a, _b;
1840
- const next = new Map(prev);
1841
- const existing = next.get(targetThreadId);
1842
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1843
- next.set(targetThreadId, {
1844
- title: normalizedTitle,
1845
- status: nextStatus,
1846
- lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString(),
1847
- control: (_b = existing == null ? void 0 : existing.control) != null ? _b : initThreadControl()
1848
- });
1849
- return next;
1850
- });
1851
- });
1852
- return unsubscribe;
1853
- }, [eventContext, backendStateRef]);
1854
1516
  (0, import_react9.useEffect)(() => {
1855
1517
  const showToolNotification = (eventType) => (event) => {
1856
1518
  const payload = event.payload;
@@ -1877,9 +1539,7 @@ function AomiRuntimeCore({
1877
1539
  };
1878
1540
  }, [eventContext, notificationContext]);
1879
1541
  (0, import_react9.useEffect)(() => {
1880
- const unsubscribe = eventContext.subscribe("system_notice", (event) => {
1881
- const payload = event.payload;
1882
- const message = payload == null ? void 0 : payload.message;
1542
+ const unsubscribe = eventContext.subscribe("system_notice", (_event) => {
1883
1543
  });
1884
1544
  return unsubscribe;
1885
1545
  }, [eventContext, notificationContext]);
@@ -1887,34 +1547,36 @@ function AomiRuntimeCore({
1887
1547
  messages: currentMessages,
1888
1548
  setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
1889
1549
  isRunning,
1890
- onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
1891
- onCancel: () => messageController.cancel(threadContext.currentThreadId),
1550
+ onNew: async (message) => {
1551
+ const text = message.content.filter(
1552
+ (part) => part.type === "text"
1553
+ ).map((part) => part.text).join("\n");
1554
+ if (text) {
1555
+ await orchestratorSendMessage(text, threadContext.currentThreadId);
1556
+ }
1557
+ },
1558
+ onCancel: async () => {
1559
+ await orchestratorCancel(threadContext.currentThreadId);
1560
+ },
1892
1561
  convertMessage: (msg) => msg,
1893
1562
  adapters: { threadList: threadListAdapter }
1894
1563
  });
1895
1564
  (0, import_react9.useEffect)(() => {
1896
1565
  return () => {
1897
- polling.stopAll();
1566
+ sessionManager.closeAll();
1898
1567
  void clearSecrets();
1899
1568
  };
1900
- }, [polling, clearSecrets]);
1569
+ }, [sessionManager, clearSecrets]);
1901
1570
  const userContext = useUser();
1902
1571
  const sendMessage = (0, import_react9.useCallback)(
1903
1572
  async (text) => {
1904
- const appendMessage = {
1905
- role: "user",
1906
- content: [{ type: "text", text }]
1907
- };
1908
- await messageController.outbound(
1909
- appendMessage,
1910
- threadContext.currentThreadId
1911
- );
1573
+ await orchestratorSendMessage(text, threadContext.currentThreadId);
1912
1574
  },
1913
- [messageController, threadContext.currentThreadId]
1575
+ [orchestratorSendMessage, threadContext.currentThreadId]
1914
1576
  );
1915
1577
  const cancelGeneration = (0, import_react9.useCallback)(() => {
1916
- messageController.cancel(threadContext.currentThreadId);
1917
- }, [messageController, threadContext.currentThreadId]);
1578
+ void orchestratorCancel(threadContext.currentThreadId);
1579
+ }, [orchestratorCancel, threadContext.currentThreadId]);
1918
1580
  const getMessages = (0, import_react9.useCallback)(
1919
1581
  (threadId) => {
1920
1582
  const id = threadId != null ? threadId : threadContext.currentThreadId;
@@ -1928,9 +1590,10 @@ function AomiRuntimeCore({
1928
1590
  }, [threadListAdapter]);
1929
1591
  const deleteThread = (0, import_react9.useCallback)(
1930
1592
  async (threadId) => {
1593
+ sessionManager.close(threadId);
1931
1594
  await threadListAdapter.onDelete(threadId);
1932
1595
  },
1933
- [threadListAdapter]
1596
+ [threadListAdapter, sessionManager]
1934
1597
  );
1935
1598
  const renameThread = (0, import_react9.useCallback)(
1936
1599
  async (threadId, title) => {
@@ -1985,7 +1648,9 @@ function AomiRuntimeCore({
1985
1648
  clearAllNotifications: notificationContext.clearAll,
1986
1649
  // Wallet API
1987
1650
  pendingWalletRequests: walletHandler.pendingRequests,
1988
- startWalletRequest: walletHandler.startProcessing,
1651
+ startWalletRequest: () => {
1652
+ },
1653
+ // No-op: ClientSession manages processing state
1989
1654
  resolveWalletRequest: walletHandler.resolveRequest,
1990
1655
  rejectWalletRequest: walletHandler.rejectRequest,
1991
1656
  // Event API
@@ -2022,7 +1687,7 @@ function AomiRuntimeProvider({
2022
1687
  children,
2023
1688
  backendUrl = "http://localhost:8080"
2024
1689
  }) {
2025
- const aomiClient = (0, import_react11.useMemo)(() => new import_client3.AomiClient({ baseUrl: backendUrl }), [backendUrl]);
1690
+ const aomiClient = (0, import_react11.useMemo)(() => new import_client2.AomiClient({ baseUrl: backendUrl }), [backendUrl]);
2026
1691
  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 }) }) }) });
2027
1692
  }
2028
1693
  function AomiRuntimeInner({
@@ -2061,10 +1726,10 @@ function generateNotificationId() {
2061
1726
  function useNotificationHandler({
2062
1727
  onNotification
2063
1728
  } = {}) {
2064
- const { subscribe: subscribe2 } = useEventContext();
1729
+ const { subscribe } = useEventContext();
2065
1730
  const [notifications, setNotifications] = (0, import_react12.useState)([]);
2066
1731
  (0, import_react12.useEffect)(() => {
2067
- const unsubscribe = subscribe2("notification", (event) => {
1732
+ const unsubscribe = subscribe("notification", (event) => {
2068
1733
  var _a, _b;
2069
1734
  const payload = event.payload;
2070
1735
  const notification = {
@@ -2073,14 +1738,14 @@ function useNotificationHandler({
2073
1738
  title: (_b = payload.title) != null ? _b : "Notification",
2074
1739
  body: payload.body,
2075
1740
  handled: false,
2076
- timestamp: event.timestamp,
1741
+ timestamp: Date.now(),
2077
1742
  sessionId: event.sessionId
2078
1743
  };
2079
1744
  setNotifications((prev) => [notification, ...prev]);
2080
1745
  onNotification == null ? void 0 : onNotification(notification);
2081
1746
  });
2082
1747
  return unsubscribe;
2083
- }, [subscribe2, onNotification]);
1748
+ }, [subscribe, onNotification]);
2084
1749
  const unhandledCount = notifications.filter((n) => !n.handled).length;
2085
1750
  const markHandled = (0, import_react12.useCallback)((id) => {
2086
1751
  setNotifications(