@aomi-labs/react 0.2.1 → 0.2.2

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.js ADDED
@@ -0,0 +1,2552 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+
21
+ // packages/react/src/backend/sse.ts
22
+ function extractSseData(rawEvent) {
23
+ const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
24
+ if (!dataLines.length) return null;
25
+ return dataLines.join("\n");
26
+ }
27
+ async function readSseStream(stream, signal, onMessage) {
28
+ const reader = stream.getReader();
29
+ const decoder = new TextDecoder();
30
+ let buffer = "";
31
+ try {
32
+ while (!signal.aborted) {
33
+ const { value, done } = await reader.read();
34
+ if (done) break;
35
+ buffer += decoder.decode(value, { stream: true });
36
+ buffer = buffer.replace(/\r/g, "");
37
+ let separatorIndex = buffer.indexOf("\n\n");
38
+ while (separatorIndex >= 0) {
39
+ const rawEvent = buffer.slice(0, separatorIndex);
40
+ buffer = buffer.slice(separatorIndex + 2);
41
+ const data = extractSseData(rawEvent);
42
+ if (data) {
43
+ onMessage(data);
44
+ }
45
+ separatorIndex = buffer.indexOf("\n\n");
46
+ }
47
+ }
48
+ } finally {
49
+ reader.releaseLock();
50
+ }
51
+ }
52
+ function createSseSubscriber({
53
+ backendUrl,
54
+ getHeaders,
55
+ shouldLog = process.env.NODE_ENV !== "production"
56
+ }) {
57
+ const subscriptions = /* @__PURE__ */ new Map();
58
+ const subscribe2 = (sessionId, onUpdate, onError) => {
59
+ const existing = subscriptions.get(sessionId);
60
+ const listener = { onUpdate, onError };
61
+ if (existing) {
62
+ existing.listeners.add(listener);
63
+ if (shouldLog) {
64
+ console.debug("[aomi][sse] listener added", {
65
+ sessionId,
66
+ listeners: existing.listeners.size
67
+ });
68
+ }
69
+ return () => {
70
+ existing.listeners.delete(listener);
71
+ if (shouldLog) {
72
+ console.debug("[aomi][sse] listener removed", {
73
+ sessionId,
74
+ listeners: existing.listeners.size
75
+ });
76
+ }
77
+ if (existing.listeners.size === 0) {
78
+ existing.stop("unsubscribe");
79
+ if (subscriptions.get(sessionId) === existing) {
80
+ subscriptions.delete(sessionId);
81
+ }
82
+ }
83
+ };
84
+ }
85
+ const subscription = {
86
+ abortController: null,
87
+ retries: 0,
88
+ retryTimer: null,
89
+ stopped: false,
90
+ listeners: /* @__PURE__ */ new Set([listener]),
91
+ stop: (reason) => {
92
+ var _a;
93
+ subscription.stopped = true;
94
+ if (subscription.retryTimer) {
95
+ clearTimeout(subscription.retryTimer);
96
+ subscription.retryTimer = null;
97
+ }
98
+ (_a = subscription.abortController) == null ? void 0 : _a.abort();
99
+ subscription.abortController = null;
100
+ if (shouldLog) {
101
+ console.debug("[aomi][sse] stop", {
102
+ sessionId,
103
+ reason,
104
+ retries: subscription.retries
105
+ });
106
+ }
107
+ }
108
+ };
109
+ const scheduleRetry = () => {
110
+ if (subscription.stopped) return;
111
+ subscription.retries += 1;
112
+ const delayMs = Math.min(500 * 2 ** (subscription.retries - 1), 1e4);
113
+ if (shouldLog) {
114
+ console.debug("[aomi][sse] retry scheduled", {
115
+ sessionId,
116
+ delayMs,
117
+ retries: subscription.retries
118
+ });
119
+ }
120
+ subscription.retryTimer = setTimeout(() => {
121
+ void open();
122
+ }, delayMs);
123
+ };
124
+ const open = async () => {
125
+ var _a;
126
+ if (subscription.stopped) return;
127
+ if (subscription.retryTimer) {
128
+ clearTimeout(subscription.retryTimer);
129
+ subscription.retryTimer = null;
130
+ }
131
+ const controller = new AbortController();
132
+ subscription.abortController = controller;
133
+ const openedAt = Date.now();
134
+ try {
135
+ const response = await fetch(`${backendUrl}/api/updates`, {
136
+ headers: getHeaders(sessionId),
137
+ signal: controller.signal
138
+ });
139
+ if (!response.ok) {
140
+ throw new Error(
141
+ `SSE HTTP ${response.status}: ${response.statusText}`
142
+ );
143
+ }
144
+ if (!response.body) {
145
+ throw new Error("SSE response missing body");
146
+ }
147
+ subscription.retries = 0;
148
+ await readSseStream(response.body, controller.signal, (data) => {
149
+ var _a2, _b;
150
+ let parsed;
151
+ try {
152
+ parsed = JSON.parse(data);
153
+ } catch (error) {
154
+ for (const item of subscription.listeners) {
155
+ (_a2 = item.onError) == null ? void 0 : _a2.call(item, error);
156
+ }
157
+ return;
158
+ }
159
+ for (const item of subscription.listeners) {
160
+ try {
161
+ item.onUpdate(parsed);
162
+ } catch (error) {
163
+ (_b = item.onError) == null ? void 0 : _b.call(item, error);
164
+ }
165
+ }
166
+ });
167
+ if (shouldLog) {
168
+ console.debug("[aomi][sse] stream ended", {
169
+ sessionId,
170
+ aborted: controller.signal.aborted,
171
+ stopped: subscription.stopped,
172
+ durationMs: Date.now() - openedAt
173
+ });
174
+ }
175
+ } catch (error) {
176
+ if (!controller.signal.aborted && !subscription.stopped) {
177
+ for (const item of subscription.listeners) {
178
+ (_a = item.onError) == null ? void 0 : _a.call(item, error);
179
+ }
180
+ }
181
+ }
182
+ if (!subscription.stopped) {
183
+ scheduleRetry();
184
+ }
185
+ };
186
+ subscriptions.set(sessionId, subscription);
187
+ void open();
188
+ return () => {
189
+ subscription.listeners.delete(listener);
190
+ if (shouldLog) {
191
+ console.debug("[aomi][sse] listener removed", {
192
+ sessionId,
193
+ listeners: subscription.listeners.size
194
+ });
195
+ }
196
+ if (subscription.listeners.size === 0) {
197
+ subscription.stop("unsubscribe");
198
+ if (subscriptions.get(sessionId) === subscription) {
199
+ subscriptions.delete(sessionId);
200
+ }
201
+ }
202
+ };
203
+ };
204
+ return { subscribe: subscribe2 };
205
+ }
206
+
207
+ // packages/react/src/backend/client.ts
208
+ var SESSION_ID_HEADER = "X-Session-Id";
209
+ var API_KEY_HEADER = "X-API-Key";
210
+ function toQueryString(payload) {
211
+ const params = new URLSearchParams();
212
+ for (const [key, value] of Object.entries(payload)) {
213
+ if (value === void 0 || value === null) continue;
214
+ params.set(key, String(value));
215
+ }
216
+ const qs = params.toString();
217
+ return qs ? `?${qs}` : "";
218
+ }
219
+ function withSessionHeader(sessionId, init) {
220
+ const headers = new Headers(init);
221
+ headers.set(SESSION_ID_HEADER, sessionId);
222
+ return headers;
223
+ }
224
+ async function postState(backendUrl, path, payload, sessionId, apiKey) {
225
+ const query = toQueryString(payload);
226
+ const url = `${backendUrl}${path}${query}`;
227
+ const headers = new Headers(withSessionHeader(sessionId));
228
+ if (apiKey) {
229
+ headers.set(API_KEY_HEADER, apiKey);
230
+ }
231
+ const response = await fetch(url, {
232
+ method: "POST",
233
+ headers
234
+ });
235
+ if (!response.ok) {
236
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
237
+ }
238
+ return await response.json();
239
+ }
240
+ var BackendApi = class {
241
+ constructor(backendUrl) {
242
+ this.backendUrl = backendUrl;
243
+ this.sseSubscriber = createSseSubscriber({
244
+ backendUrl,
245
+ getHeaders: (sessionId) => withSessionHeader(sessionId, { Accept: "text/event-stream" })
246
+ });
247
+ }
248
+ async fetchState(sessionId, userState) {
249
+ const url = new URL("/api/state", this.backendUrl);
250
+ if (userState) {
251
+ url.searchParams.set("user_state", JSON.stringify(userState));
252
+ }
253
+ const response = await fetch(url.toString(), {
254
+ headers: withSessionHeader(sessionId)
255
+ });
256
+ if (!response.ok) {
257
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
258
+ }
259
+ return await response.json();
260
+ }
261
+ async postChatMessage(sessionId, message, namespace, publicKey, apiKey) {
262
+ const payload = { message, namespace };
263
+ if (publicKey) {
264
+ payload.public_key = publicKey;
265
+ }
266
+ return postState(
267
+ this.backendUrl,
268
+ "/api/chat",
269
+ payload,
270
+ sessionId,
271
+ apiKey
272
+ );
273
+ }
274
+ async postSystemMessage(sessionId, message) {
275
+ return postState(
276
+ this.backendUrl,
277
+ "/api/system",
278
+ {
279
+ message
280
+ },
281
+ sessionId
282
+ );
283
+ }
284
+ async postInterrupt(sessionId) {
285
+ return postState(
286
+ this.backendUrl,
287
+ "/api/interrupt",
288
+ {},
289
+ sessionId
290
+ );
291
+ }
292
+ /**
293
+ * Subscribe to SSE updates for a session.
294
+ * Uses fetch streaming and reconnects on disconnects.
295
+ * Returns an unsubscribe function.
296
+ */
297
+ subscribeSSE(sessionId, onUpdate, onError) {
298
+ return this.sseSubscriber.subscribe(sessionId, onUpdate, onError);
299
+ }
300
+ async fetchThreads(publicKey) {
301
+ const url = `${this.backendUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
302
+ const response = await fetch(url);
303
+ if (!response.ok) {
304
+ throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
305
+ }
306
+ return await response.json();
307
+ }
308
+ async fetchThread(sessionId) {
309
+ const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
310
+ const response = await fetch(url, {
311
+ headers: withSessionHeader(sessionId)
312
+ });
313
+ if (!response.ok) {
314
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
315
+ }
316
+ return await response.json();
317
+ }
318
+ async createThread(threadId, publicKey) {
319
+ const body = {};
320
+ if (publicKey) body.public_key = publicKey;
321
+ const url = `${this.backendUrl}/api/sessions`;
322
+ const response = await fetch(url, {
323
+ method: "POST",
324
+ headers: withSessionHeader(threadId, {
325
+ "Content-Type": "application/json"
326
+ }),
327
+ body: JSON.stringify(body)
328
+ });
329
+ if (!response.ok) {
330
+ throw new Error(`Failed to create thread: HTTP ${response.status}`);
331
+ }
332
+ return await response.json();
333
+ }
334
+ async archiveThread(sessionId) {
335
+ const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
336
+ const response = await fetch(url, {
337
+ method: "POST",
338
+ headers: withSessionHeader(sessionId)
339
+ });
340
+ if (!response.ok) {
341
+ throw new Error(`Failed to archive thread: HTTP ${response.status}`);
342
+ }
343
+ }
344
+ async unarchiveThread(sessionId) {
345
+ const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
346
+ const response = await fetch(url, {
347
+ method: "POST",
348
+ headers: withSessionHeader(sessionId)
349
+ });
350
+ if (!response.ok) {
351
+ throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
352
+ }
353
+ }
354
+ async deleteThread(sessionId) {
355
+ const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
356
+ const response = await fetch(url, {
357
+ method: "DELETE",
358
+ headers: withSessionHeader(sessionId)
359
+ });
360
+ if (!response.ok) {
361
+ throw new Error(`Failed to delete thread: HTTP ${response.status}`);
362
+ }
363
+ }
364
+ async renameThread(sessionId, newTitle) {
365
+ const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
366
+ const response = await fetch(url, {
367
+ method: "PATCH",
368
+ headers: withSessionHeader(sessionId, {
369
+ "Content-Type": "application/json"
370
+ }),
371
+ body: JSON.stringify({ title: newTitle })
372
+ });
373
+ if (!response.ok) {
374
+ throw new Error(`Failed to rename thread: HTTP ${response.status}`);
375
+ }
376
+ }
377
+ async getSystemEvents(sessionId, count) {
378
+ const url = new URL("/api/events", this.backendUrl);
379
+ if (count !== void 0) {
380
+ url.searchParams.set("count", String(count));
381
+ }
382
+ const response = await fetch(url.toString(), {
383
+ headers: withSessionHeader(sessionId)
384
+ });
385
+ if (!response.ok) {
386
+ if (response.status === 404) return [];
387
+ throw new Error(`Failed to get system events: HTTP ${response.status}`);
388
+ }
389
+ return await response.json();
390
+ }
391
+ // ===========================================================================
392
+ // Control API
393
+ // ===========================================================================
394
+ /**
395
+ * Get allowed namespaces for the current request context.
396
+ */
397
+ async getNamespaces(sessionId, publicKey, apiKey) {
398
+ const url = new URL("/api/control/namespaces", this.backendUrl);
399
+ if (publicKey) {
400
+ url.searchParams.set("public_key", publicKey);
401
+ }
402
+ console.log("[BackendApi.getNamespaces]", {
403
+ backendUrl: this.backendUrl,
404
+ fullUrl: url.toString(),
405
+ sessionId,
406
+ publicKey
407
+ });
408
+ const headers = new Headers(withSessionHeader(sessionId));
409
+ if (apiKey) {
410
+ headers.set(API_KEY_HEADER, apiKey);
411
+ }
412
+ const response = await fetch(url.toString(), { headers });
413
+ if (!response.ok) {
414
+ throw new Error(`Failed to get namespaces: HTTP ${response.status}`);
415
+ }
416
+ return await response.json();
417
+ }
418
+ /**
419
+ * Get available models.
420
+ */
421
+ async getModels(sessionId) {
422
+ const url = new URL("/api/control/models", this.backendUrl);
423
+ console.log("[BackendApi.getModels]", {
424
+ backendUrl: this.backendUrl,
425
+ fullUrl: url.toString(),
426
+ sessionId
427
+ });
428
+ const response = await fetch(url.toString(), {
429
+ headers: withSessionHeader(sessionId)
430
+ });
431
+ if (!response.ok) {
432
+ throw new Error(`Failed to get models: HTTP ${response.status}`);
433
+ }
434
+ return await response.json();
435
+ }
436
+ /**
437
+ * Set the model selection for a session.
438
+ */
439
+ async setModel(sessionId, rig, namespace, apiKey) {
440
+ const payload = { rig };
441
+ if (namespace) {
442
+ payload.namespace = namespace;
443
+ }
444
+ return postState(this.backendUrl, "/api/control/model", payload, sessionId, apiKey);
445
+ }
446
+ };
447
+
448
+ // packages/react/src/runtime/aomi-runtime.tsx
449
+ import { useMemo as useMemo3 } from "react";
450
+
451
+ // packages/react/src/contexts/control-context.tsx
452
+ import {
453
+ createContext,
454
+ useCallback,
455
+ useContext,
456
+ useRef,
457
+ useState,
458
+ useEffect
459
+ } from "react";
460
+
461
+ // packages/react/src/utils/uuid.ts
462
+ function generateUUID() {
463
+ if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
464
+ return crypto.randomUUID();
465
+ }
466
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
467
+ const r = Math.random() * 16 | 0;
468
+ const v = c === "x" ? r : r & 3 | 8;
469
+ return v.toString(16);
470
+ });
471
+ }
472
+
473
+ // packages/react/src/state/thread-store.ts
474
+ var shouldLogThreadUpdates = process.env.NODE_ENV !== "production";
475
+ var logThreadMetadataChange = (source, threadId, prev, next) => {
476
+ if (!shouldLogThreadUpdates) return;
477
+ if (!prev && !next) return;
478
+ if (!prev || !next) {
479
+ console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
480
+ return;
481
+ }
482
+ if (prev.title !== next.title || prev.status !== next.status || prev.lastActiveAt !== next.lastActiveAt) {
483
+ console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
484
+ }
485
+ };
486
+ function initThreadControl() {
487
+ return {
488
+ model: null,
489
+ namespace: null,
490
+ controlDirty: false,
491
+ isProcessing: false
492
+ };
493
+ }
494
+ var ThreadStore = class {
495
+ constructor(options) {
496
+ this.listeners = /* @__PURE__ */ new Set();
497
+ this.subscribe = (listener) => {
498
+ this.listeners.add(listener);
499
+ return () => {
500
+ this.listeners.delete(listener);
501
+ };
502
+ };
503
+ this.getSnapshot = () => this.snapshot;
504
+ this.setCurrentThreadId = (threadId) => {
505
+ this.ensureThreadExists(threadId);
506
+ this.updateState({ currentThreadId: threadId });
507
+ };
508
+ this.bumpThreadViewKey = () => {
509
+ this.updateState({ threadViewKey: this.state.threadViewKey + 1 });
510
+ };
511
+ this.setThreadCnt = (updater) => {
512
+ const nextCnt = this.resolveStateAction(updater, this.state.threadCnt);
513
+ this.updateState({ threadCnt: nextCnt });
514
+ };
515
+ this.setThreads = (updater) => {
516
+ const nextThreads = this.resolveStateAction(updater, this.state.threads);
517
+ this.updateState({ threads: new Map(nextThreads) });
518
+ };
519
+ this.setThreadMetadata = (updater) => {
520
+ const prevMetadata = this.state.threadMetadata;
521
+ const nextMetadata = this.resolveStateAction(updater, prevMetadata);
522
+ for (const [threadId, next] of nextMetadata.entries()) {
523
+ logThreadMetadataChange(
524
+ "setThreadMetadata",
525
+ threadId,
526
+ prevMetadata.get(threadId),
527
+ next
528
+ );
529
+ }
530
+ for (const [threadId, prev] of prevMetadata.entries()) {
531
+ if (!nextMetadata.has(threadId)) {
532
+ logThreadMetadataChange("setThreadMetadata", threadId, prev, void 0);
533
+ }
534
+ }
535
+ this.updateState({ threadMetadata: new Map(nextMetadata) });
536
+ };
537
+ this.setThreadMessages = (threadId, messages) => {
538
+ this.ensureThreadExists(threadId);
539
+ const nextThreads = new Map(this.state.threads);
540
+ nextThreads.set(threadId, messages);
541
+ this.updateState({ threads: nextThreads });
542
+ };
543
+ this.getThreadMessages = (threadId) => {
544
+ var _a;
545
+ return (_a = this.state.threads.get(threadId)) != null ? _a : [];
546
+ };
547
+ this.getThreadMetadata = (threadId) => {
548
+ return this.state.threadMetadata.get(threadId);
549
+ };
550
+ this.updateThreadMetadata = (threadId, updates) => {
551
+ const existing = this.state.threadMetadata.get(threadId);
552
+ if (!existing) {
553
+ return;
554
+ }
555
+ const next = __spreadValues(__spreadValues({}, existing), updates);
556
+ const nextMetadata = new Map(this.state.threadMetadata);
557
+ nextMetadata.set(threadId, next);
558
+ logThreadMetadataChange("updateThreadMetadata", threadId, existing, next);
559
+ this.updateState({ threadMetadata: nextMetadata });
560
+ };
561
+ var _a;
562
+ const initialThreadId = (_a = options == null ? void 0 : options.initialThreadId) != null ? _a : generateUUID();
563
+ this.state = {
564
+ currentThreadId: initialThreadId,
565
+ threadViewKey: 0,
566
+ threadCnt: 1,
567
+ threads: /* @__PURE__ */ new Map([[initialThreadId, []]]),
568
+ threadMetadata: /* @__PURE__ */ new Map([
569
+ [
570
+ initialThreadId,
571
+ {
572
+ title: "New Chat",
573
+ status: "pending",
574
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
575
+ control: initThreadControl()
576
+ }
577
+ ]
578
+ ])
579
+ };
580
+ this.snapshot = this.buildSnapshot();
581
+ }
582
+ emit() {
583
+ for (const listener of this.listeners) {
584
+ listener();
585
+ }
586
+ }
587
+ resolveStateAction(updater, current) {
588
+ return typeof updater === "function" ? updater(current) : updater;
589
+ }
590
+ ensureThreadExists(threadId) {
591
+ if (!this.state.threadMetadata.has(threadId)) {
592
+ const nextMetadata = new Map(this.state.threadMetadata);
593
+ nextMetadata.set(threadId, {
594
+ title: "New Chat",
595
+ status: "regular",
596
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
597
+ control: initThreadControl()
598
+ });
599
+ this.state = __spreadProps(__spreadValues({}, this.state), { threadMetadata: nextMetadata });
600
+ }
601
+ if (!this.state.threads.has(threadId)) {
602
+ const nextThreads = new Map(this.state.threads);
603
+ nextThreads.set(threadId, []);
604
+ this.state = __spreadProps(__spreadValues({}, this.state), { threads: nextThreads });
605
+ }
606
+ }
607
+ updateState(partial) {
608
+ this.state = __spreadValues(__spreadValues({}, this.state), partial);
609
+ this.snapshot = this.buildSnapshot();
610
+ this.emit();
611
+ }
612
+ buildSnapshot() {
613
+ return {
614
+ currentThreadId: this.state.currentThreadId,
615
+ setCurrentThreadId: this.setCurrentThreadId,
616
+ threadViewKey: this.state.threadViewKey,
617
+ bumpThreadViewKey: this.bumpThreadViewKey,
618
+ allThreads: this.state.threads,
619
+ setThreads: this.setThreads,
620
+ allThreadsMetadata: this.state.threadMetadata,
621
+ setThreadMetadata: this.setThreadMetadata,
622
+ threadCnt: this.state.threadCnt,
623
+ setThreadCnt: this.setThreadCnt,
624
+ getThreadMessages: this.getThreadMessages,
625
+ setThreadMessages: this.setThreadMessages,
626
+ getThreadMetadata: this.getThreadMetadata,
627
+ updateThreadMetadata: this.updateThreadMetadata
628
+ };
629
+ }
630
+ };
631
+
632
+ // packages/react/src/contexts/control-context.tsx
633
+ import { jsx } from "react/jsx-runtime";
634
+ var API_KEY_STORAGE_KEY = "aomi_api_key";
635
+ var ControlContext = createContext(null);
636
+ function useControl() {
637
+ const ctx = useContext(ControlContext);
638
+ if (!ctx) {
639
+ throw new Error("useControl must be used within ControlContextProvider");
640
+ }
641
+ return ctx;
642
+ }
643
+ function ControlContextProvider({
644
+ children,
645
+ backendApi,
646
+ sessionId,
647
+ publicKey,
648
+ getThreadMetadata,
649
+ updateThreadMetadata
650
+ }) {
651
+ var _a, _b;
652
+ const [state, setStateInternal] = useState(() => ({
653
+ apiKey: null,
654
+ availableModels: [],
655
+ authorizedNamespaces: [],
656
+ defaultModel: null,
657
+ defaultNamespace: null
658
+ }));
659
+ const stateRef = useRef(state);
660
+ stateRef.current = state;
661
+ const backendApiRef = useRef(backendApi);
662
+ backendApiRef.current = backendApi;
663
+ const sessionIdRef = useRef(sessionId);
664
+ sessionIdRef.current = sessionId;
665
+ const publicKeyRef = useRef(publicKey);
666
+ publicKeyRef.current = publicKey;
667
+ const getThreadMetadataRef = useRef(getThreadMetadata);
668
+ getThreadMetadataRef.current = getThreadMetadata;
669
+ const updateThreadMetadataRef = useRef(updateThreadMetadata);
670
+ updateThreadMetadataRef.current = updateThreadMetadata;
671
+ const callbacks = useRef(/* @__PURE__ */ new Set());
672
+ const currentThreadMetadata = getThreadMetadata(sessionId);
673
+ const isProcessing = (_b = (_a = currentThreadMetadata == null ? void 0 : currentThreadMetadata.control) == null ? void 0 : _a.isProcessing) != null ? _b : false;
674
+ useEffect(() => {
675
+ var _a2, _b2;
676
+ try {
677
+ const storedApiKey = (_b2 = (_a2 = globalThis.localStorage) == null ? void 0 : _a2.getItem(API_KEY_STORAGE_KEY)) != null ? _b2 : null;
678
+ if (storedApiKey) {
679
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), { apiKey: storedApiKey }));
680
+ }
681
+ } catch (e) {
682
+ }
683
+ }, []);
684
+ useEffect(() => {
685
+ var _a2, _b2;
686
+ try {
687
+ if (state.apiKey) {
688
+ (_a2 = globalThis.localStorage) == null ? void 0 : _a2.setItem(API_KEY_STORAGE_KEY, state.apiKey);
689
+ } else {
690
+ (_b2 = globalThis.localStorage) == null ? void 0 : _b2.removeItem(API_KEY_STORAGE_KEY);
691
+ }
692
+ } catch (e) {
693
+ }
694
+ }, [state.apiKey]);
695
+ useEffect(() => {
696
+ const fetchNamespaces = async () => {
697
+ var _a2, _b2;
698
+ try {
699
+ const namespaces = await backendApiRef.current.getNamespaces(
700
+ sessionIdRef.current,
701
+ publicKeyRef.current,
702
+ (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
703
+ );
704
+ const defaultNs = namespaces.includes("default") ? "default" : (_b2 = namespaces[0]) != null ? _b2 : null;
705
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
706
+ authorizedNamespaces: namespaces,
707
+ defaultNamespace: defaultNs
708
+ }));
709
+ } catch (error) {
710
+ console.error("Failed to fetch namespaces:", error);
711
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
712
+ authorizedNamespaces: ["default"],
713
+ defaultNamespace: "default"
714
+ }));
715
+ }
716
+ };
717
+ void fetchNamespaces();
718
+ }, [state.apiKey]);
719
+ useEffect(() => {
720
+ const fetchModels = async () => {
721
+ try {
722
+ const models = await backendApiRef.current.getModels(
723
+ sessionIdRef.current
724
+ );
725
+ setStateInternal((prev) => {
726
+ var _a2;
727
+ return __spreadProps(__spreadValues({}, prev), {
728
+ availableModels: models,
729
+ defaultModel: (_a2 = models[0]) != null ? _a2 : null
730
+ });
731
+ });
732
+ } catch (error) {
733
+ console.error("Failed to fetch models:", error);
734
+ }
735
+ };
736
+ void fetchModels();
737
+ }, []);
738
+ const setApiKey = useCallback((apiKey) => {
739
+ setStateInternal((prev) => {
740
+ const next = __spreadProps(__spreadValues({}, prev), { apiKey: apiKey === "" ? null : apiKey });
741
+ callbacks.current.forEach((cb) => cb(next));
742
+ return next;
743
+ });
744
+ }, []);
745
+ const getAvailableModels = useCallback(async () => {
746
+ try {
747
+ const models = await backendApiRef.current.getModels(
748
+ sessionIdRef.current
749
+ );
750
+ setStateInternal((prev) => {
751
+ var _a2, _b2;
752
+ return __spreadProps(__spreadValues({}, prev), {
753
+ availableModels: models,
754
+ defaultModel: (_b2 = (_a2 = prev.defaultModel) != null ? _a2 : models[0]) != null ? _b2 : null
755
+ });
756
+ });
757
+ return models;
758
+ } catch (error) {
759
+ console.error("Failed to fetch models:", error);
760
+ return [];
761
+ }
762
+ }, []);
763
+ const getAuthorizedNamespaces = useCallback(async () => {
764
+ var _a2, _b2;
765
+ try {
766
+ const namespaces = await backendApiRef.current.getNamespaces(
767
+ sessionIdRef.current,
768
+ publicKeyRef.current,
769
+ (_a2 = stateRef.current.apiKey) != null ? _a2 : void 0
770
+ );
771
+ const defaultNs = namespaces.includes("default") ? "default" : (_b2 = namespaces[0]) != null ? _b2 : null;
772
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
773
+ authorizedNamespaces: namespaces,
774
+ defaultNamespace: defaultNs
775
+ }));
776
+ return namespaces;
777
+ } catch (error) {
778
+ console.error("Failed to fetch namespaces:", error);
779
+ setStateInternal((prev) => __spreadProps(__spreadValues({}, prev), {
780
+ authorizedNamespaces: ["default"],
781
+ defaultNamespace: "default"
782
+ }));
783
+ return ["default"];
784
+ }
785
+ }, []);
786
+ const getCurrentThreadControl = useCallback(() => {
787
+ var _a2;
788
+ const metadata = getThreadMetadataRef.current(sessionIdRef.current);
789
+ return (_a2 = metadata == null ? void 0 : metadata.control) != null ? _a2 : initThreadControl();
790
+ }, []);
791
+ const onModelSelect = useCallback(async (model) => {
792
+ var _a2, _b2, _c, _d, _e;
793
+ const threadId = sessionIdRef.current;
794
+ const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
795
+ const isProcessing2 = currentControl.isProcessing;
796
+ console.log("[control-context] onModelSelect called", {
797
+ model,
798
+ isProcessing: isProcessing2,
799
+ threadId
800
+ });
801
+ if (isProcessing2) {
802
+ console.warn("[control-context] Cannot switch model while processing");
803
+ return;
804
+ }
805
+ const namespace = (_d = (_c = currentControl.namespace) != null ? _c : stateRef.current.defaultNamespace) != null ? _d : "default";
806
+ console.log("[control-context] onModelSelect updating metadata", {
807
+ threadId,
808
+ model,
809
+ namespace,
810
+ currentControl
811
+ });
812
+ updateThreadMetadataRef.current(threadId, {
813
+ control: __spreadProps(__spreadValues({}, currentControl), {
814
+ model,
815
+ namespace,
816
+ controlDirty: true
817
+ })
818
+ });
819
+ console.log("[control-context] onModelSelect calling backend setModel", {
820
+ threadId,
821
+ model,
822
+ namespace,
823
+ backendUrl: backendApiRef.current
824
+ });
825
+ try {
826
+ const result = await backendApiRef.current.setModel(
827
+ threadId,
828
+ model,
829
+ namespace,
830
+ (_e = stateRef.current.apiKey) != null ? _e : void 0
831
+ );
832
+ console.log("[control-context] onModelSelect backend result", result);
833
+ } catch (err) {
834
+ console.error("[control-context] setModel failed:", err);
835
+ throw err;
836
+ }
837
+ }, []);
838
+ const onNamespaceSelect = useCallback((namespace) => {
839
+ var _a2, _b2;
840
+ const threadId = sessionIdRef.current;
841
+ const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
842
+ const isProcessing2 = currentControl.isProcessing;
843
+ console.log("[control-context] onNamespaceSelect called", {
844
+ namespace,
845
+ isProcessing: isProcessing2,
846
+ threadId
847
+ });
848
+ if (isProcessing2) {
849
+ console.warn(
850
+ "[control-context] Cannot switch namespace while processing"
851
+ );
852
+ return;
853
+ }
854
+ console.log("[control-context] onNamespaceSelect updating metadata", {
855
+ threadId,
856
+ namespace,
857
+ currentControl
858
+ });
859
+ updateThreadMetadataRef.current(threadId, {
860
+ control: __spreadProps(__spreadValues({}, currentControl), {
861
+ namespace,
862
+ controlDirty: true
863
+ })
864
+ });
865
+ console.log("[control-context] onNamespaceSelect metadata updated");
866
+ }, []);
867
+ const markControlSynced = useCallback(() => {
868
+ var _a2, _b2;
869
+ const threadId = sessionIdRef.current;
870
+ const currentControl = (_b2 = (_a2 = getThreadMetadataRef.current(threadId)) == null ? void 0 : _a2.control) != null ? _b2 : initThreadControl();
871
+ if (currentControl.controlDirty) {
872
+ updateThreadMetadataRef.current(threadId, {
873
+ control: __spreadProps(__spreadValues({}, currentControl), {
874
+ controlDirty: false
875
+ })
876
+ });
877
+ }
878
+ }, []);
879
+ const getControlState = useCallback(() => stateRef.current, []);
880
+ const onControlStateChange = useCallback(
881
+ (callback) => {
882
+ callbacks.current.add(callback);
883
+ return () => {
884
+ callbacks.current.delete(callback);
885
+ };
886
+ },
887
+ []
888
+ );
889
+ const setState = useCallback(
890
+ (updates) => {
891
+ var _a2;
892
+ if ("apiKey" in updates) {
893
+ setApiKey((_a2 = updates.apiKey) != null ? _a2 : null);
894
+ }
895
+ if ("namespace" in updates && updates.namespace !== void 0 && updates.namespace !== null) {
896
+ onNamespaceSelect(updates.namespace);
897
+ }
898
+ },
899
+ [setApiKey, onNamespaceSelect]
900
+ );
901
+ return /* @__PURE__ */ jsx(
902
+ ControlContext.Provider,
903
+ {
904
+ value: {
905
+ state,
906
+ setApiKey,
907
+ getAvailableModels,
908
+ getAuthorizedNamespaces,
909
+ getCurrentThreadControl,
910
+ onModelSelect,
911
+ onNamespaceSelect,
912
+ isProcessing,
913
+ markControlSynced,
914
+ getControlState,
915
+ onControlStateChange,
916
+ setState
917
+ },
918
+ children
919
+ }
920
+ );
921
+ }
922
+
923
+ // packages/react/src/contexts/event-context.tsx
924
+ import {
925
+ createContext as createContext2,
926
+ useCallback as useCallback2,
927
+ useContext as useContext2,
928
+ useEffect as useEffect2,
929
+ useRef as useRef2,
930
+ useState as useState2
931
+ } from "react";
932
+
933
+ // packages/react/src/backend/types.ts
934
+ function isInlineCall(event) {
935
+ return "InlineCall" in event;
936
+ }
937
+ function isSystemNotice(event) {
938
+ return "SystemNotice" in event;
939
+ }
940
+ function isSystemError(event) {
941
+ return "SystemError" in event;
942
+ }
943
+ function isAsyncCallback(event) {
944
+ return "AsyncCallback" in event;
945
+ }
946
+
947
+ // packages/react/src/state/event-buffer.ts
948
+ function createEventBuffer() {
949
+ return {
950
+ inboundQueue: [],
951
+ outboundQueue: [],
952
+ sseStatus: "disconnected",
953
+ lastEventId: null,
954
+ subscribers: /* @__PURE__ */ new Map()
955
+ };
956
+ }
957
+ function enqueueInbound(state, event) {
958
+ state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
959
+ status: "pending",
960
+ timestamp: Date.now()
961
+ }));
962
+ }
963
+ function subscribe(state, type, callback) {
964
+ if (!state.subscribers.has(type)) {
965
+ state.subscribers.set(type, /* @__PURE__ */ new Set());
966
+ }
967
+ state.subscribers.get(type).add(callback);
968
+ return () => {
969
+ var _a;
970
+ (_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
971
+ };
972
+ }
973
+ function dispatch(state, event) {
974
+ const typeSubscribers = state.subscribers.get(event.type);
975
+ if (typeSubscribers) {
976
+ for (const callback of typeSubscribers) {
977
+ callback(event);
978
+ }
979
+ }
980
+ const allSubscribers = state.subscribers.get("*");
981
+ if (allSubscribers) {
982
+ for (const callback of allSubscribers) {
983
+ callback(event);
984
+ }
985
+ }
986
+ }
987
+ function setSSEStatus(state, status) {
988
+ state.sseStatus = status;
989
+ }
990
+
991
+ // packages/react/src/contexts/event-context.tsx
992
+ import { jsx as jsx2 } from "react/jsx-runtime";
993
+ var EventContextState = createContext2(null);
994
+ function useEventContext() {
995
+ const context = useContext2(EventContextState);
996
+ if (!context) {
997
+ throw new Error(
998
+ "useEventContext must be used within EventContextProvider. Wrap your app with <EventContextProvider>...</EventContextProvider>"
999
+ );
1000
+ }
1001
+ return context;
1002
+ }
1003
+ function EventContextProvider({
1004
+ children,
1005
+ backendApi,
1006
+ sessionId
1007
+ }) {
1008
+ const bufferRef = useRef2(null);
1009
+ if (!bufferRef.current) {
1010
+ bufferRef.current = createEventBuffer();
1011
+ }
1012
+ const buffer = bufferRef.current;
1013
+ const [sseStatus, setSseStatus] = useState2("disconnected");
1014
+ useEffect2(() => {
1015
+ setSSEStatus(buffer, "connecting");
1016
+ setSseStatus("connecting");
1017
+ const unsubscribe = backendApi.subscribeSSE(
1018
+ sessionId,
1019
+ (event) => {
1020
+ enqueueInbound(buffer, {
1021
+ type: event.type,
1022
+ sessionId: event.session_id,
1023
+ payload: event
1024
+ });
1025
+ const inboundEvent = {
1026
+ type: event.type,
1027
+ sessionId: event.session_id,
1028
+ payload: event,
1029
+ status: "fetched",
1030
+ timestamp: Date.now()
1031
+ };
1032
+ dispatch(buffer, inboundEvent);
1033
+ },
1034
+ (error) => {
1035
+ console.error("SSE error:", error);
1036
+ setSSEStatus(buffer, "disconnected");
1037
+ setSseStatus("disconnected");
1038
+ }
1039
+ );
1040
+ setSSEStatus(buffer, "connected");
1041
+ setSseStatus("connected");
1042
+ return () => {
1043
+ unsubscribe();
1044
+ setSSEStatus(buffer, "disconnected");
1045
+ setSseStatus("disconnected");
1046
+ };
1047
+ }, [backendApi, sessionId, buffer]);
1048
+ const subscribeCallback = useCallback2(
1049
+ (type, callback) => {
1050
+ return subscribe(buffer, type, callback);
1051
+ },
1052
+ [buffer]
1053
+ );
1054
+ const sendOutbound = useCallback2(
1055
+ async (event) => {
1056
+ try {
1057
+ const message = JSON.stringify({
1058
+ type: event.type,
1059
+ payload: event.payload
1060
+ });
1061
+ await backendApi.postSystemMessage(event.sessionId, message);
1062
+ } catch (error) {
1063
+ console.error("Failed to send outbound event:", error);
1064
+ }
1065
+ },
1066
+ [backendApi]
1067
+ );
1068
+ const dispatchSystemEvents = useCallback2(
1069
+ (sessionId2, events) => {
1070
+ var _a;
1071
+ for (const event of events) {
1072
+ let eventType;
1073
+ let payload;
1074
+ if (isInlineCall(event)) {
1075
+ eventType = event.InlineCall.type;
1076
+ payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
1077
+ } else if (isSystemNotice(event)) {
1078
+ eventType = "system_notice";
1079
+ payload = { message: event.SystemNotice };
1080
+ } else if (isSystemError(event)) {
1081
+ eventType = "system_error";
1082
+ payload = { message: event.SystemError };
1083
+ } else if (isAsyncCallback(event)) {
1084
+ eventType = "async_callback";
1085
+ payload = event.AsyncCallback;
1086
+ } else {
1087
+ console.warn("Unknown system event type:", event);
1088
+ continue;
1089
+ }
1090
+ const inboundEvent = {
1091
+ type: eventType,
1092
+ sessionId: sessionId2,
1093
+ payload,
1094
+ status: "fetched",
1095
+ timestamp: Date.now()
1096
+ };
1097
+ enqueueInbound(buffer, {
1098
+ type: eventType,
1099
+ sessionId: sessionId2,
1100
+ payload
1101
+ });
1102
+ dispatch(buffer, inboundEvent);
1103
+ }
1104
+ },
1105
+ [buffer]
1106
+ );
1107
+ const contextValue = {
1108
+ subscribe: subscribeCallback,
1109
+ sendOutboundSystem: sendOutbound,
1110
+ dispatchInboundSystem: dispatchSystemEvents,
1111
+ sseStatus
1112
+ };
1113
+ return /* @__PURE__ */ jsx2(EventContextState.Provider, { value: contextValue, children });
1114
+ }
1115
+
1116
+ // packages/react/src/contexts/notification-context.tsx
1117
+ import {
1118
+ createContext as createContext3,
1119
+ useCallback as useCallback3,
1120
+ useContext as useContext3,
1121
+ useState as useState3
1122
+ } from "react";
1123
+ import { jsx as jsx3 } from "react/jsx-runtime";
1124
+ var NotificationContext = createContext3(null);
1125
+ function useNotification() {
1126
+ const context = useContext3(NotificationContext);
1127
+ if (!context) {
1128
+ throw new Error(
1129
+ "useNotification must be used within NotificationContextProvider"
1130
+ );
1131
+ }
1132
+ return context;
1133
+ }
1134
+ var notificationIdCounter = 0;
1135
+ function generateId() {
1136
+ return `notif-${Date.now()}-${++notificationIdCounter}`;
1137
+ }
1138
+ function NotificationContextProvider({
1139
+ children
1140
+ }) {
1141
+ const [notifications, setNotifications] = useState3([]);
1142
+ const showNotification = useCallback3((params) => {
1143
+ const id = generateId();
1144
+ const notification = __spreadProps(__spreadValues({}, params), {
1145
+ id,
1146
+ timestamp: Date.now()
1147
+ });
1148
+ setNotifications((prev) => [notification, ...prev]);
1149
+ return id;
1150
+ }, []);
1151
+ const dismissNotification = useCallback3((id) => {
1152
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
1153
+ }, []);
1154
+ const clearAll = useCallback3(() => {
1155
+ setNotifications([]);
1156
+ }, []);
1157
+ const value = {
1158
+ notifications,
1159
+ showNotification,
1160
+ dismissNotification,
1161
+ clearAll
1162
+ };
1163
+ return /* @__PURE__ */ jsx3(NotificationContext.Provider, { value, children });
1164
+ }
1165
+
1166
+ // packages/react/src/contexts/thread-context.tsx
1167
+ import {
1168
+ createContext as createContext4,
1169
+ useContext as useContext4,
1170
+ useMemo,
1171
+ useRef as useRef3,
1172
+ useSyncExternalStore
1173
+ } from "react";
1174
+ import { jsx as jsx4 } from "react/jsx-runtime";
1175
+ var ThreadContextState = createContext4(null);
1176
+ function useThreadContext() {
1177
+ const context = useContext4(ThreadContextState);
1178
+ if (!context) {
1179
+ throw new Error(
1180
+ "useThreadContext must be used within ThreadContextProvider. Wrap your app with <ThreadContextProvider>...</ThreadContextProvider>"
1181
+ );
1182
+ }
1183
+ return context;
1184
+ }
1185
+ function ThreadContextProvider({
1186
+ children,
1187
+ initialThreadId
1188
+ }) {
1189
+ const storeRef = useRef3(null);
1190
+ if (!storeRef.current) {
1191
+ storeRef.current = new ThreadStore({ initialThreadId });
1192
+ }
1193
+ const store = storeRef.current;
1194
+ const value = useSyncExternalStore(
1195
+ store.subscribe,
1196
+ store.getSnapshot,
1197
+ store.getSnapshot
1198
+ );
1199
+ return /* @__PURE__ */ jsx4(ThreadContextState.Provider, { value, children });
1200
+ }
1201
+ function useCurrentThreadMessages() {
1202
+ const { currentThreadId, getThreadMessages } = useThreadContext();
1203
+ return useMemo(
1204
+ () => getThreadMessages(currentThreadId),
1205
+ [currentThreadId, getThreadMessages]
1206
+ );
1207
+ }
1208
+ function useCurrentThreadMetadata() {
1209
+ const { currentThreadId, getThreadMetadata } = useThreadContext();
1210
+ return useMemo(
1211
+ () => getThreadMetadata(currentThreadId),
1212
+ [currentThreadId, getThreadMetadata]
1213
+ );
1214
+ }
1215
+
1216
+ // packages/react/src/contexts/user-context.tsx
1217
+ import {
1218
+ createContext as createContext5,
1219
+ useCallback as useCallback4,
1220
+ useContext as useContext5,
1221
+ useRef as useRef4,
1222
+ useState as useState4
1223
+ } from "react";
1224
+ import { jsx as jsx5 } from "react/jsx-runtime";
1225
+ var UserContext = createContext5(void 0);
1226
+ function useUser() {
1227
+ const context = useContext5(UserContext);
1228
+ if (!context) {
1229
+ throw new Error("useUser must be used within UserContextProvider");
1230
+ }
1231
+ return {
1232
+ user: context.user,
1233
+ setUser: context.setUser,
1234
+ getUserState: context.getUserState,
1235
+ onUserStateChange: context.onUserStateChange
1236
+ };
1237
+ }
1238
+ function UserContextProvider({ children }) {
1239
+ const [user, setUserState] = useState4({
1240
+ isConnected: false,
1241
+ address: void 0,
1242
+ chainId: void 0,
1243
+ ensName: void 0
1244
+ });
1245
+ const userRef = useRef4(user);
1246
+ userRef.current = user;
1247
+ const StateChangeCallbacks = useRef4(
1248
+ /* @__PURE__ */ new Set()
1249
+ );
1250
+ const setUser = useCallback4((data) => {
1251
+ setUserState((prev) => {
1252
+ const next = __spreadValues(__spreadValues({}, prev), data);
1253
+ StateChangeCallbacks.current.forEach((callback) => {
1254
+ callback(next);
1255
+ });
1256
+ return next;
1257
+ });
1258
+ }, []);
1259
+ const getUserState = useCallback4(() => userRef.current, []);
1260
+ const onUserStateChange = useCallback4(
1261
+ (callback) => {
1262
+ StateChangeCallbacks.current.add(callback);
1263
+ return () => {
1264
+ StateChangeCallbacks.current.delete(callback);
1265
+ };
1266
+ },
1267
+ []
1268
+ );
1269
+ return /* @__PURE__ */ jsx5(
1270
+ UserContext.Provider,
1271
+ {
1272
+ value: {
1273
+ user,
1274
+ setUser,
1275
+ getUserState,
1276
+ onUserStateChange
1277
+ },
1278
+ children
1279
+ }
1280
+ );
1281
+ }
1282
+
1283
+ // packages/react/src/runtime/core.tsx
1284
+ import { useCallback as useCallback6, useEffect as useEffect3, useMemo as useMemo2, useRef as useRef6 } from "react";
1285
+ import {
1286
+ AssistantRuntimeProvider,
1287
+ useExternalStoreRuntime
1288
+ } from "@assistant-ui/react";
1289
+
1290
+ // packages/react/src/runtime/orchestrator.ts
1291
+ import { useCallback as useCallback5, useRef as useRef5, useState as useState5 } from "react";
1292
+
1293
+ // packages/react/src/runtime/utils.ts
1294
+ import { clsx } from "clsx";
1295
+ import { twMerge } from "tailwind-merge";
1296
+ function cn(...inputs) {
1297
+ return twMerge(clsx(inputs));
1298
+ }
1299
+ var parseTimestamp = (value) => {
1300
+ if (value === void 0 || value === null) return 0;
1301
+ if (typeof value === "number") {
1302
+ return Number.isFinite(value) ? value < 1e12 ? value * 1e3 : value : 0;
1303
+ }
1304
+ const numeric = Number(value);
1305
+ if (!Number.isNaN(numeric)) {
1306
+ return numeric < 1e12 ? numeric * 1e3 : numeric;
1307
+ }
1308
+ const ts = Date.parse(value);
1309
+ return Number.isNaN(ts) ? 0 : ts;
1310
+ };
1311
+ var isPlaceholderTitle = (title) => {
1312
+ var _a;
1313
+ const normalized = (_a = title == null ? void 0 : title.trim()) != null ? _a : "";
1314
+ return !normalized || normalized.startsWith("#[");
1315
+ };
1316
+ function toInboundMessage(msg) {
1317
+ var _a;
1318
+ if (msg.sender === "system") return null;
1319
+ const content = [];
1320
+ const role = msg.sender === "user" ? "user" : "assistant";
1321
+ if (msg.content) {
1322
+ content.push({ type: "text", text: msg.content });
1323
+ }
1324
+ const [topic, toolContent] = (_a = parseToolPayload(msg)) != null ? _a : [];
1325
+ if (topic && toolContent) {
1326
+ content.push({
1327
+ type: "tool-call",
1328
+ toolCallId: `tool_${Date.now()}`,
1329
+ toolName: topic,
1330
+ args: void 0,
1331
+ result: (() => {
1332
+ try {
1333
+ return JSON.parse(toolContent);
1334
+ } catch (e) {
1335
+ return { args: toolContent };
1336
+ }
1337
+ })()
1338
+ });
1339
+ }
1340
+ const threadMessage = __spreadValues({
1341
+ role,
1342
+ content: content.length > 0 ? content : [{ type: "text", text: "" }]
1343
+ }, msg.timestamp && { createdAt: new Date(msg.timestamp) });
1344
+ return threadMessage;
1345
+ }
1346
+ function parseToolPayload(msg) {
1347
+ return parseToolResult(msg.tool_result);
1348
+ }
1349
+ function parseToolResult(toolResult) {
1350
+ if (!toolResult) return null;
1351
+ if (Array.isArray(toolResult) && toolResult.length === 2) {
1352
+ const [topic, content] = toolResult;
1353
+ return [String(topic), String(content != null ? content : "")];
1354
+ }
1355
+ return null;
1356
+ }
1357
+ var getNetworkName = (chainId) => {
1358
+ if (chainId === void 0) return "";
1359
+ const id = typeof chainId === "string" ? Number(chainId) : chainId;
1360
+ switch (id) {
1361
+ case 1:
1362
+ return "ethereum";
1363
+ case 137:
1364
+ return "polygon";
1365
+ case 42161:
1366
+ return "arbitrum";
1367
+ case 8453:
1368
+ return "base";
1369
+ case 10:
1370
+ return "optimism";
1371
+ case 11155111:
1372
+ return "sepolia";
1373
+ case 1337:
1374
+ case 31337:
1375
+ return "testnet";
1376
+ case 59140:
1377
+ return "linea-sepolia";
1378
+ case 59144:
1379
+ return "linea";
1380
+ default:
1381
+ return "testnet";
1382
+ }
1383
+ };
1384
+ var formatAddress = (addr) => addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : "Connect Wallet";
1385
+
1386
+ // packages/react/src/state/backend-state.ts
1387
+ function createBackendState() {
1388
+ return {
1389
+ skipInitialFetch: /* @__PURE__ */ new Set(),
1390
+ pendingChat: /* @__PURE__ */ new Map(),
1391
+ runningThreads: /* @__PURE__ */ new Set(),
1392
+ creatingThreadId: null,
1393
+ createThreadPromise: null
1394
+ };
1395
+ }
1396
+ function resolveThreadId(state, threadId) {
1397
+ return threadId;
1398
+ }
1399
+ function isThreadReady(state, threadId) {
1400
+ return state.creatingThreadId !== threadId;
1401
+ }
1402
+ function markSkipInitialFetch(state, threadId) {
1403
+ state.skipInitialFetch.add(threadId);
1404
+ }
1405
+ function shouldSkipInitialFetch(state, threadId) {
1406
+ return state.skipInitialFetch.has(threadId);
1407
+ }
1408
+ function clearSkipInitialFetch(state, threadId) {
1409
+ state.skipInitialFetch.delete(threadId);
1410
+ }
1411
+ function setThreadRunning(state, threadId, running) {
1412
+ if (running) {
1413
+ state.runningThreads.add(threadId);
1414
+ } else {
1415
+ state.runningThreads.delete(threadId);
1416
+ }
1417
+ }
1418
+ function isThreadRunning(state, threadId) {
1419
+ return state.runningThreads.has(threadId);
1420
+ }
1421
+ function enqueuePendingChat(state, threadId, text) {
1422
+ var _a;
1423
+ const existing = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
1424
+ state.pendingChat.set(threadId, [...existing, text]);
1425
+ }
1426
+ function dequeuePendingChat(state, threadId) {
1427
+ var _a;
1428
+ const pending = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
1429
+ state.pendingChat.delete(threadId);
1430
+ return pending;
1431
+ }
1432
+ function hasPendingChat(state, threadId) {
1433
+ var _a, _b;
1434
+ return ((_b = (_a = state.pendingChat.get(threadId)) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
1435
+ }
1436
+
1437
+ // packages/react/src/runtime/message-controller.ts
1438
+ var MessageController = class {
1439
+ constructor(config) {
1440
+ this.config = config;
1441
+ }
1442
+ inbound(threadId, msgs) {
1443
+ const backendState = this.config.backendStateRef.current;
1444
+ if (!msgs) return;
1445
+ if (hasPendingChat(backendState, threadId)) {
1446
+ return;
1447
+ }
1448
+ const threadMessages = [];
1449
+ for (const msg of msgs) {
1450
+ const threadMessage = toInboundMessage(msg);
1451
+ if (threadMessage) {
1452
+ threadMessages.push(threadMessage);
1453
+ }
1454
+ }
1455
+ this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
1456
+ }
1457
+ async outbound(message, threadId) {
1458
+ var _a, _b, _c, _d, _e, _f;
1459
+ const backendState = this.config.backendStateRef.current;
1460
+ const text = message.content.filter(
1461
+ (part) => part.type === "text"
1462
+ ).map(
1463
+ (part) => part.text
1464
+ ).join("\n");
1465
+ if (!text) return;
1466
+ const threadState = this.getThreadContextApi();
1467
+ const existingMessages = threadState.getThreadMessages(threadId);
1468
+ const userMessage = {
1469
+ role: "user",
1470
+ content: [{ type: "text", text }],
1471
+ createdAt: /* @__PURE__ */ new Date()
1472
+ };
1473
+ threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
1474
+ threadState.updateThreadMetadata(threadId, {
1475
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1476
+ });
1477
+ if (!isThreadReady(backendState, threadId)) {
1478
+ this.markRunning(threadId, true);
1479
+ enqueuePendingChat(backendState, threadId, text);
1480
+ return;
1481
+ }
1482
+ const backendThreadId = resolveThreadId(backendState, threadId);
1483
+ const namespace = this.config.getNamespace();
1484
+ const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1485
+ const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
1486
+ try {
1487
+ this.markRunning(threadId, true);
1488
+ const response = await this.config.backendApiRef.current.postChatMessage(
1489
+ backendThreadId,
1490
+ text,
1491
+ namespace,
1492
+ publicKey,
1493
+ apiKey
1494
+ );
1495
+ if (response == null ? void 0 : response.messages) {
1496
+ this.inbound(threadId, response.messages);
1497
+ }
1498
+ if (((_f = response == null ? void 0 : response.system_events) == null ? void 0 : _f.length) && this.config.onSyncEvents) {
1499
+ this.config.onSyncEvents(backendThreadId, response.system_events);
1500
+ }
1501
+ if (response == null ? void 0 : response.is_processing) {
1502
+ this.config.polling.start(threadId);
1503
+ } else if (!this.config.polling.isPolling(threadId)) {
1504
+ this.markRunning(threadId, false);
1505
+ }
1506
+ } catch (error) {
1507
+ console.error("Failed to send message:", error);
1508
+ this.markRunning(threadId, false);
1509
+ }
1510
+ }
1511
+ async flushPendingChat(threadId) {
1512
+ var _a, _b, _c, _d, _e;
1513
+ const backendState = this.config.backendStateRef.current;
1514
+ const pending = dequeuePendingChat(backendState, threadId);
1515
+ if (!pending.length) return;
1516
+ const backendThreadId = resolveThreadId(backendState, threadId);
1517
+ const namespace = this.config.getNamespace();
1518
+ const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1519
+ const apiKey = (_e = (_d = (_c = this.config).getApiKey) == null ? void 0 : _d.call(_c)) != null ? _e : void 0;
1520
+ for (const text of pending) {
1521
+ try {
1522
+ await this.config.backendApiRef.current.postChatMessage(
1523
+ backendThreadId,
1524
+ text,
1525
+ namespace,
1526
+ publicKey,
1527
+ apiKey
1528
+ );
1529
+ } catch (error) {
1530
+ console.error("Failed to send queued message:", error);
1531
+ }
1532
+ }
1533
+ this.config.polling.start(threadId);
1534
+ }
1535
+ async cancel(threadId) {
1536
+ var _a;
1537
+ const backendState = this.config.backendStateRef.current;
1538
+ if (!isThreadReady(backendState, threadId)) return;
1539
+ this.config.polling.stop(threadId);
1540
+ const backendThreadId = resolveThreadId(backendState, threadId);
1541
+ try {
1542
+ const response = await this.config.backendApiRef.current.postInterrupt(backendThreadId);
1543
+ if (response == null ? void 0 : response.messages) {
1544
+ this.inbound(threadId, response.messages);
1545
+ }
1546
+ if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1547
+ this.config.onSyncEvents(backendThreadId, response.system_events);
1548
+ }
1549
+ this.markRunning(threadId, false);
1550
+ } catch (error) {
1551
+ console.error("Failed to cancel:", error);
1552
+ }
1553
+ }
1554
+ markRunning(threadId, running) {
1555
+ var _a, _b;
1556
+ setThreadRunning(this.config.backendStateRef.current, threadId, running);
1557
+ if (this.config.threadContextRef.current.currentThreadId === threadId) {
1558
+ (_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
1559
+ }
1560
+ }
1561
+ getThreadContextApi() {
1562
+ const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
1563
+ return { getThreadMessages, setThreadMessages, updateThreadMetadata };
1564
+ }
1565
+ };
1566
+
1567
+ // packages/react/src/runtime/polling-controller.ts
1568
+ var PollingController = class {
1569
+ constructor(config) {
1570
+ this.config = config;
1571
+ this.intervals = /* @__PURE__ */ new Map();
1572
+ var _a;
1573
+ this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
1574
+ }
1575
+ start(threadId) {
1576
+ var _a, _b;
1577
+ const backendState = this.config.backendStateRef.current;
1578
+ if (!isThreadReady(backendState, threadId)) return;
1579
+ if (this.intervals.has(threadId)) return;
1580
+ const backendThreadId = resolveThreadId(backendState, threadId);
1581
+ setThreadRunning(backendState, threadId, true);
1582
+ const tick = async () => {
1583
+ var _a2, _b2;
1584
+ if (!this.intervals.has(threadId)) return;
1585
+ try {
1586
+ console.log(
1587
+ "[PollingController] Fetching state for threadId:",
1588
+ threadId
1589
+ );
1590
+ const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
1591
+ const state = await this.config.backendApiRef.current.fetchState(
1592
+ backendThreadId,
1593
+ userState
1594
+ );
1595
+ if (!this.intervals.has(threadId)) return;
1596
+ this.handleState(threadId, state);
1597
+ } catch (error) {
1598
+ console.error("Polling error:", error);
1599
+ this.stop(threadId);
1600
+ }
1601
+ };
1602
+ const intervalId = setInterval(tick, this.intervalMs);
1603
+ this.intervals.set(threadId, intervalId);
1604
+ (_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
1605
+ }
1606
+ stop(threadId) {
1607
+ var _a, _b;
1608
+ const intervalId = this.intervals.get(threadId);
1609
+ if (intervalId) {
1610
+ clearInterval(intervalId);
1611
+ this.intervals.delete(threadId);
1612
+ }
1613
+ setThreadRunning(this.config.backendStateRef.current, threadId, false);
1614
+ (_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
1615
+ }
1616
+ isPolling(threadId) {
1617
+ return this.intervals.has(threadId);
1618
+ }
1619
+ stopAll() {
1620
+ for (const threadId of this.intervals.keys()) {
1621
+ this.stop(threadId);
1622
+ }
1623
+ }
1624
+ handleState(threadId, state) {
1625
+ var _a;
1626
+ if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1627
+ const backendState = this.config.backendStateRef.current;
1628
+ const sessionId = resolveThreadId(backendState, threadId);
1629
+ this.config.onSyncEvents(sessionId, state.system_events);
1630
+ }
1631
+ this.config.applyMessages(threadId, state.messages);
1632
+ if (!state.is_processing) {
1633
+ this.stop(threadId);
1634
+ }
1635
+ }
1636
+ };
1637
+
1638
+ // packages/react/src/runtime/orchestrator.ts
1639
+ function useRuntimeOrchestrator(backendApi, options) {
1640
+ const threadContext = useThreadContext();
1641
+ const threadContextRef = useRef5(threadContext);
1642
+ threadContextRef.current = threadContext;
1643
+ const backendApiRef = useRef5(backendApi);
1644
+ backendApiRef.current = backendApi;
1645
+ const backendStateRef = useRef5(createBackendState());
1646
+ const [isRunning, setIsRunning] = useState5(false);
1647
+ const messageControllerRef = useRef5(null);
1648
+ const pollingRef = useRef5(null);
1649
+ const pendingFetches = useRef5(/* @__PURE__ */ new Set());
1650
+ if (!pollingRef.current) {
1651
+ pollingRef.current = new PollingController({
1652
+ backendApiRef,
1653
+ backendStateRef,
1654
+ applyMessages: (threadId, msgs) => {
1655
+ var _a;
1656
+ (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
1657
+ },
1658
+ onSyncEvents: options.onSyncEvents,
1659
+ getUserState: options.getUserState,
1660
+ onStart: (threadId) => {
1661
+ if (threadContextRef.current.currentThreadId === threadId) {
1662
+ setIsRunning(true);
1663
+ }
1664
+ },
1665
+ onStop: (threadId) => {
1666
+ if (threadContextRef.current.currentThreadId === threadId) {
1667
+ setIsRunning(false);
1668
+ }
1669
+ }
1670
+ });
1671
+ }
1672
+ if (!messageControllerRef.current) {
1673
+ messageControllerRef.current = new MessageController({
1674
+ backendApiRef,
1675
+ backendStateRef,
1676
+ threadContextRef,
1677
+ polling: pollingRef.current,
1678
+ setGlobalIsRunning: setIsRunning,
1679
+ getPublicKey: options.getPublicKey,
1680
+ getNamespace: options.getNamespace,
1681
+ getApiKey: options.getApiKey,
1682
+ onSyncEvents: options.onSyncEvents
1683
+ });
1684
+ }
1685
+ const ensureInitialState = useCallback5(async (threadId) => {
1686
+ var _a, _b, _c, _d;
1687
+ const backendState = backendStateRef.current;
1688
+ if (shouldSkipInitialFetch(backendState, threadId)) {
1689
+ clearSkipInitialFetch(backendState, threadId);
1690
+ if (threadContextRef.current.currentThreadId === threadId) {
1691
+ setIsRunning(false);
1692
+ }
1693
+ return;
1694
+ }
1695
+ if (!isThreadReady(backendState, threadId)) {
1696
+ if (threadContextRef.current.currentThreadId === threadId) {
1697
+ setIsRunning(false);
1698
+ }
1699
+ return;
1700
+ }
1701
+ if (pendingFetches.current.has(threadId)) return;
1702
+ const backendThreadId = resolveThreadId(backendState, threadId);
1703
+ pendingFetches.current.add(threadId);
1704
+ try {
1705
+ const userState = (_a = options.getUserState) == null ? void 0 : _a.call(options);
1706
+ const state = await backendApiRef.current.fetchState(
1707
+ backendThreadId,
1708
+ userState
1709
+ );
1710
+ (_b = messageControllerRef.current) == null ? void 0 : _b.inbound(threadId, state.messages);
1711
+ if (((_c = state.system_events) == null ? void 0 : _c.length) && options.onSyncEvents) {
1712
+ options.onSyncEvents(backendThreadId, state.system_events);
1713
+ }
1714
+ if (threadContextRef.current.currentThreadId === threadId) {
1715
+ if (state.is_processing) {
1716
+ setIsRunning(true);
1717
+ (_d = pollingRef.current) == null ? void 0 : _d.start(threadId);
1718
+ } else {
1719
+ setIsRunning(false);
1720
+ }
1721
+ }
1722
+ } catch (error) {
1723
+ console.error("Failed to fetch initial state:", error);
1724
+ if (threadContextRef.current.currentThreadId === threadId) {
1725
+ setIsRunning(false);
1726
+ }
1727
+ } finally {
1728
+ pendingFetches.current.delete(threadId);
1729
+ }
1730
+ }, []);
1731
+ return {
1732
+ backendStateRef,
1733
+ polling: pollingRef.current,
1734
+ messageController: messageControllerRef.current,
1735
+ isRunning,
1736
+ setIsRunning,
1737
+ ensureInitialState,
1738
+ backendApiRef
1739
+ };
1740
+ }
1741
+
1742
+ // packages/react/src/runtime/threadlist-adapter.ts
1743
+ var sortByLastActiveDesc = ([, metaA], [, metaB]) => {
1744
+ const tsA = parseTimestamp(metaA.lastActiveAt);
1745
+ const tsB = parseTimestamp(metaB.lastActiveAt);
1746
+ return tsB - tsA;
1747
+ };
1748
+ function buildThreadLists(threadMetadata) {
1749
+ const entries = Array.from(threadMetadata.entries()).filter(
1750
+ ([, meta]) => !isPlaceholderTitle(meta.title)
1751
+ );
1752
+ const regularThreads = entries.filter(([, meta]) => meta.status !== "archived").sort(sortByLastActiveDesc).map(
1753
+ ([id, meta]) => ({
1754
+ id,
1755
+ title: meta.title || "New Chat",
1756
+ status: "regular"
1757
+ })
1758
+ );
1759
+ const archivedThreads = entries.filter(([, meta]) => meta.status === "archived").sort(sortByLastActiveDesc).map(
1760
+ ([id, meta]) => ({
1761
+ id,
1762
+ title: meta.title || "New Chat",
1763
+ status: "archived"
1764
+ })
1765
+ );
1766
+ return { regularThreads, archivedThreads };
1767
+ }
1768
+ function buildThreadListAdapter({
1769
+ backendStateRef,
1770
+ backendApiRef,
1771
+ threadContext,
1772
+ currentThreadIdRef,
1773
+ polling,
1774
+ userAddress,
1775
+ setIsRunning,
1776
+ getNamespace,
1777
+ getApiKey
1778
+ }) {
1779
+ const backendState = backendStateRef.current;
1780
+ const { regularThreads, archivedThreads } = buildThreadLists(
1781
+ threadContext.allThreadsMetadata
1782
+ );
1783
+ const preparePendingThread = (threadId) => {
1784
+ const previousPendingId = backendState.creatingThreadId;
1785
+ if (previousPendingId && previousPendingId !== threadId) {
1786
+ threadContext.setThreadMetadata((prev) => {
1787
+ const next = new Map(prev);
1788
+ next.delete(previousPendingId);
1789
+ return next;
1790
+ });
1791
+ threadContext.setThreads((prev) => {
1792
+ const next = new Map(prev);
1793
+ next.delete(previousPendingId);
1794
+ return next;
1795
+ });
1796
+ backendState.pendingChat.delete(previousPendingId);
1797
+ backendState.skipInitialFetch.delete(previousPendingId);
1798
+ }
1799
+ backendState.creatingThreadId = threadId;
1800
+ backendState.pendingChat.delete(threadId);
1801
+ threadContext.setThreadMetadata(
1802
+ (prev) => new Map(prev).set(threadId, {
1803
+ title: "New Chat",
1804
+ status: "pending",
1805
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
1806
+ control: initThreadControl()
1807
+ })
1808
+ );
1809
+ threadContext.setThreadMessages(threadId, []);
1810
+ threadContext.setCurrentThreadId(threadId);
1811
+ setIsRunning(false);
1812
+ threadContext.bumpThreadViewKey();
1813
+ };
1814
+ const findPendingThreadId = () => {
1815
+ if (backendState.creatingThreadId) return backendState.creatingThreadId;
1816
+ for (const [id, meta] of threadContext.allThreadsMetadata.entries()) {
1817
+ if (meta.status === "pending") return id;
1818
+ }
1819
+ return null;
1820
+ };
1821
+ return {
1822
+ threadId: threadContext.currentThreadId,
1823
+ threads: regularThreads,
1824
+ archivedThreads,
1825
+ onSwitchToNewThread: async () => {
1826
+ var _a;
1827
+ const pendingId = findPendingThreadId();
1828
+ if (pendingId) {
1829
+ preparePendingThread(pendingId);
1830
+ return;
1831
+ }
1832
+ if (backendState.createThreadPromise) {
1833
+ preparePendingThread((_a = backendState.creatingThreadId) != null ? _a : generateUUID());
1834
+ return;
1835
+ }
1836
+ const threadId = generateUUID();
1837
+ preparePendingThread(threadId);
1838
+ const createPromise = backendApiRef.current.createThread(threadId, userAddress).then(async (newThread) => {
1839
+ var _a2, _b;
1840
+ const uiThreadId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
1841
+ const backendId = newThread.session_id;
1842
+ if (uiThreadId !== backendId) {
1843
+ console.warn("[aomi][thread] backend id mismatch", {
1844
+ uiThreadId,
1845
+ backendId
1846
+ });
1847
+ }
1848
+ markSkipInitialFetch(backendState, uiThreadId);
1849
+ threadContext.setThreadMetadata((prev) => {
1850
+ var _a3, _b2, _c;
1851
+ const next = new Map(prev);
1852
+ const existing = next.get(uiThreadId);
1853
+ const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1854
+ next.set(uiThreadId, {
1855
+ title: (_a3 = existing == null ? void 0 : existing.title) != null ? _a3 : "New Chat",
1856
+ status: nextStatus,
1857
+ lastActiveAt: (_b2 = existing == null ? void 0 : existing.lastActiveAt) != null ? _b2 : (/* @__PURE__ */ new Date()).toISOString(),
1858
+ control: (_c = existing == null ? void 0 : existing.control) != null ? _c : initThreadControl()
1859
+ });
1860
+ return next;
1861
+ });
1862
+ if (backendState.creatingThreadId === uiThreadId) {
1863
+ backendState.creatingThreadId = null;
1864
+ }
1865
+ const pendingMessages = backendState.pendingChat.get(uiThreadId);
1866
+ if (pendingMessages == null ? void 0 : pendingMessages.length) {
1867
+ backendState.pendingChat.delete(uiThreadId);
1868
+ const namespace = getNamespace();
1869
+ const apiKey = (_b = getApiKey == null ? void 0 : getApiKey()) != null ? _b : void 0;
1870
+ for (const text of pendingMessages) {
1871
+ try {
1872
+ await backendApiRef.current.postChatMessage(
1873
+ backendId,
1874
+ text,
1875
+ namespace,
1876
+ userAddress,
1877
+ apiKey
1878
+ );
1879
+ } catch (error) {
1880
+ console.error("Failed to send queued message:", error);
1881
+ }
1882
+ }
1883
+ if (currentThreadIdRef.current === uiThreadId) {
1884
+ polling == null ? void 0 : polling.start(uiThreadId);
1885
+ }
1886
+ }
1887
+ }).catch((error) => {
1888
+ var _a2;
1889
+ console.error("Failed to create new thread:", error);
1890
+ const failedId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
1891
+ threadContext.setThreadMetadata((prev) => {
1892
+ const next = new Map(prev);
1893
+ next.delete(failedId);
1894
+ return next;
1895
+ });
1896
+ threadContext.setThreads((prev) => {
1897
+ const next = new Map(prev);
1898
+ next.delete(failedId);
1899
+ return next;
1900
+ });
1901
+ if (backendState.creatingThreadId === failedId) {
1902
+ backendState.creatingThreadId = null;
1903
+ }
1904
+ }).finally(() => {
1905
+ backendState.createThreadPromise = null;
1906
+ });
1907
+ backendState.createThreadPromise = createPromise;
1908
+ },
1909
+ onSwitchToThread: (threadId) => {
1910
+ threadContext.setCurrentThreadId(threadId);
1911
+ },
1912
+ onRename: async (threadId, newTitle) => {
1913
+ var _a, _b;
1914
+ const previousTitle = (_b = (_a = threadContext.getThreadMetadata(threadId)) == null ? void 0 : _a.title) != null ? _b : "";
1915
+ const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1916
+ threadContext.updateThreadMetadata(threadId, {
1917
+ title: normalizedTitle
1918
+ });
1919
+ try {
1920
+ await backendApiRef.current.renameThread(threadId, newTitle);
1921
+ } catch (error) {
1922
+ console.error("Failed to rename thread:", error);
1923
+ threadContext.updateThreadMetadata(threadId, {
1924
+ title: previousTitle
1925
+ });
1926
+ }
1927
+ },
1928
+ onArchive: async (threadId) => {
1929
+ threadContext.updateThreadMetadata(threadId, { status: "archived" });
1930
+ try {
1931
+ await backendApiRef.current.archiveThread(threadId);
1932
+ } catch (error) {
1933
+ console.error("Failed to archive thread:", error);
1934
+ threadContext.updateThreadMetadata(threadId, { status: "regular" });
1935
+ }
1936
+ },
1937
+ onUnarchive: async (threadId) => {
1938
+ threadContext.updateThreadMetadata(threadId, { status: "regular" });
1939
+ try {
1940
+ await backendApiRef.current.unarchiveThread(threadId);
1941
+ } catch (error) {
1942
+ console.error("Failed to unarchive thread:", error);
1943
+ threadContext.updateThreadMetadata(threadId, { status: "archived" });
1944
+ }
1945
+ },
1946
+ onDelete: async (threadId) => {
1947
+ try {
1948
+ await backendApiRef.current.deleteThread(threadId);
1949
+ threadContext.setThreadMetadata((prev) => {
1950
+ const next = new Map(prev);
1951
+ next.delete(threadId);
1952
+ return next;
1953
+ });
1954
+ threadContext.setThreads((prev) => {
1955
+ const next = new Map(prev);
1956
+ next.delete(threadId);
1957
+ return next;
1958
+ });
1959
+ backendState.pendingChat.delete(threadId);
1960
+ backendState.skipInitialFetch.delete(threadId);
1961
+ backendState.runningThreads.delete(threadId);
1962
+ if (backendState.creatingThreadId === threadId) {
1963
+ backendState.creatingThreadId = null;
1964
+ }
1965
+ if (threadContext.currentThreadId === threadId) {
1966
+ const firstRegularThread = Array.from(
1967
+ threadContext.allThreadsMetadata.entries()
1968
+ ).find(([id, meta]) => meta.status === "regular" && id !== threadId);
1969
+ if (firstRegularThread) {
1970
+ threadContext.setCurrentThreadId(firstRegularThread[0]);
1971
+ } else {
1972
+ const defaultId = "default-session";
1973
+ threadContext.setThreadMetadata(
1974
+ (prev) => new Map(prev).set(defaultId, {
1975
+ title: "New Chat",
1976
+ status: "regular",
1977
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString(),
1978
+ control: initThreadControl()
1979
+ })
1980
+ );
1981
+ threadContext.setThreadMessages(defaultId, []);
1982
+ threadContext.setCurrentThreadId(defaultId);
1983
+ }
1984
+ }
1985
+ } catch (error) {
1986
+ console.error("Failed to delete thread:", error);
1987
+ throw error;
1988
+ }
1989
+ }
1990
+ };
1991
+ }
1992
+
1993
+ // packages/react/src/interface.tsx
1994
+ import { createContext as createContext6, useContext as useContext6 } from "react";
1995
+ var AomiRuntimeContext = createContext6(null);
1996
+ var AomiRuntimeApiProvider = AomiRuntimeContext.Provider;
1997
+ function useAomiRuntime() {
1998
+ const context = useContext6(AomiRuntimeContext);
1999
+ if (!context) {
2000
+ throw new Error(
2001
+ "useAomiRuntime must be used within AomiRuntimeProvider. Wrap your app with <AomiRuntimeProvider>...</AomiRuntimeProvider>"
2002
+ );
2003
+ }
2004
+ return context;
2005
+ }
2006
+
2007
+ // packages/react/src/runtime/core.tsx
2008
+ import { jsx as jsx6 } from "react/jsx-runtime";
2009
+ function AomiRuntimeCore({
2010
+ children,
2011
+ backendApi
2012
+ }) {
2013
+ const threadContext = useThreadContext();
2014
+ const eventContext = useEventContext();
2015
+ const notificationContext = useNotification();
2016
+ const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
2017
+ const { user, onUserStateChange, getUserState } = useUser();
2018
+ const { getControlState, getCurrentThreadControl } = useControl();
2019
+ const {
2020
+ backendStateRef,
2021
+ polling,
2022
+ messageController,
2023
+ isRunning,
2024
+ setIsRunning,
2025
+ ensureInitialState,
2026
+ backendApiRef
2027
+ } = useRuntimeOrchestrator(backendApi, {
2028
+ onSyncEvents: dispatchSystemEvents,
2029
+ getPublicKey: () => getUserState().address,
2030
+ getUserState,
2031
+ getNamespace: () => {
2032
+ var _a, _b;
2033
+ return (_b = (_a = getCurrentThreadControl().namespace) != null ? _a : getControlState().defaultNamespace) != null ? _b : "default";
2034
+ },
2035
+ getApiKey: () => getControlState().apiKey
2036
+ });
2037
+ useEffect3(() => {
2038
+ const unsubscribe = onUserStateChange(async (newUser) => {
2039
+ const sessionId = threadContext.currentThreadId;
2040
+ const message = JSON.stringify({
2041
+ type: "wallet:state_changed",
2042
+ payload: {
2043
+ address: newUser.address,
2044
+ chainId: newUser.chainId,
2045
+ isConnected: newUser.isConnected,
2046
+ ensName: newUser.ensName
2047
+ }
2048
+ });
2049
+ await backendApiRef.current.postSystemMessage(sessionId, message);
2050
+ });
2051
+ return unsubscribe;
2052
+ }, [onUserStateChange, backendApiRef, threadContext.currentThreadId]);
2053
+ const threadContextRef = useRef6(threadContext);
2054
+ threadContextRef.current = threadContext;
2055
+ const currentThreadIdRef = useRef6(threadContext.currentThreadId);
2056
+ useEffect3(() => {
2057
+ currentThreadIdRef.current = threadContext.currentThreadId;
2058
+ }, [threadContext.currentThreadId]);
2059
+ useEffect3(() => {
2060
+ void ensureInitialState(threadContext.currentThreadId);
2061
+ }, [ensureInitialState, threadContext.currentThreadId]);
2062
+ useEffect3(() => {
2063
+ const threadId = threadContext.currentThreadId;
2064
+ setIsRunning(isThreadRunning(backendStateRef.current, threadId));
2065
+ }, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
2066
+ useEffect3(() => {
2067
+ const threadId = threadContext.currentThreadId;
2068
+ const currentMeta = threadContext.getThreadMetadata(threadId);
2069
+ if (currentMeta && currentMeta.control.isProcessing !== isRunning) {
2070
+ threadContext.updateThreadMetadata(threadId, {
2071
+ control: __spreadProps(__spreadValues({}, currentMeta.control), {
2072
+ isProcessing: isRunning
2073
+ })
2074
+ });
2075
+ }
2076
+ }, [isRunning, threadContext]);
2077
+ const currentMessages = threadContext.getThreadMessages(
2078
+ threadContext.currentThreadId
2079
+ );
2080
+ const resolvedSessionId = useMemo2(
2081
+ () => resolveThreadId(backendStateRef.current, threadContext.currentThreadId),
2082
+ [
2083
+ backendStateRef,
2084
+ threadContext.currentThreadId,
2085
+ threadContext.allThreadsMetadata
2086
+ ]
2087
+ );
2088
+ useEffect3(() => {
2089
+ const userAddress = user.address;
2090
+ if (!userAddress) return;
2091
+ const fetchThreadList = async () => {
2092
+ var _a, _b, _c;
2093
+ try {
2094
+ const threadList = await backendApiRef.current.fetchThreads(userAddress);
2095
+ const currentContext = threadContextRef.current;
2096
+ const newMetadata = new Map(currentContext.allThreadsMetadata);
2097
+ let maxChatNum = currentContext.threadCnt;
2098
+ for (const thread of threadList) {
2099
+ const rawTitle = (_a = thread.title) != null ? _a : "";
2100
+ const title = isPlaceholderTitle(rawTitle) ? "" : rawTitle;
2101
+ const lastActive = ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
2102
+ const existingControl = (_c = newMetadata.get(thread.session_id)) == null ? void 0 : _c.control;
2103
+ newMetadata.set(thread.session_id, {
2104
+ title,
2105
+ status: thread.is_archived ? "archived" : "regular",
2106
+ lastActiveAt: lastActive,
2107
+ control: existingControl != null ? existingControl : initThreadControl()
2108
+ });
2109
+ const match = title.match(/^Chat (\d+)$/);
2110
+ if (match) {
2111
+ const num = parseInt(match[1], 10);
2112
+ if (num > maxChatNum) {
2113
+ maxChatNum = num;
2114
+ }
2115
+ }
2116
+ }
2117
+ currentContext.setThreadMetadata(newMetadata);
2118
+ if (maxChatNum > currentContext.threadCnt) {
2119
+ currentContext.setThreadCnt(maxChatNum);
2120
+ }
2121
+ } catch (error) {
2122
+ console.error("Failed to fetch thread list:", error);
2123
+ }
2124
+ };
2125
+ void fetchThreadList();
2126
+ }, [user.address, backendApiRef]);
2127
+ const threadListAdapter = useMemo2(
2128
+ () => buildThreadListAdapter({
2129
+ backendStateRef,
2130
+ backendApiRef,
2131
+ threadContext,
2132
+ currentThreadIdRef,
2133
+ polling,
2134
+ userAddress: user.address,
2135
+ setIsRunning,
2136
+ getNamespace: () => {
2137
+ var _a, _b;
2138
+ return (_b = (_a = getCurrentThreadControl().namespace) != null ? _a : getControlState().defaultNamespace) != null ? _b : "default";
2139
+ },
2140
+ getApiKey: () => getControlState().apiKey
2141
+ }),
2142
+ [
2143
+ backendApiRef,
2144
+ polling,
2145
+ user.address,
2146
+ backendStateRef,
2147
+ setIsRunning,
2148
+ threadContext,
2149
+ threadContext.currentThreadId,
2150
+ threadContext.allThreadsMetadata,
2151
+ getControlState
2152
+ ]
2153
+ );
2154
+ useEffect3(() => {
2155
+ const backendState = backendStateRef.current;
2156
+ const currentSessionId = threadContext.currentThreadId;
2157
+ if (process.env.NODE_ENV !== "production") {
2158
+ console.debug("[aomi][sse] subscribe", {
2159
+ currentSessionId,
2160
+ resolvedSessionId,
2161
+ hasMapping: currentSessionId !== resolvedSessionId
2162
+ });
2163
+ }
2164
+ const unsubscribe = backendApiRef.current.subscribeSSE(
2165
+ resolvedSessionId,
2166
+ (event) => {
2167
+ const eventType = event.type;
2168
+ const sessionId = event.session_id;
2169
+ if (eventType === "title_changed") {
2170
+ const newTitle = event.new_title;
2171
+ const targetThreadId = resolveThreadId(backendState, sessionId);
2172
+ const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
2173
+ if (process.env.NODE_ENV !== "production") {
2174
+ console.debug("[aomi][sse] title_changed", {
2175
+ sessionId,
2176
+ newTitle,
2177
+ normalizedTitle,
2178
+ currentThreadId: threadContextRef.current.currentThreadId,
2179
+ targetThreadId,
2180
+ hasMapping: sessionId !== targetThreadId,
2181
+ creatingThreadId: backendState.creatingThreadId
2182
+ });
2183
+ }
2184
+ threadContextRef.current.setThreadMetadata((prev) => {
2185
+ var _a, _b;
2186
+ const next = new Map(prev);
2187
+ const existing = next.get(targetThreadId);
2188
+ const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
2189
+ next.set(targetThreadId, {
2190
+ title: normalizedTitle,
2191
+ status: nextStatus,
2192
+ lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString(),
2193
+ control: (_b = existing == null ? void 0 : existing.control) != null ? _b : initThreadControl()
2194
+ });
2195
+ return next;
2196
+ });
2197
+ if (!isPlaceholderTitle(newTitle) && backendState.creatingThreadId === targetThreadId) {
2198
+ backendState.creatingThreadId = null;
2199
+ }
2200
+ }
2201
+ }
2202
+ );
2203
+ return () => {
2204
+ unsubscribe == null ? void 0 : unsubscribe();
2205
+ };
2206
+ }, [
2207
+ backendApiRef,
2208
+ backendStateRef,
2209
+ threadContext.currentThreadId,
2210
+ resolvedSessionId
2211
+ ]);
2212
+ useEffect3(() => {
2213
+ const threadId = threadContext.currentThreadId;
2214
+ if (!isThreadReady(backendStateRef.current, threadId)) return;
2215
+ void messageController.flushPendingChat(threadId);
2216
+ }, [messageController, backendStateRef, threadContext.currentThreadId]);
2217
+ useEffect3(() => {
2218
+ const showToolNotification = (eventType) => (event) => {
2219
+ const payload = event.payload;
2220
+ const toolName = typeof (payload == null ? void 0 : payload.tool_name) === "string" ? payload.tool_name : void 0;
2221
+ const title = toolName ? `${eventType === "tool_update" ? "Tool update" : "Tool complete"}: ${toolName}` : eventType === "tool_update" ? "Tool update" : "Tool complete";
2222
+ const message = typeof (payload == null ? void 0 : payload.message) === "string" ? payload.message : typeof (payload == null ? void 0 : payload.result) === "string" ? payload.result : void 0;
2223
+ notificationContext.showNotification({
2224
+ type: "notice",
2225
+ title,
2226
+ message
2227
+ });
2228
+ };
2229
+ const unsubscribeUpdate = eventContext.subscribe(
2230
+ "tool_update",
2231
+ showToolNotification("tool_update")
2232
+ );
2233
+ const unsubscribeComplete = eventContext.subscribe(
2234
+ "tool_complete",
2235
+ showToolNotification("tool_complete")
2236
+ );
2237
+ return () => {
2238
+ unsubscribeUpdate();
2239
+ unsubscribeComplete();
2240
+ };
2241
+ }, [eventContext, notificationContext]);
2242
+ useEffect3(() => {
2243
+ const unsubscribe = eventContext.subscribe("system_notice", (event) => {
2244
+ const payload = event.payload;
2245
+ const message = payload == null ? void 0 : payload.message;
2246
+ });
2247
+ return unsubscribe;
2248
+ }, [eventContext, notificationContext]);
2249
+ const runtime = useExternalStoreRuntime({
2250
+ messages: currentMessages,
2251
+ setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
2252
+ isRunning,
2253
+ onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
2254
+ onCancel: () => messageController.cancel(threadContext.currentThreadId),
2255
+ convertMessage: (msg) => msg,
2256
+ adapters: { threadList: threadListAdapter }
2257
+ });
2258
+ useEffect3(() => {
2259
+ return () => {
2260
+ polling.stopAll();
2261
+ };
2262
+ }, [polling]);
2263
+ const userContext = useUser();
2264
+ const sendMessage = useCallback6(
2265
+ async (text) => {
2266
+ const appendMessage = {
2267
+ role: "user",
2268
+ content: [{ type: "text", text }]
2269
+ };
2270
+ await messageController.outbound(
2271
+ appendMessage,
2272
+ threadContext.currentThreadId
2273
+ );
2274
+ },
2275
+ [messageController, threadContext.currentThreadId]
2276
+ );
2277
+ const cancelGeneration = useCallback6(() => {
2278
+ messageController.cancel(threadContext.currentThreadId);
2279
+ }, [messageController, threadContext.currentThreadId]);
2280
+ const getMessages = useCallback6(
2281
+ (threadId) => {
2282
+ const id = threadId != null ? threadId : threadContext.currentThreadId;
2283
+ return threadContext.getThreadMessages(id);
2284
+ },
2285
+ [threadContext]
2286
+ );
2287
+ const createThread = useCallback6(async () => {
2288
+ await threadListAdapter.onSwitchToNewThread();
2289
+ return threadContextRef.current.currentThreadId;
2290
+ }, [threadListAdapter]);
2291
+ const deleteThread = useCallback6(
2292
+ async (threadId) => {
2293
+ await threadListAdapter.onDelete(threadId);
2294
+ },
2295
+ [threadListAdapter]
2296
+ );
2297
+ const renameThread = useCallback6(
2298
+ async (threadId, title) => {
2299
+ await threadListAdapter.onRename(threadId, title);
2300
+ },
2301
+ [threadListAdapter]
2302
+ );
2303
+ const archiveThread = useCallback6(
2304
+ async (threadId) => {
2305
+ await threadListAdapter.onArchive(threadId);
2306
+ },
2307
+ [threadListAdapter]
2308
+ );
2309
+ const selectThread = useCallback6(
2310
+ (threadId) => {
2311
+ if (threadContext.allThreadsMetadata.has(threadId)) {
2312
+ threadListAdapter.onSwitchToThread(threadId);
2313
+ } else {
2314
+ void threadListAdapter.onSwitchToNewThread();
2315
+ }
2316
+ },
2317
+ [threadContext.allThreadsMetadata, threadListAdapter]
2318
+ );
2319
+ const aomiRuntimeApi = useMemo2(
2320
+ () => ({
2321
+ // User API
2322
+ user: userContext.user,
2323
+ getUserState: userContext.getUserState,
2324
+ setUser: userContext.setUser,
2325
+ onUserStateChange: userContext.onUserStateChange,
2326
+ // Thread API
2327
+ currentThreadId: threadContext.currentThreadId,
2328
+ threadViewKey: threadContext.threadViewKey,
2329
+ threadMetadata: threadContext.allThreadsMetadata,
2330
+ getThreadMetadata: threadContext.getThreadMetadata,
2331
+ createThread,
2332
+ deleteThread,
2333
+ renameThread,
2334
+ archiveThread,
2335
+ selectThread,
2336
+ // Chat API
2337
+ isRunning,
2338
+ getMessages,
2339
+ sendMessage,
2340
+ cancelGeneration,
2341
+ // Notification API
2342
+ notifications: notificationContext.notifications,
2343
+ showNotification: notificationContext.showNotification,
2344
+ dismissNotification: notificationContext.dismissNotification,
2345
+ clearAllNotifications: notificationContext.clearAll,
2346
+ // Event API
2347
+ subscribe: eventContext.subscribe,
2348
+ sendSystemCommand: eventContext.sendOutboundSystem,
2349
+ sseStatus: eventContext.sseStatus
2350
+ }),
2351
+ [
2352
+ userContext,
2353
+ threadContext.currentThreadId,
2354
+ threadContext.threadViewKey,
2355
+ threadContext.allThreadsMetadata,
2356
+ threadContext.getThreadMetadata,
2357
+ createThread,
2358
+ deleteThread,
2359
+ renameThread,
2360
+ archiveThread,
2361
+ selectThread,
2362
+ isRunning,
2363
+ getMessages,
2364
+ sendMessage,
2365
+ cancelGeneration,
2366
+ notificationContext,
2367
+ eventContext
2368
+ ]
2369
+ );
2370
+ return /* @__PURE__ */ jsx6(AomiRuntimeApiProvider, { value: aomiRuntimeApi, children: /* @__PURE__ */ jsx6(AssistantRuntimeProvider, { runtime, children }) });
2371
+ }
2372
+
2373
+ // packages/react/src/runtime/aomi-runtime.tsx
2374
+ import { jsx as jsx7 } from "react/jsx-runtime";
2375
+ function AomiRuntimeProvider({
2376
+ children,
2377
+ backendUrl = "http://localhost:8080"
2378
+ }) {
2379
+ const backendApi = useMemo3(() => new BackendApi(backendUrl), [backendUrl]);
2380
+ return /* @__PURE__ */ jsx7(ThreadContextProvider, { children: /* @__PURE__ */ jsx7(NotificationContextProvider, { children: /* @__PURE__ */ jsx7(UserContextProvider, { children: /* @__PURE__ */ jsx7(AomiRuntimeInner, { backendApi, children }) }) }) });
2381
+ }
2382
+ function AomiRuntimeInner({
2383
+ children,
2384
+ backendApi
2385
+ }) {
2386
+ var _a;
2387
+ const threadContext = useThreadContext();
2388
+ const { user } = useUser();
2389
+ return /* @__PURE__ */ jsx7(
2390
+ ControlContextProvider,
2391
+ {
2392
+ backendApi,
2393
+ sessionId: threadContext.currentThreadId,
2394
+ publicKey: (_a = user.address) != null ? _a : void 0,
2395
+ getThreadMetadata: threadContext.getThreadMetadata,
2396
+ updateThreadMetadata: threadContext.updateThreadMetadata,
2397
+ children: /* @__PURE__ */ jsx7(
2398
+ EventContextProvider,
2399
+ {
2400
+ backendApi,
2401
+ sessionId: threadContext.currentThreadId,
2402
+ children: /* @__PURE__ */ jsx7(AomiRuntimeCore, { backendApi, children })
2403
+ }
2404
+ )
2405
+ }
2406
+ );
2407
+ }
2408
+
2409
+ // packages/react/src/handlers/wallet-handler.ts
2410
+ import { useCallback as useCallback7, useEffect as useEffect4, useState as useState6 } from "react";
2411
+ function useWalletHandler({
2412
+ sessionId,
2413
+ onTxRequest
2414
+ }) {
2415
+ const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
2416
+ const { setUser, getUserState } = useUser();
2417
+ const [pendingTxRequests, setPendingTxRequests] = useState6(
2418
+ []
2419
+ );
2420
+ useEffect4(() => {
2421
+ const unsubscribe = subscribe2(
2422
+ "wallet_tx_request",
2423
+ (event) => {
2424
+ const request = event.payload;
2425
+ setPendingTxRequests((prev) => [...prev, request]);
2426
+ onTxRequest == null ? void 0 : onTxRequest(request);
2427
+ }
2428
+ );
2429
+ return unsubscribe;
2430
+ }, [subscribe2, onTxRequest]);
2431
+ useEffect4(() => {
2432
+ const unsubscribe = subscribe2(
2433
+ "user_state_request",
2434
+ (event) => {
2435
+ sendOutbound({
2436
+ type: "user_state_response",
2437
+ sessionId,
2438
+ payload: getUserState()
2439
+ });
2440
+ }
2441
+ );
2442
+ return unsubscribe;
2443
+ }, [subscribe2, onTxRequest]);
2444
+ const sendTxComplete = useCallback7(
2445
+ (tx) => {
2446
+ sendOutbound({
2447
+ type: "wallet:tx_complete",
2448
+ sessionId,
2449
+ payload: tx
2450
+ });
2451
+ },
2452
+ [sendOutbound, sessionId]
2453
+ );
2454
+ const sendConnectionChange = useCallback7(
2455
+ (status, address, chainId) => {
2456
+ if (status === "connected") {
2457
+ setUser({
2458
+ isConnected: true,
2459
+ address,
2460
+ chainId
2461
+ });
2462
+ } else {
2463
+ setUser({
2464
+ isConnected: false,
2465
+ address: void 0,
2466
+ chainId: void 0
2467
+ });
2468
+ }
2469
+ sendOutbound({
2470
+ type: status === "connected" ? "wallet:connected" : "wallet:disconnected",
2471
+ sessionId,
2472
+ payload: { status, address }
2473
+ });
2474
+ },
2475
+ [setUser, sendOutbound, sessionId]
2476
+ );
2477
+ const clearTxRequest = useCallback7((index) => {
2478
+ setPendingTxRequests((prev) => prev.filter((_, i) => i !== index));
2479
+ }, []);
2480
+ return {
2481
+ sendTxComplete,
2482
+ sendConnectionChange,
2483
+ pendingTxRequests,
2484
+ clearTxRequest
2485
+ };
2486
+ }
2487
+
2488
+ // packages/react/src/handlers/notification-handler.ts
2489
+ import { useCallback as useCallback8, useEffect as useEffect5, useState as useState7 } from "react";
2490
+ var notificationIdCounter2 = 0;
2491
+ function generateNotificationId() {
2492
+ return `notif-${Date.now()}-${++notificationIdCounter2}`;
2493
+ }
2494
+ function useNotificationHandler({
2495
+ onNotification
2496
+ } = {}) {
2497
+ const { subscribe: subscribe2 } = useEventContext();
2498
+ const [notifications, setNotifications] = useState7([]);
2499
+ useEffect5(() => {
2500
+ const unsubscribe = subscribe2("notification", (event) => {
2501
+ var _a, _b;
2502
+ const payload = event.payload;
2503
+ const notification = {
2504
+ id: generateNotificationId(),
2505
+ type: (_a = payload.type) != null ? _a : "notification",
2506
+ title: (_b = payload.title) != null ? _b : "Notification",
2507
+ body: payload.body,
2508
+ handled: false,
2509
+ timestamp: event.timestamp,
2510
+ sessionId: event.sessionId
2511
+ };
2512
+ setNotifications((prev) => [notification, ...prev]);
2513
+ onNotification == null ? void 0 : onNotification(notification);
2514
+ });
2515
+ return unsubscribe;
2516
+ }, [subscribe2, onNotification]);
2517
+ const unhandledCount = notifications.filter((n) => !n.handled).length;
2518
+ const markHandled = useCallback8((id) => {
2519
+ setNotifications(
2520
+ (prev) => prev.map((n) => n.id === id ? __spreadProps(__spreadValues({}, n), { handled: true }) : n)
2521
+ );
2522
+ }, []);
2523
+ return {
2524
+ notifications,
2525
+ unhandledCount,
2526
+ markDone: markHandled
2527
+ };
2528
+ }
2529
+ export {
2530
+ AomiRuntimeProvider,
2531
+ BackendApi,
2532
+ ControlContextProvider,
2533
+ EventContextProvider,
2534
+ NotificationContextProvider,
2535
+ ThreadContextProvider,
2536
+ UserContextProvider,
2537
+ cn,
2538
+ formatAddress,
2539
+ getNetworkName,
2540
+ initThreadControl,
2541
+ useAomiRuntime,
2542
+ useControl,
2543
+ useCurrentThreadMessages,
2544
+ useCurrentThreadMetadata,
2545
+ useEventContext,
2546
+ useNotification,
2547
+ useNotificationHandler,
2548
+ useThreadContext,
2549
+ useUser,
2550
+ useWalletHandler
2551
+ };
2552
+ //# sourceMappingURL=index.js.map