@aomi-labs/react 0.2.0 → 0.2.1

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 DELETED
@@ -1,2123 +0,0 @@
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
- // 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
- // src/backend/client.ts
208
- var SESSION_ID_HEADER = "X-Session-Id";
209
- function toQueryString(payload) {
210
- const params = new URLSearchParams();
211
- for (const [key, value] of Object.entries(payload)) {
212
- if (value === void 0 || value === null) continue;
213
- params.set(key, String(value));
214
- }
215
- const qs = params.toString();
216
- return qs ? `?${qs}` : "";
217
- }
218
- function withSessionHeader(sessionId, init) {
219
- const headers = new Headers(init);
220
- headers.set(SESSION_ID_HEADER, sessionId);
221
- return headers;
222
- }
223
- async function postState(backendUrl, path, payload, sessionId) {
224
- const query = toQueryString(payload);
225
- const url = `${backendUrl}${path}${query}`;
226
- const response = await fetch(url, {
227
- method: "POST",
228
- headers: withSessionHeader(sessionId)
229
- });
230
- if (!response.ok) {
231
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
232
- }
233
- return await response.json();
234
- }
235
- var BackendApi = class {
236
- constructor(backendUrl) {
237
- this.backendUrl = backendUrl;
238
- this.sseSubscriber = createSseSubscriber({
239
- backendUrl,
240
- getHeaders: (sessionId) => withSessionHeader(sessionId, { Accept: "text/event-stream" })
241
- });
242
- }
243
- async fetchState(sessionId, userState) {
244
- const url = new URL("/api/state", this.backendUrl);
245
- if (userState) {
246
- url.searchParams.set("user_state", JSON.stringify(userState));
247
- }
248
- const response = await fetch(url.toString(), {
249
- headers: withSessionHeader(sessionId)
250
- });
251
- if (!response.ok) {
252
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
253
- }
254
- return await response.json();
255
- }
256
- async postChatMessage(sessionId, message, publicKey) {
257
- return postState(
258
- this.backendUrl,
259
- "/api/chat",
260
- {
261
- message,
262
- public_key: publicKey
263
- },
264
- sessionId
265
- );
266
- }
267
- async postSystemMessage(sessionId, message) {
268
- return postState(
269
- this.backendUrl,
270
- "/api/system",
271
- {
272
- message
273
- },
274
- sessionId
275
- );
276
- }
277
- async postInterrupt(sessionId) {
278
- return postState(
279
- this.backendUrl,
280
- "/api/interrupt",
281
- {},
282
- sessionId
283
- );
284
- }
285
- /**
286
- * Subscribe to SSE updates for a session.
287
- * Uses fetch streaming and reconnects on disconnects.
288
- * Returns an unsubscribe function.
289
- */
290
- subscribeSSE(sessionId, onUpdate, onError) {
291
- return this.sseSubscriber.subscribe(sessionId, onUpdate, onError);
292
- }
293
- async fetchThreads(publicKey) {
294
- const url = `${this.backendUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
295
- const response = await fetch(url);
296
- if (!response.ok) {
297
- throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
298
- }
299
- return await response.json();
300
- }
301
- async fetchThread(sessionId) {
302
- const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
303
- const response = await fetch(url, {
304
- headers: withSessionHeader(sessionId)
305
- });
306
- if (!response.ok) {
307
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
308
- }
309
- return await response.json();
310
- }
311
- async createThread(threadId, publicKey) {
312
- const body = {};
313
- if (publicKey) body.public_key = publicKey;
314
- const url = `${this.backendUrl}/api/sessions`;
315
- const response = await fetch(url, {
316
- method: "POST",
317
- headers: withSessionHeader(threadId, {
318
- "Content-Type": "application/json"
319
- }),
320
- body: JSON.stringify(body)
321
- });
322
- if (!response.ok) {
323
- throw new Error(`Failed to create thread: HTTP ${response.status}`);
324
- }
325
- return await response.json();
326
- }
327
- async archiveThread(sessionId) {
328
- const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
329
- const response = await fetch(url, {
330
- method: "POST",
331
- headers: withSessionHeader(sessionId)
332
- });
333
- if (!response.ok) {
334
- throw new Error(`Failed to archive thread: HTTP ${response.status}`);
335
- }
336
- }
337
- async unarchiveThread(sessionId) {
338
- const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
339
- const response = await fetch(url, {
340
- method: "POST",
341
- headers: withSessionHeader(sessionId)
342
- });
343
- if (!response.ok) {
344
- throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
345
- }
346
- }
347
- async deleteThread(sessionId) {
348
- const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
349
- const response = await fetch(url, {
350
- method: "DELETE",
351
- headers: withSessionHeader(sessionId)
352
- });
353
- if (!response.ok) {
354
- throw new Error(`Failed to delete thread: HTTP ${response.status}`);
355
- }
356
- }
357
- async renameThread(sessionId, newTitle) {
358
- const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
359
- const response = await fetch(url, {
360
- method: "PATCH",
361
- headers: withSessionHeader(sessionId, {
362
- "Content-Type": "application/json"
363
- }),
364
- body: JSON.stringify({ title: newTitle })
365
- });
366
- if (!response.ok) {
367
- throw new Error(`Failed to rename thread: HTTP ${response.status}`);
368
- }
369
- }
370
- async getSystemEvents(sessionId, count) {
371
- const url = new URL("/api/events", this.backendUrl);
372
- if (count !== void 0) {
373
- url.searchParams.set("count", String(count));
374
- }
375
- const response = await fetch(url.toString(), {
376
- headers: withSessionHeader(sessionId)
377
- });
378
- if (!response.ok) {
379
- if (response.status === 404) return [];
380
- throw new Error(`Failed to get system events: HTTP ${response.status}`);
381
- }
382
- return await response.json();
383
- }
384
- // fetchEventsAfter removed: /api/events only supports count now
385
- };
386
-
387
- // src/runtime/aomi-runtime.tsx
388
- import { useMemo as useMemo3 } from "react";
389
-
390
- // src/contexts/event-context.tsx
391
- import {
392
- createContext,
393
- useCallback,
394
- useContext,
395
- useEffect,
396
- useRef,
397
- useState
398
- } from "react";
399
-
400
- // src/backend/types.ts
401
- function isInlineCall(event) {
402
- return "InlineCall" in event;
403
- }
404
- function isSystemNotice(event) {
405
- return "SystemNotice" in event;
406
- }
407
- function isSystemError(event) {
408
- return "SystemError" in event;
409
- }
410
- function isAsyncCallback(event) {
411
- return "AsyncCallback" in event;
412
- }
413
-
414
- // src/state/event-buffer.ts
415
- function createEventBuffer() {
416
- return {
417
- inboundQueue: [],
418
- outboundQueue: [],
419
- sseStatus: "disconnected",
420
- lastEventId: null,
421
- subscribers: /* @__PURE__ */ new Map()
422
- };
423
- }
424
- function enqueueInbound(state, event) {
425
- state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
426
- status: "pending",
427
- timestamp: Date.now()
428
- }));
429
- }
430
- function subscribe(state, type, callback) {
431
- if (!state.subscribers.has(type)) {
432
- state.subscribers.set(type, /* @__PURE__ */ new Set());
433
- }
434
- state.subscribers.get(type).add(callback);
435
- return () => {
436
- var _a;
437
- (_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
438
- };
439
- }
440
- function dispatch(state, event) {
441
- const typeSubscribers = state.subscribers.get(event.type);
442
- if (typeSubscribers) {
443
- for (const callback of typeSubscribers) {
444
- callback(event);
445
- }
446
- }
447
- const allSubscribers = state.subscribers.get("*");
448
- if (allSubscribers) {
449
- for (const callback of allSubscribers) {
450
- callback(event);
451
- }
452
- }
453
- }
454
- function setSSEStatus(state, status) {
455
- state.sseStatus = status;
456
- }
457
-
458
- // src/contexts/event-context.tsx
459
- import { jsx } from "react/jsx-runtime";
460
- var EventContextState = createContext(null);
461
- function useEventContext() {
462
- const context = useContext(EventContextState);
463
- if (!context) {
464
- throw new Error(
465
- "useEventContext must be used within EventContextProvider. Wrap your app with <EventContextProvider>...</EventContextProvider>"
466
- );
467
- }
468
- return context;
469
- }
470
- function EventContextProvider({
471
- children,
472
- backendApi,
473
- sessionId
474
- }) {
475
- const bufferRef = useRef(null);
476
- if (!bufferRef.current) {
477
- bufferRef.current = createEventBuffer();
478
- }
479
- const buffer = bufferRef.current;
480
- const [sseStatus, setSseStatus] = useState("disconnected");
481
- useEffect(() => {
482
- setSSEStatus(buffer, "connecting");
483
- setSseStatus("connecting");
484
- const unsubscribe = backendApi.subscribeSSE(
485
- sessionId,
486
- (event) => {
487
- enqueueInbound(buffer, {
488
- type: event.type,
489
- sessionId: event.session_id,
490
- payload: event
491
- });
492
- const inboundEvent = {
493
- type: event.type,
494
- sessionId: event.session_id,
495
- payload: event,
496
- status: "fetched",
497
- timestamp: Date.now()
498
- };
499
- dispatch(buffer, inboundEvent);
500
- },
501
- (error) => {
502
- console.error("SSE error:", error);
503
- setSSEStatus(buffer, "disconnected");
504
- setSseStatus("disconnected");
505
- }
506
- );
507
- setSSEStatus(buffer, "connected");
508
- setSseStatus("connected");
509
- return () => {
510
- unsubscribe();
511
- setSSEStatus(buffer, "disconnected");
512
- setSseStatus("disconnected");
513
- };
514
- }, [backendApi, sessionId, buffer]);
515
- const subscribeCallback = useCallback(
516
- (type, callback) => {
517
- return subscribe(buffer, type, callback);
518
- },
519
- [buffer]
520
- );
521
- const sendOutbound = useCallback(
522
- async (event) => {
523
- try {
524
- const message = JSON.stringify({
525
- type: event.type,
526
- payload: event.payload
527
- });
528
- await backendApi.postSystemMessage(event.sessionId, message);
529
- } catch (error) {
530
- console.error("Failed to send outbound event:", error);
531
- }
532
- },
533
- [backendApi]
534
- );
535
- const dispatchSystemEvents = useCallback(
536
- (sessionId2, events) => {
537
- var _a;
538
- for (const event of events) {
539
- let eventType;
540
- let payload;
541
- if (isInlineCall(event)) {
542
- eventType = event.InlineCall.type;
543
- payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
544
- } else if (isSystemNotice(event)) {
545
- eventType = "system_notice";
546
- payload = { message: event.SystemNotice };
547
- } else if (isSystemError(event)) {
548
- eventType = "system_error";
549
- payload = { message: event.SystemError };
550
- } else if (isAsyncCallback(event)) {
551
- eventType = "async_callback";
552
- payload = event.AsyncCallback;
553
- } else {
554
- console.warn("Unknown system event type:", event);
555
- continue;
556
- }
557
- const inboundEvent = {
558
- type: eventType,
559
- sessionId: sessionId2,
560
- payload,
561
- status: "fetched",
562
- timestamp: Date.now()
563
- };
564
- enqueueInbound(buffer, {
565
- type: eventType,
566
- sessionId: sessionId2,
567
- payload
568
- });
569
- dispatch(buffer, inboundEvent);
570
- }
571
- },
572
- [buffer]
573
- );
574
- const contextValue = {
575
- subscribe: subscribeCallback,
576
- sendOutboundSystem: sendOutbound,
577
- dispatchInboundSystem: dispatchSystemEvents,
578
- sseStatus
579
- };
580
- return /* @__PURE__ */ jsx(EventContextState.Provider, { value: contextValue, children });
581
- }
582
-
583
- // src/contexts/notification-context.tsx
584
- import {
585
- createContext as createContext2,
586
- useCallback as useCallback2,
587
- useContext as useContext2,
588
- useState as useState2
589
- } from "react";
590
- import { jsx as jsx2 } from "react/jsx-runtime";
591
- var NotificationContext = createContext2(null);
592
- function useNotification() {
593
- const context = useContext2(NotificationContext);
594
- if (!context) {
595
- throw new Error(
596
- "useNotification must be used within NotificationContextProvider"
597
- );
598
- }
599
- return context;
600
- }
601
- var notificationIdCounter = 0;
602
- function generateId() {
603
- return `notif-${Date.now()}-${++notificationIdCounter}`;
604
- }
605
- function NotificationContextProvider({
606
- children
607
- }) {
608
- const [notifications, setNotifications] = useState2([]);
609
- const showNotification = useCallback2((params) => {
610
- const id = generateId();
611
- const notification = __spreadProps(__spreadValues({}, params), {
612
- id,
613
- timestamp: Date.now()
614
- });
615
- setNotifications((prev) => [notification, ...prev]);
616
- return id;
617
- }, []);
618
- const dismissNotification = useCallback2((id) => {
619
- setNotifications((prev) => prev.filter((n) => n.id !== id));
620
- }, []);
621
- const clearAll = useCallback2(() => {
622
- setNotifications([]);
623
- }, []);
624
- const value = {
625
- notifications,
626
- showNotification,
627
- dismissNotification,
628
- clearAll
629
- };
630
- return /* @__PURE__ */ jsx2(NotificationContext.Provider, { value, children });
631
- }
632
-
633
- // src/contexts/thread-context.tsx
634
- import {
635
- createContext as createContext3,
636
- useContext as useContext3,
637
- useMemo,
638
- useRef as useRef2,
639
- useSyncExternalStore
640
- } from "react";
641
-
642
- // src/state/thread-store.ts
643
- var shouldLogThreadUpdates = process.env.NODE_ENV !== "production";
644
- var logThreadMetadataChange = (source, threadId, prev, next) => {
645
- if (!shouldLogThreadUpdates) return;
646
- if (!prev && !next) return;
647
- if (!prev || !next) {
648
- console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
649
- return;
650
- }
651
- if (prev.title !== next.title || prev.status !== next.status || prev.lastActiveAt !== next.lastActiveAt) {
652
- console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
653
- }
654
- };
655
- var ThreadStore = class {
656
- constructor(options) {
657
- this.listeners = /* @__PURE__ */ new Set();
658
- this.subscribe = (listener) => {
659
- this.listeners.add(listener);
660
- return () => {
661
- this.listeners.delete(listener);
662
- };
663
- };
664
- this.getSnapshot = () => this.snapshot;
665
- this.setCurrentThreadId = (threadId) => {
666
- this.ensureThreadExists(threadId);
667
- this.updateState({ currentThreadId: threadId });
668
- };
669
- this.bumpThreadViewKey = () => {
670
- this.updateState({ threadViewKey: this.state.threadViewKey + 1 });
671
- };
672
- this.setThreadCnt = (updater) => {
673
- const nextCnt = this.resolveStateAction(updater, this.state.threadCnt);
674
- this.updateState({ threadCnt: nextCnt });
675
- };
676
- this.setThreads = (updater) => {
677
- const nextThreads = this.resolveStateAction(updater, this.state.threads);
678
- this.updateState({ threads: new Map(nextThreads) });
679
- };
680
- this.setThreadMetadata = (updater) => {
681
- const prevMetadata = this.state.threadMetadata;
682
- const nextMetadata = this.resolveStateAction(updater, prevMetadata);
683
- for (const [threadId, next] of nextMetadata.entries()) {
684
- logThreadMetadataChange(
685
- "setThreadMetadata",
686
- threadId,
687
- prevMetadata.get(threadId),
688
- next
689
- );
690
- }
691
- for (const [threadId, prev] of prevMetadata.entries()) {
692
- if (!nextMetadata.has(threadId)) {
693
- logThreadMetadataChange("setThreadMetadata", threadId, prev, void 0);
694
- }
695
- }
696
- this.updateState({ threadMetadata: new Map(nextMetadata) });
697
- };
698
- this.setThreadMessages = (threadId, messages) => {
699
- this.ensureThreadExists(threadId);
700
- const nextThreads = new Map(this.state.threads);
701
- nextThreads.set(threadId, messages);
702
- this.updateState({ threads: nextThreads });
703
- };
704
- this.getThreadMessages = (threadId) => {
705
- var _a;
706
- return (_a = this.state.threads.get(threadId)) != null ? _a : [];
707
- };
708
- this.getThreadMetadata = (threadId) => {
709
- return this.state.threadMetadata.get(threadId);
710
- };
711
- this.updateThreadMetadata = (threadId, updates) => {
712
- const existing = this.state.threadMetadata.get(threadId);
713
- if (!existing) {
714
- return;
715
- }
716
- const next = __spreadValues(__spreadValues({}, existing), updates);
717
- const nextMetadata = new Map(this.state.threadMetadata);
718
- nextMetadata.set(threadId, next);
719
- logThreadMetadataChange("updateThreadMetadata", threadId, existing, next);
720
- this.updateState({ threadMetadata: nextMetadata });
721
- };
722
- var _a;
723
- const initialThreadId = (_a = options == null ? void 0 : options.initialThreadId) != null ? _a : crypto.randomUUID();
724
- this.state = {
725
- currentThreadId: initialThreadId,
726
- threadViewKey: 0,
727
- threadCnt: 1,
728
- threads: /* @__PURE__ */ new Map([[initialThreadId, []]]),
729
- threadMetadata: /* @__PURE__ */ new Map([
730
- [
731
- initialThreadId,
732
- {
733
- title: "New Chat",
734
- status: "pending",
735
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
736
- }
737
- ]
738
- ])
739
- };
740
- this.snapshot = this.buildSnapshot();
741
- }
742
- emit() {
743
- for (const listener of this.listeners) {
744
- listener();
745
- }
746
- }
747
- resolveStateAction(updater, current) {
748
- return typeof updater === "function" ? updater(current) : updater;
749
- }
750
- ensureThreadExists(threadId) {
751
- if (!this.state.threadMetadata.has(threadId)) {
752
- const nextMetadata = new Map(this.state.threadMetadata);
753
- nextMetadata.set(threadId, {
754
- title: "New Chat",
755
- status: "regular",
756
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
757
- });
758
- this.state = __spreadProps(__spreadValues({}, this.state), { threadMetadata: nextMetadata });
759
- }
760
- if (!this.state.threads.has(threadId)) {
761
- const nextThreads = new Map(this.state.threads);
762
- nextThreads.set(threadId, []);
763
- this.state = __spreadProps(__spreadValues({}, this.state), { threads: nextThreads });
764
- }
765
- }
766
- updateState(partial) {
767
- this.state = __spreadValues(__spreadValues({}, this.state), partial);
768
- this.snapshot = this.buildSnapshot();
769
- this.emit();
770
- }
771
- buildSnapshot() {
772
- return {
773
- currentThreadId: this.state.currentThreadId,
774
- setCurrentThreadId: this.setCurrentThreadId,
775
- threadViewKey: this.state.threadViewKey,
776
- bumpThreadViewKey: this.bumpThreadViewKey,
777
- allThreads: this.state.threads,
778
- setThreads: this.setThreads,
779
- allThreadsMetadata: this.state.threadMetadata,
780
- setThreadMetadata: this.setThreadMetadata,
781
- threadCnt: this.state.threadCnt,
782
- setThreadCnt: this.setThreadCnt,
783
- getThreadMessages: this.getThreadMessages,
784
- setThreadMessages: this.setThreadMessages,
785
- getThreadMetadata: this.getThreadMetadata,
786
- updateThreadMetadata: this.updateThreadMetadata
787
- };
788
- }
789
- };
790
-
791
- // src/contexts/thread-context.tsx
792
- import { jsx as jsx3 } from "react/jsx-runtime";
793
- var ThreadContextState = createContext3(null);
794
- function useThreadContext() {
795
- const context = useContext3(ThreadContextState);
796
- if (!context) {
797
- throw new Error(
798
- "useThreadContext must be used within ThreadContextProvider. Wrap your app with <ThreadContextProvider>...</ThreadContextProvider>"
799
- );
800
- }
801
- return context;
802
- }
803
- function ThreadContextProvider({
804
- children,
805
- initialThreadId
806
- }) {
807
- const storeRef = useRef2(null);
808
- if (!storeRef.current) {
809
- storeRef.current = new ThreadStore({ initialThreadId });
810
- }
811
- const store = storeRef.current;
812
- const value = useSyncExternalStore(
813
- store.subscribe,
814
- store.getSnapshot,
815
- store.getSnapshot
816
- );
817
- return /* @__PURE__ */ jsx3(ThreadContextState.Provider, { value, children });
818
- }
819
- function useCurrentThreadMessages() {
820
- const { currentThreadId, getThreadMessages } = useThreadContext();
821
- return useMemo(
822
- () => getThreadMessages(currentThreadId),
823
- [currentThreadId, getThreadMessages]
824
- );
825
- }
826
- function useCurrentThreadMetadata() {
827
- const { currentThreadId, getThreadMetadata } = useThreadContext();
828
- return useMemo(
829
- () => getThreadMetadata(currentThreadId),
830
- [currentThreadId, getThreadMetadata]
831
- );
832
- }
833
-
834
- // src/contexts/user-context.tsx
835
- import {
836
- createContext as createContext4,
837
- useCallback as useCallback3,
838
- useContext as useContext4,
839
- useRef as useRef3,
840
- useState as useState3
841
- } from "react";
842
- import { jsx as jsx4 } from "react/jsx-runtime";
843
- var UserContext = createContext4(void 0);
844
- function useUser() {
845
- const context = useContext4(UserContext);
846
- if (!context) {
847
- throw new Error("useUser must be used within UserContextProvider");
848
- }
849
- return {
850
- user: context.user,
851
- setUser: context.setUser,
852
- getUserState: context.getUserState,
853
- onUserStateChange: context.onUserStateChange
854
- };
855
- }
856
- function UserContextProvider({ children }) {
857
- const [user, setUserState] = useState3({
858
- isConnected: false,
859
- address: void 0,
860
- chainId: void 0,
861
- ensName: void 0
862
- });
863
- const userRef = useRef3(user);
864
- userRef.current = user;
865
- const StateChangeCallbacks = useRef3(
866
- /* @__PURE__ */ new Set()
867
- );
868
- const setUser = useCallback3((data) => {
869
- setUserState((prev) => {
870
- const next = __spreadValues(__spreadValues({}, prev), data);
871
- StateChangeCallbacks.current.forEach((callback) => {
872
- callback(next);
873
- });
874
- return next;
875
- });
876
- }, []);
877
- const getUserState = useCallback3(() => userRef.current, []);
878
- const onUserStateChange = useCallback3(
879
- (callback) => {
880
- StateChangeCallbacks.current.add(callback);
881
- return () => {
882
- StateChangeCallbacks.current.delete(callback);
883
- };
884
- },
885
- []
886
- );
887
- return /* @__PURE__ */ jsx4(
888
- UserContext.Provider,
889
- {
890
- value: {
891
- user,
892
- setUser,
893
- getUserState,
894
- onUserStateChange
895
- },
896
- children
897
- }
898
- );
899
- }
900
-
901
- // src/runtime/core.tsx
902
- import { useCallback as useCallback5, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef5 } from "react";
903
- import {
904
- AssistantRuntimeProvider,
905
- useExternalStoreRuntime
906
- } from "@assistant-ui/react";
907
-
908
- // src/runtime/orchestrator.ts
909
- import { useCallback as useCallback4, useRef as useRef4, useState as useState4 } from "react";
910
-
911
- // src/runtime/utils.ts
912
- import { clsx } from "clsx";
913
- import { twMerge } from "tailwind-merge";
914
- function cn(...inputs) {
915
- return twMerge(clsx(inputs));
916
- }
917
- var parseTimestamp = (value) => {
918
- if (value === void 0 || value === null) return 0;
919
- if (typeof value === "number") {
920
- return Number.isFinite(value) ? value < 1e12 ? value * 1e3 : value : 0;
921
- }
922
- const numeric = Number(value);
923
- if (!Number.isNaN(numeric)) {
924
- return numeric < 1e12 ? numeric * 1e3 : numeric;
925
- }
926
- const ts = Date.parse(value);
927
- return Number.isNaN(ts) ? 0 : ts;
928
- };
929
- var isPlaceholderTitle = (title) => {
930
- var _a;
931
- const normalized = (_a = title == null ? void 0 : title.trim()) != null ? _a : "";
932
- return !normalized || normalized.startsWith("#[");
933
- };
934
- function toInboundMessage(msg) {
935
- var _a;
936
- if (msg.sender === "system") return null;
937
- const content = [];
938
- const role = msg.sender === "user" ? "user" : "assistant";
939
- if (msg.content) {
940
- content.push({ type: "text", text: msg.content });
941
- }
942
- const [topic, toolContent] = (_a = parseToolPayload(msg)) != null ? _a : [];
943
- if (topic && toolContent) {
944
- content.push({
945
- type: "tool-call",
946
- toolCallId: `tool_${Date.now()}`,
947
- toolName: topic,
948
- args: void 0,
949
- result: (() => {
950
- try {
951
- return JSON.parse(toolContent);
952
- } catch (e) {
953
- return { args: toolContent };
954
- }
955
- })()
956
- });
957
- }
958
- const threadMessage = __spreadValues({
959
- role,
960
- content: content.length > 0 ? content : [{ type: "text", text: "" }]
961
- }, msg.timestamp && { createdAt: new Date(msg.timestamp) });
962
- return threadMessage;
963
- }
964
- function parseToolPayload(msg) {
965
- return parseToolResult(msg.tool_result);
966
- }
967
- function parseToolResult(toolResult) {
968
- if (!toolResult) return null;
969
- if (Array.isArray(toolResult) && toolResult.length === 2) {
970
- const [topic, content] = toolResult;
971
- return [String(topic), String(content != null ? content : "")];
972
- }
973
- return null;
974
- }
975
- var getNetworkName = (chainId) => {
976
- if (chainId === void 0) return "";
977
- const id = typeof chainId === "string" ? Number(chainId) : chainId;
978
- switch (id) {
979
- case 1:
980
- return "ethereum";
981
- case 137:
982
- return "polygon";
983
- case 42161:
984
- return "arbitrum";
985
- case 8453:
986
- return "base";
987
- case 10:
988
- return "optimism";
989
- case 11155111:
990
- return "sepolia";
991
- case 1337:
992
- case 31337:
993
- return "testnet";
994
- case 59140:
995
- return "linea-sepolia";
996
- case 59144:
997
- return "linea";
998
- default:
999
- return "testnet";
1000
- }
1001
- };
1002
- var formatAddress = (addr) => addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : "Connect Wallet";
1003
-
1004
- // src/state/backend-state.ts
1005
- function createBackendState() {
1006
- return {
1007
- skipInitialFetch: /* @__PURE__ */ new Set(),
1008
- pendingChat: /* @__PURE__ */ new Map(),
1009
- runningThreads: /* @__PURE__ */ new Set(),
1010
- creatingThreadId: null,
1011
- createThreadPromise: null
1012
- };
1013
- }
1014
- function resolveThreadId(state, threadId) {
1015
- return threadId;
1016
- }
1017
- function isThreadReady(state, threadId) {
1018
- return state.creatingThreadId !== threadId;
1019
- }
1020
- function markSkipInitialFetch(state, threadId) {
1021
- state.skipInitialFetch.add(threadId);
1022
- }
1023
- function shouldSkipInitialFetch(state, threadId) {
1024
- return state.skipInitialFetch.has(threadId);
1025
- }
1026
- function clearSkipInitialFetch(state, threadId) {
1027
- state.skipInitialFetch.delete(threadId);
1028
- }
1029
- function setThreadRunning(state, threadId, running) {
1030
- if (running) {
1031
- state.runningThreads.add(threadId);
1032
- } else {
1033
- state.runningThreads.delete(threadId);
1034
- }
1035
- }
1036
- function isThreadRunning(state, threadId) {
1037
- return state.runningThreads.has(threadId);
1038
- }
1039
- function enqueuePendingChat(state, threadId, text) {
1040
- var _a;
1041
- const existing = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
1042
- state.pendingChat.set(threadId, [...existing, text]);
1043
- }
1044
- function dequeuePendingChat(state, threadId) {
1045
- var _a;
1046
- const pending = (_a = state.pendingChat.get(threadId)) != null ? _a : [];
1047
- state.pendingChat.delete(threadId);
1048
- return pending;
1049
- }
1050
- function hasPendingChat(state, threadId) {
1051
- var _a, _b;
1052
- return ((_b = (_a = state.pendingChat.get(threadId)) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
1053
- }
1054
-
1055
- // src/runtime/message-controller.ts
1056
- var MessageController = class {
1057
- constructor(config) {
1058
- this.config = config;
1059
- }
1060
- inbound(threadId, msgs) {
1061
- const backendState = this.config.backendStateRef.current;
1062
- if (!msgs) return;
1063
- if (hasPendingChat(backendState, threadId)) {
1064
- return;
1065
- }
1066
- const threadMessages = [];
1067
- for (const msg of msgs) {
1068
- const threadMessage = toInboundMessage(msg);
1069
- if (threadMessage) {
1070
- threadMessages.push(threadMessage);
1071
- }
1072
- }
1073
- this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
1074
- }
1075
- async outbound(message, threadId) {
1076
- var _a, _b, _c;
1077
- const backendState = this.config.backendStateRef.current;
1078
- const text = message.content.filter(
1079
- (part) => part.type === "text"
1080
- ).map(
1081
- (part) => part.text
1082
- ).join("\n");
1083
- if (!text) return;
1084
- const threadState = this.getThreadContextApi();
1085
- const existingMessages = threadState.getThreadMessages(threadId);
1086
- const userMessage = {
1087
- role: "user",
1088
- content: [{ type: "text", text }],
1089
- createdAt: /* @__PURE__ */ new Date()
1090
- };
1091
- threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
1092
- threadState.updateThreadMetadata(threadId, {
1093
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1094
- });
1095
- if (!isThreadReady(backendState, threadId)) {
1096
- this.markRunning(threadId, true);
1097
- enqueuePendingChat(backendState, threadId, text);
1098
- return;
1099
- }
1100
- const backendThreadId = resolveThreadId(backendState, threadId);
1101
- const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1102
- try {
1103
- this.markRunning(threadId, true);
1104
- const response = publicKey ? await this.config.backendApiRef.current.postChatMessage(
1105
- backendThreadId,
1106
- text,
1107
- publicKey
1108
- ) : await this.config.backendApiRef.current.postChatMessage(
1109
- backendThreadId,
1110
- text
1111
- );
1112
- if (response == null ? void 0 : response.messages) {
1113
- this.inbound(threadId, response.messages);
1114
- }
1115
- if (((_c = response == null ? void 0 : response.system_events) == null ? void 0 : _c.length) && this.config.onSyncEvents) {
1116
- this.config.onSyncEvents(backendThreadId, response.system_events);
1117
- }
1118
- if (response == null ? void 0 : response.is_processing) {
1119
- this.config.polling.start(threadId);
1120
- } else if (!this.config.polling.isPolling(threadId)) {
1121
- this.markRunning(threadId, false);
1122
- }
1123
- } catch (error) {
1124
- console.error("Failed to send message:", error);
1125
- this.markRunning(threadId, false);
1126
- }
1127
- }
1128
- async flushPendingChat(threadId) {
1129
- var _a, _b;
1130
- const backendState = this.config.backendStateRef.current;
1131
- const pending = dequeuePendingChat(backendState, threadId);
1132
- if (!pending.length) return;
1133
- const backendThreadId = resolveThreadId(backendState, threadId);
1134
- const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
1135
- for (const text of pending) {
1136
- try {
1137
- if (publicKey) {
1138
- await this.config.backendApiRef.current.postChatMessage(
1139
- backendThreadId,
1140
- text,
1141
- publicKey
1142
- );
1143
- } else {
1144
- await this.config.backendApiRef.current.postChatMessage(
1145
- backendThreadId,
1146
- text
1147
- );
1148
- }
1149
- } catch (error) {
1150
- console.error("Failed to send queued message:", error);
1151
- }
1152
- }
1153
- this.config.polling.start(threadId);
1154
- }
1155
- async cancel(threadId) {
1156
- var _a;
1157
- const backendState = this.config.backendStateRef.current;
1158
- if (!isThreadReady(backendState, threadId)) return;
1159
- this.config.polling.stop(threadId);
1160
- const backendThreadId = resolveThreadId(backendState, threadId);
1161
- try {
1162
- const response = await this.config.backendApiRef.current.postInterrupt(backendThreadId);
1163
- if (response == null ? void 0 : response.messages) {
1164
- this.inbound(threadId, response.messages);
1165
- }
1166
- if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1167
- this.config.onSyncEvents(backendThreadId, response.system_events);
1168
- }
1169
- this.markRunning(threadId, false);
1170
- } catch (error) {
1171
- console.error("Failed to cancel:", error);
1172
- }
1173
- }
1174
- markRunning(threadId, running) {
1175
- var _a, _b;
1176
- setThreadRunning(this.config.backendStateRef.current, threadId, running);
1177
- if (this.config.threadContextRef.current.currentThreadId === threadId) {
1178
- (_b = (_a = this.config).setGlobalIsRunning) == null ? void 0 : _b.call(_a, running);
1179
- }
1180
- }
1181
- getThreadContextApi() {
1182
- const { getThreadMessages, setThreadMessages, updateThreadMetadata } = this.config.threadContextRef.current;
1183
- return { getThreadMessages, setThreadMessages, updateThreadMetadata };
1184
- }
1185
- };
1186
-
1187
- // src/runtime/polling-controller.ts
1188
- var PollingController = class {
1189
- constructor(config) {
1190
- this.config = config;
1191
- this.intervals = /* @__PURE__ */ new Map();
1192
- var _a;
1193
- this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
1194
- }
1195
- start(threadId) {
1196
- var _a, _b;
1197
- const backendState = this.config.backendStateRef.current;
1198
- if (!isThreadReady(backendState, threadId)) return;
1199
- if (this.intervals.has(threadId)) return;
1200
- const backendThreadId = resolveThreadId(backendState, threadId);
1201
- setThreadRunning(backendState, threadId, true);
1202
- const tick = async () => {
1203
- var _a2, _b2;
1204
- if (!this.intervals.has(threadId)) return;
1205
- try {
1206
- console.log(
1207
- "[PollingController] Fetching state for threadId:",
1208
- threadId
1209
- );
1210
- const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
1211
- const state = await this.config.backendApiRef.current.fetchState(
1212
- backendThreadId,
1213
- userState
1214
- );
1215
- if (!this.intervals.has(threadId)) return;
1216
- this.handleState(threadId, state);
1217
- } catch (error) {
1218
- console.error("Polling error:", error);
1219
- this.stop(threadId);
1220
- }
1221
- };
1222
- const intervalId = setInterval(tick, this.intervalMs);
1223
- this.intervals.set(threadId, intervalId);
1224
- (_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
1225
- }
1226
- stop(threadId) {
1227
- var _a, _b;
1228
- const intervalId = this.intervals.get(threadId);
1229
- if (intervalId) {
1230
- clearInterval(intervalId);
1231
- this.intervals.delete(threadId);
1232
- }
1233
- setThreadRunning(this.config.backendStateRef.current, threadId, false);
1234
- (_b = (_a = this.config).onStop) == null ? void 0 : _b.call(_a, threadId);
1235
- }
1236
- isPolling(threadId) {
1237
- return this.intervals.has(threadId);
1238
- }
1239
- stopAll() {
1240
- for (const threadId of this.intervals.keys()) {
1241
- this.stop(threadId);
1242
- }
1243
- }
1244
- handleState(threadId, state) {
1245
- var _a;
1246
- if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1247
- const backendState = this.config.backendStateRef.current;
1248
- const sessionId = resolveThreadId(backendState, threadId);
1249
- this.config.onSyncEvents(sessionId, state.system_events);
1250
- }
1251
- this.config.applyMessages(threadId, state.messages);
1252
- if (!state.is_processing) {
1253
- this.stop(threadId);
1254
- }
1255
- }
1256
- };
1257
-
1258
- // src/runtime/orchestrator.ts
1259
- function useRuntimeOrchestrator(backendApi, options) {
1260
- const threadContext = useThreadContext();
1261
- const threadContextRef = useRef4(threadContext);
1262
- threadContextRef.current = threadContext;
1263
- const backendApiRef = useRef4(backendApi);
1264
- backendApiRef.current = backendApi;
1265
- const backendStateRef = useRef4(createBackendState());
1266
- const [isRunning, setIsRunning] = useState4(false);
1267
- const messageControllerRef = useRef4(null);
1268
- const pollingRef = useRef4(null);
1269
- const pendingFetches = useRef4(/* @__PURE__ */ new Set());
1270
- if (!pollingRef.current) {
1271
- pollingRef.current = new PollingController({
1272
- backendApiRef,
1273
- backendStateRef,
1274
- applyMessages: (threadId, msgs) => {
1275
- var _a;
1276
- (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
1277
- },
1278
- onSyncEvents: options == null ? void 0 : options.onSyncEvents,
1279
- getUserState: options == null ? void 0 : options.getUserState,
1280
- onStart: (threadId) => {
1281
- if (threadContextRef.current.currentThreadId === threadId) {
1282
- setIsRunning(true);
1283
- }
1284
- },
1285
- onStop: (threadId) => {
1286
- if (threadContextRef.current.currentThreadId === threadId) {
1287
- setIsRunning(false);
1288
- }
1289
- }
1290
- });
1291
- }
1292
- if (!messageControllerRef.current) {
1293
- messageControllerRef.current = new MessageController({
1294
- backendApiRef,
1295
- backendStateRef,
1296
- threadContextRef,
1297
- polling: pollingRef.current,
1298
- setGlobalIsRunning: setIsRunning,
1299
- getPublicKey: options == null ? void 0 : options.getPublicKey,
1300
- onSyncEvents: options == null ? void 0 : options.onSyncEvents
1301
- });
1302
- }
1303
- const ensureInitialState = useCallback4(async (threadId) => {
1304
- var _a, _b, _c, _d;
1305
- const backendState = backendStateRef.current;
1306
- if (shouldSkipInitialFetch(backendState, threadId)) {
1307
- clearSkipInitialFetch(backendState, threadId);
1308
- if (threadContextRef.current.currentThreadId === threadId) {
1309
- setIsRunning(false);
1310
- }
1311
- return;
1312
- }
1313
- if (!isThreadReady(backendState, threadId)) {
1314
- if (threadContextRef.current.currentThreadId === threadId) {
1315
- setIsRunning(false);
1316
- }
1317
- return;
1318
- }
1319
- if (pendingFetches.current.has(threadId)) return;
1320
- const backendThreadId = resolveThreadId(backendState, threadId);
1321
- pendingFetches.current.add(threadId);
1322
- try {
1323
- const userState = (_a = options == null ? void 0 : options.getUserState) == null ? void 0 : _a.call(options);
1324
- const state = await backendApiRef.current.fetchState(
1325
- backendThreadId,
1326
- userState
1327
- );
1328
- (_b = messageControllerRef.current) == null ? void 0 : _b.inbound(threadId, state.messages);
1329
- if (((_c = state.system_events) == null ? void 0 : _c.length) && (options == null ? void 0 : options.onSyncEvents)) {
1330
- options.onSyncEvents(backendThreadId, state.system_events);
1331
- }
1332
- if (threadContextRef.current.currentThreadId === threadId) {
1333
- if (state.is_processing) {
1334
- setIsRunning(true);
1335
- (_d = pollingRef.current) == null ? void 0 : _d.start(threadId);
1336
- } else {
1337
- setIsRunning(false);
1338
- }
1339
- }
1340
- } catch (error) {
1341
- console.error("Failed to fetch initial state:", error);
1342
- if (threadContextRef.current.currentThreadId === threadId) {
1343
- setIsRunning(false);
1344
- }
1345
- } finally {
1346
- pendingFetches.current.delete(threadId);
1347
- }
1348
- }, []);
1349
- return {
1350
- backendStateRef,
1351
- polling: pollingRef.current,
1352
- messageController: messageControllerRef.current,
1353
- isRunning,
1354
- setIsRunning,
1355
- ensureInitialState,
1356
- backendApiRef
1357
- };
1358
- }
1359
-
1360
- // src/runtime/threadlist-adapter.ts
1361
- var sortByLastActiveDesc = ([, metaA], [, metaB]) => {
1362
- const tsA = parseTimestamp(metaA.lastActiveAt);
1363
- const tsB = parseTimestamp(metaB.lastActiveAt);
1364
- return tsB - tsA;
1365
- };
1366
- function buildThreadLists(threadMetadata) {
1367
- const entries = Array.from(threadMetadata.entries()).filter(
1368
- ([, meta]) => !isPlaceholderTitle(meta.title)
1369
- );
1370
- const regularThreads = entries.filter(([, meta]) => meta.status !== "archived").sort(sortByLastActiveDesc).map(
1371
- ([id, meta]) => ({
1372
- id,
1373
- title: meta.title || "New Chat",
1374
- status: "regular"
1375
- })
1376
- );
1377
- const archivedThreads = entries.filter(([, meta]) => meta.status === "archived").sort(sortByLastActiveDesc).map(
1378
- ([id, meta]) => ({
1379
- id,
1380
- title: meta.title || "New Chat",
1381
- status: "archived"
1382
- })
1383
- );
1384
- return { regularThreads, archivedThreads };
1385
- }
1386
- function buildThreadListAdapter({
1387
- backendStateRef,
1388
- backendApiRef,
1389
- threadContext,
1390
- currentThreadIdRef,
1391
- polling,
1392
- userAddress,
1393
- setIsRunning
1394
- }) {
1395
- const backendState = backendStateRef.current;
1396
- const { regularThreads, archivedThreads } = buildThreadLists(
1397
- threadContext.allThreadsMetadata
1398
- );
1399
- const preparePendingThread = (threadId) => {
1400
- const previousPendingId = backendState.creatingThreadId;
1401
- if (previousPendingId && previousPendingId !== threadId) {
1402
- threadContext.setThreadMetadata((prev) => {
1403
- const next = new Map(prev);
1404
- next.delete(previousPendingId);
1405
- return next;
1406
- });
1407
- threadContext.setThreads((prev) => {
1408
- const next = new Map(prev);
1409
- next.delete(previousPendingId);
1410
- return next;
1411
- });
1412
- backendState.pendingChat.delete(previousPendingId);
1413
- backendState.skipInitialFetch.delete(previousPendingId);
1414
- }
1415
- backendState.creatingThreadId = threadId;
1416
- backendState.pendingChat.delete(threadId);
1417
- threadContext.setThreadMetadata(
1418
- (prev) => new Map(prev).set(threadId, {
1419
- title: "New Chat",
1420
- status: "pending",
1421
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1422
- })
1423
- );
1424
- threadContext.setThreadMessages(threadId, []);
1425
- threadContext.setCurrentThreadId(threadId);
1426
- setIsRunning(false);
1427
- threadContext.bumpThreadViewKey();
1428
- };
1429
- const findPendingThreadId = () => {
1430
- if (backendState.creatingThreadId) return backendState.creatingThreadId;
1431
- for (const [id, meta] of threadContext.allThreadsMetadata.entries()) {
1432
- if (meta.status === "pending") return id;
1433
- }
1434
- return null;
1435
- };
1436
- return {
1437
- threadId: threadContext.currentThreadId,
1438
- threads: regularThreads,
1439
- archivedThreads,
1440
- onSwitchToNewThread: async () => {
1441
- var _a;
1442
- const pendingId = findPendingThreadId();
1443
- if (pendingId) {
1444
- preparePendingThread(pendingId);
1445
- return;
1446
- }
1447
- if (backendState.createThreadPromise) {
1448
- preparePendingThread(
1449
- (_a = backendState.creatingThreadId) != null ? _a : crypto.randomUUID()
1450
- );
1451
- return;
1452
- }
1453
- const threadId = crypto.randomUUID();
1454
- preparePendingThread(threadId);
1455
- const createPromise = backendApiRef.current.createThread(threadId, userAddress).then(async (newThread) => {
1456
- var _a2;
1457
- const uiThreadId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
1458
- const backendId = newThread.session_id;
1459
- if (uiThreadId !== backendId) {
1460
- console.warn("[aomi][thread] backend id mismatch", {
1461
- uiThreadId,
1462
- backendId
1463
- });
1464
- }
1465
- markSkipInitialFetch(backendState, uiThreadId);
1466
- threadContext.setThreadMetadata((prev) => {
1467
- var _a3, _b;
1468
- const next = new Map(prev);
1469
- const existing = next.get(uiThreadId);
1470
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1471
- next.set(uiThreadId, {
1472
- title: (_a3 = existing == null ? void 0 : existing.title) != null ? _a3 : "New Chat",
1473
- status: nextStatus,
1474
- lastActiveAt: (_b = existing == null ? void 0 : existing.lastActiveAt) != null ? _b : (/* @__PURE__ */ new Date()).toISOString()
1475
- });
1476
- return next;
1477
- });
1478
- if (backendState.creatingThreadId === uiThreadId) {
1479
- backendState.creatingThreadId = null;
1480
- }
1481
- const pendingMessages = backendState.pendingChat.get(uiThreadId);
1482
- if (pendingMessages == null ? void 0 : pendingMessages.length) {
1483
- backendState.pendingChat.delete(uiThreadId);
1484
- for (const text of pendingMessages) {
1485
- try {
1486
- await backendApiRef.current.postChatMessage(backendId, text);
1487
- } catch (error) {
1488
- console.error("Failed to send queued message:", error);
1489
- }
1490
- }
1491
- if (currentThreadIdRef.current === uiThreadId) {
1492
- polling == null ? void 0 : polling.start(uiThreadId);
1493
- }
1494
- }
1495
- }).catch((error) => {
1496
- var _a2;
1497
- console.error("Failed to create new thread:", error);
1498
- const failedId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
1499
- threadContext.setThreadMetadata((prev) => {
1500
- const next = new Map(prev);
1501
- next.delete(failedId);
1502
- return next;
1503
- });
1504
- threadContext.setThreads((prev) => {
1505
- const next = new Map(prev);
1506
- next.delete(failedId);
1507
- return next;
1508
- });
1509
- if (backendState.creatingThreadId === failedId) {
1510
- backendState.creatingThreadId = null;
1511
- }
1512
- }).finally(() => {
1513
- backendState.createThreadPromise = null;
1514
- });
1515
- backendState.createThreadPromise = createPromise;
1516
- },
1517
- onSwitchToThread: (threadId) => {
1518
- threadContext.setCurrentThreadId(threadId);
1519
- },
1520
- onRename: async (threadId, newTitle) => {
1521
- var _a, _b;
1522
- const previousTitle = (_b = (_a = threadContext.getThreadMetadata(threadId)) == null ? void 0 : _a.title) != null ? _b : "";
1523
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1524
- threadContext.updateThreadMetadata(threadId, {
1525
- title: normalizedTitle
1526
- });
1527
- try {
1528
- await backendApiRef.current.renameThread(threadId, newTitle);
1529
- } catch (error) {
1530
- console.error("Failed to rename thread:", error);
1531
- threadContext.updateThreadMetadata(threadId, {
1532
- title: previousTitle
1533
- });
1534
- }
1535
- },
1536
- onArchive: async (threadId) => {
1537
- threadContext.updateThreadMetadata(threadId, { status: "archived" });
1538
- try {
1539
- await backendApiRef.current.archiveThread(threadId);
1540
- } catch (error) {
1541
- console.error("Failed to archive thread:", error);
1542
- threadContext.updateThreadMetadata(threadId, { status: "regular" });
1543
- }
1544
- },
1545
- onUnarchive: async (threadId) => {
1546
- threadContext.updateThreadMetadata(threadId, { status: "regular" });
1547
- try {
1548
- await backendApiRef.current.unarchiveThread(threadId);
1549
- } catch (error) {
1550
- console.error("Failed to unarchive thread:", error);
1551
- threadContext.updateThreadMetadata(threadId, { status: "archived" });
1552
- }
1553
- },
1554
- onDelete: async (threadId) => {
1555
- try {
1556
- await backendApiRef.current.deleteThread(threadId);
1557
- threadContext.setThreadMetadata((prev) => {
1558
- const next = new Map(prev);
1559
- next.delete(threadId);
1560
- return next;
1561
- });
1562
- threadContext.setThreads((prev) => {
1563
- const next = new Map(prev);
1564
- next.delete(threadId);
1565
- return next;
1566
- });
1567
- backendState.pendingChat.delete(threadId);
1568
- backendState.skipInitialFetch.delete(threadId);
1569
- backendState.runningThreads.delete(threadId);
1570
- if (backendState.creatingThreadId === threadId) {
1571
- backendState.creatingThreadId = null;
1572
- }
1573
- if (threadContext.currentThreadId === threadId) {
1574
- const firstRegularThread = Array.from(
1575
- threadContext.allThreadsMetadata.entries()
1576
- ).find(([id, meta]) => meta.status === "regular" && id !== threadId);
1577
- if (firstRegularThread) {
1578
- threadContext.setCurrentThreadId(firstRegularThread[0]);
1579
- } else {
1580
- const defaultId = "default-session";
1581
- threadContext.setThreadMetadata(
1582
- (prev) => new Map(prev).set(defaultId, {
1583
- title: "New Chat",
1584
- status: "regular",
1585
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1586
- })
1587
- );
1588
- threadContext.setThreadMessages(defaultId, []);
1589
- threadContext.setCurrentThreadId(defaultId);
1590
- }
1591
- }
1592
- } catch (error) {
1593
- console.error("Failed to delete thread:", error);
1594
- throw error;
1595
- }
1596
- }
1597
- };
1598
- }
1599
-
1600
- // src/interface.tsx
1601
- import { createContext as createContext5, useContext as useContext5 } from "react";
1602
- var AomiRuntimeContext = createContext5(null);
1603
- var AomiRuntimeApiProvider = AomiRuntimeContext.Provider;
1604
- function useAomiRuntime() {
1605
- const context = useContext5(AomiRuntimeContext);
1606
- if (!context) {
1607
- throw new Error(
1608
- "useAomiRuntime must be used within AomiRuntimeProvider. Wrap your app with <AomiRuntimeProvider>...</AomiRuntimeProvider>"
1609
- );
1610
- }
1611
- return context;
1612
- }
1613
-
1614
- // src/runtime/core.tsx
1615
- import { jsx as jsx5 } from "react/jsx-runtime";
1616
- function AomiRuntimeCore({
1617
- children,
1618
- backendApi
1619
- }) {
1620
- const threadContext = useThreadContext();
1621
- const eventContext = useEventContext();
1622
- const notificationContext = useNotification();
1623
- const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
1624
- const { user, onUserStateChange, getUserState } = useUser();
1625
- const {
1626
- backendStateRef,
1627
- polling,
1628
- messageController,
1629
- isRunning,
1630
- setIsRunning,
1631
- ensureInitialState,
1632
- backendApiRef
1633
- } = useRuntimeOrchestrator(backendApi, {
1634
- onSyncEvents: dispatchSystemEvents,
1635
- getPublicKey: () => getUserState().address,
1636
- getUserState
1637
- });
1638
- useEffect2(() => {
1639
- const unsubscribe = onUserStateChange(async (newUser) => {
1640
- const sessionId = threadContext.currentThreadId;
1641
- const message = JSON.stringify({
1642
- type: "wallet:state_changed",
1643
- payload: {
1644
- address: newUser.address,
1645
- chainId: newUser.chainId,
1646
- isConnected: newUser.isConnected,
1647
- ensName: newUser.ensName
1648
- }
1649
- });
1650
- await backendApiRef.current.postSystemMessage(sessionId, message);
1651
- });
1652
- return unsubscribe;
1653
- }, [onUserStateChange, backendApiRef, threadContext.currentThreadId]);
1654
- const threadContextRef = useRef5(threadContext);
1655
- threadContextRef.current = threadContext;
1656
- const currentThreadIdRef = useRef5(threadContext.currentThreadId);
1657
- useEffect2(() => {
1658
- currentThreadIdRef.current = threadContext.currentThreadId;
1659
- }, [threadContext.currentThreadId]);
1660
- useEffect2(() => {
1661
- void ensureInitialState(threadContext.currentThreadId);
1662
- }, [ensureInitialState, threadContext.currentThreadId]);
1663
- useEffect2(() => {
1664
- const threadId = threadContext.currentThreadId;
1665
- setIsRunning(isThreadRunning(backendStateRef.current, threadId));
1666
- }, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
1667
- const currentMessages = threadContext.getThreadMessages(
1668
- threadContext.currentThreadId
1669
- );
1670
- const resolvedSessionId = useMemo2(
1671
- () => resolveThreadId(backendStateRef.current, threadContext.currentThreadId),
1672
- [
1673
- backendStateRef,
1674
- threadContext.currentThreadId,
1675
- threadContext.allThreadsMetadata
1676
- ]
1677
- );
1678
- useEffect2(() => {
1679
- const userAddress = user.address;
1680
- if (!userAddress) return;
1681
- const fetchThreadList = async () => {
1682
- var _a, _b;
1683
- try {
1684
- const threadList = await backendApiRef.current.fetchThreads(userAddress);
1685
- const currentContext = threadContextRef.current;
1686
- const newMetadata = new Map(currentContext.allThreadsMetadata);
1687
- let maxChatNum = currentContext.threadCnt;
1688
- for (const thread of threadList) {
1689
- const rawTitle = (_a = thread.title) != null ? _a : "";
1690
- const title = isPlaceholderTitle(rawTitle) ? "" : rawTitle;
1691
- const lastActive = ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
1692
- newMetadata.set(thread.session_id, {
1693
- title,
1694
- status: thread.is_archived ? "archived" : "regular",
1695
- lastActiveAt: lastActive
1696
- });
1697
- const match = title.match(/^Chat (\d+)$/);
1698
- if (match) {
1699
- const num = parseInt(match[1], 10);
1700
- if (num > maxChatNum) {
1701
- maxChatNum = num;
1702
- }
1703
- }
1704
- }
1705
- currentContext.setThreadMetadata(newMetadata);
1706
- if (maxChatNum > currentContext.threadCnt) {
1707
- currentContext.setThreadCnt(maxChatNum);
1708
- }
1709
- } catch (error) {
1710
- console.error("Failed to fetch thread list:", error);
1711
- }
1712
- };
1713
- void fetchThreadList();
1714
- }, [user.address, backendApiRef]);
1715
- const threadListAdapter = useMemo2(
1716
- () => buildThreadListAdapter({
1717
- backendStateRef,
1718
- backendApiRef,
1719
- threadContext,
1720
- currentThreadIdRef,
1721
- polling,
1722
- userAddress: user.address,
1723
- setIsRunning
1724
- }),
1725
- [
1726
- backendApiRef,
1727
- polling,
1728
- user.address,
1729
- backendStateRef,
1730
- setIsRunning,
1731
- threadContext,
1732
- threadContext.currentThreadId,
1733
- threadContext.allThreadsMetadata
1734
- ]
1735
- );
1736
- useEffect2(() => {
1737
- const backendState = backendStateRef.current;
1738
- const currentSessionId = threadContext.currentThreadId;
1739
- if (process.env.NODE_ENV !== "production") {
1740
- console.debug("[aomi][sse] subscribe", {
1741
- currentSessionId,
1742
- resolvedSessionId,
1743
- hasMapping: currentSessionId !== resolvedSessionId
1744
- });
1745
- }
1746
- const unsubscribe = backendApiRef.current.subscribeSSE(
1747
- resolvedSessionId,
1748
- (event) => {
1749
- const eventType = event.type;
1750
- const sessionId = event.session_id;
1751
- if (eventType === "title_changed") {
1752
- const newTitle = event.new_title;
1753
- const targetThreadId = resolveThreadId(backendState, sessionId);
1754
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1755
- if (process.env.NODE_ENV !== "production") {
1756
- console.debug("[aomi][sse] title_changed", {
1757
- sessionId,
1758
- newTitle,
1759
- normalizedTitle,
1760
- currentThreadId: threadContextRef.current.currentThreadId,
1761
- targetThreadId,
1762
- hasMapping: sessionId !== targetThreadId,
1763
- creatingThreadId: backendState.creatingThreadId
1764
- });
1765
- }
1766
- threadContextRef.current.setThreadMetadata((prev) => {
1767
- var _a;
1768
- const next = new Map(prev);
1769
- const existing = next.get(targetThreadId);
1770
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1771
- next.set(targetThreadId, {
1772
- title: normalizedTitle,
1773
- status: nextStatus,
1774
- lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString()
1775
- });
1776
- return next;
1777
- });
1778
- if (!isPlaceholderTitle(newTitle) && backendState.creatingThreadId === targetThreadId) {
1779
- backendState.creatingThreadId = null;
1780
- }
1781
- }
1782
- }
1783
- );
1784
- return () => {
1785
- unsubscribe == null ? void 0 : unsubscribe();
1786
- };
1787
- }, [
1788
- backendApiRef,
1789
- backendStateRef,
1790
- threadContext.currentThreadId,
1791
- resolvedSessionId
1792
- ]);
1793
- useEffect2(() => {
1794
- const threadId = threadContext.currentThreadId;
1795
- if (!isThreadReady(backendStateRef.current, threadId)) return;
1796
- void messageController.flushPendingChat(threadId);
1797
- }, [messageController, backendStateRef, threadContext.currentThreadId]);
1798
- useEffect2(() => {
1799
- const showToolNotification = (eventType) => (event) => {
1800
- const payload = event.payload;
1801
- const toolName = typeof (payload == null ? void 0 : payload.tool_name) === "string" ? payload.tool_name : void 0;
1802
- const title = toolName ? `${eventType === "tool_update" ? "Tool update" : "Tool complete"}: ${toolName}` : eventType === "tool_update" ? "Tool update" : "Tool complete";
1803
- const message = typeof (payload == null ? void 0 : payload.message) === "string" ? payload.message : typeof (payload == null ? void 0 : payload.result) === "string" ? payload.result : void 0;
1804
- notificationContext.showNotification({
1805
- type: "notice",
1806
- title,
1807
- message
1808
- });
1809
- };
1810
- const unsubscribeUpdate = eventContext.subscribe(
1811
- "tool_update",
1812
- showToolNotification("tool_update")
1813
- );
1814
- const unsubscribeComplete = eventContext.subscribe(
1815
- "tool_complete",
1816
- showToolNotification("tool_complete")
1817
- );
1818
- return () => {
1819
- unsubscribeUpdate();
1820
- unsubscribeComplete();
1821
- };
1822
- }, [eventContext, notificationContext]);
1823
- useEffect2(() => {
1824
- const unsubscribe = eventContext.subscribe("system_notice", (event) => {
1825
- const payload = event.payload;
1826
- const message = payload == null ? void 0 : payload.message;
1827
- notificationContext.showNotification({
1828
- type: "notice",
1829
- title: "System notice",
1830
- message
1831
- });
1832
- });
1833
- return unsubscribe;
1834
- }, [eventContext, notificationContext]);
1835
- const runtime = useExternalStoreRuntime({
1836
- messages: currentMessages,
1837
- setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
1838
- isRunning,
1839
- onNew: (message) => messageController.outbound(message, threadContext.currentThreadId),
1840
- onCancel: () => messageController.cancel(threadContext.currentThreadId),
1841
- convertMessage: (msg) => msg,
1842
- adapters: { threadList: threadListAdapter }
1843
- });
1844
- useEffect2(() => {
1845
- return () => {
1846
- polling.stopAll();
1847
- };
1848
- }, [polling]);
1849
- const userContext = useUser();
1850
- const sendMessage = useCallback5(
1851
- async (text) => {
1852
- const appendMessage = {
1853
- role: "user",
1854
- content: [{ type: "text", text }]
1855
- };
1856
- await messageController.outbound(
1857
- appendMessage,
1858
- threadContext.currentThreadId
1859
- );
1860
- },
1861
- [messageController, threadContext.currentThreadId]
1862
- );
1863
- const cancelGeneration = useCallback5(() => {
1864
- messageController.cancel(threadContext.currentThreadId);
1865
- }, [messageController, threadContext.currentThreadId]);
1866
- const getMessages = useCallback5(
1867
- (threadId) => {
1868
- const id = threadId != null ? threadId : threadContext.currentThreadId;
1869
- return threadContext.getThreadMessages(id);
1870
- },
1871
- [threadContext]
1872
- );
1873
- const createThread = useCallback5(async () => {
1874
- await threadListAdapter.onSwitchToNewThread();
1875
- return threadContextRef.current.currentThreadId;
1876
- }, [threadListAdapter]);
1877
- const deleteThread = useCallback5(
1878
- async (threadId) => {
1879
- await threadListAdapter.onDelete(threadId);
1880
- },
1881
- [threadListAdapter]
1882
- );
1883
- const renameThread = useCallback5(
1884
- async (threadId, title) => {
1885
- await threadListAdapter.onRename(threadId, title);
1886
- },
1887
- [threadListAdapter]
1888
- );
1889
- const archiveThread = useCallback5(
1890
- async (threadId) => {
1891
- await threadListAdapter.onArchive(threadId);
1892
- },
1893
- [threadListAdapter]
1894
- );
1895
- const selectThread = useCallback5(
1896
- (threadId) => {
1897
- if (threadContext.allThreadsMetadata.has(threadId)) {
1898
- threadListAdapter.onSwitchToThread(threadId);
1899
- } else {
1900
- void threadListAdapter.onSwitchToNewThread();
1901
- }
1902
- },
1903
- [threadContext.allThreadsMetadata, threadListAdapter]
1904
- );
1905
- const aomiRuntimeApi = useMemo2(
1906
- () => ({
1907
- // User API
1908
- user: userContext.user,
1909
- getUserState: userContext.getUserState,
1910
- setUser: userContext.setUser,
1911
- onUserStateChange: userContext.onUserStateChange,
1912
- // Thread API
1913
- currentThreadId: threadContext.currentThreadId,
1914
- threadViewKey: threadContext.threadViewKey,
1915
- threadMetadata: threadContext.allThreadsMetadata,
1916
- getThreadMetadata: threadContext.getThreadMetadata,
1917
- createThread,
1918
- deleteThread,
1919
- renameThread,
1920
- archiveThread,
1921
- selectThread,
1922
- // Chat API
1923
- isRunning,
1924
- getMessages,
1925
- sendMessage,
1926
- cancelGeneration,
1927
- // Notification API
1928
- notifications: notificationContext.notifications,
1929
- showNotification: notificationContext.showNotification,
1930
- dismissNotification: notificationContext.dismissNotification,
1931
- clearAllNotifications: notificationContext.clearAll,
1932
- // Event API
1933
- subscribe: eventContext.subscribe,
1934
- sendSystemCommand: eventContext.sendOutboundSystem,
1935
- sseStatus: eventContext.sseStatus
1936
- }),
1937
- [
1938
- userContext,
1939
- threadContext.currentThreadId,
1940
- threadContext.threadViewKey,
1941
- threadContext.allThreadsMetadata,
1942
- threadContext.getThreadMetadata,
1943
- createThread,
1944
- deleteThread,
1945
- renameThread,
1946
- archiveThread,
1947
- selectThread,
1948
- isRunning,
1949
- getMessages,
1950
- sendMessage,
1951
- cancelGeneration,
1952
- notificationContext,
1953
- eventContext
1954
- ]
1955
- );
1956
- return /* @__PURE__ */ jsx5(AomiRuntimeApiProvider, { value: aomiRuntimeApi, children: /* @__PURE__ */ jsx5(AssistantRuntimeProvider, { runtime, children }) });
1957
- }
1958
-
1959
- // src/runtime/aomi-runtime.tsx
1960
- import { jsx as jsx6 } from "react/jsx-runtime";
1961
- function AomiRuntimeProvider({
1962
- children,
1963
- backendUrl = "http://localhost:8080"
1964
- }) {
1965
- const backendApi = useMemo3(() => new BackendApi(backendUrl), [backendUrl]);
1966
- return /* @__PURE__ */ jsx6(ThreadContextProvider, { children: /* @__PURE__ */ jsx6(NotificationContextProvider, { children: /* @__PURE__ */ jsx6(UserContextProvider, { children: /* @__PURE__ */ jsx6(AomiRuntimeInner, { backendApi, children }) }) }) });
1967
- }
1968
- function AomiRuntimeInner({
1969
- children,
1970
- backendApi
1971
- }) {
1972
- const threadContext = useThreadContext();
1973
- return /* @__PURE__ */ jsx6(
1974
- EventContextProvider,
1975
- {
1976
- backendApi,
1977
- sessionId: threadContext.currentThreadId,
1978
- children: /* @__PURE__ */ jsx6(AomiRuntimeCore, { backendApi, children })
1979
- }
1980
- );
1981
- }
1982
-
1983
- // src/handlers/wallet-handler.ts
1984
- import { useCallback as useCallback6, useEffect as useEffect3, useState as useState5 } from "react";
1985
- function useWalletHandler({
1986
- sessionId,
1987
- onTxRequest
1988
- }) {
1989
- const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
1990
- const { setUser, getUserState } = useUser();
1991
- const [pendingTxRequests, setPendingTxRequests] = useState5(
1992
- []
1993
- );
1994
- useEffect3(() => {
1995
- const unsubscribe = subscribe2(
1996
- "wallet_tx_request",
1997
- (event) => {
1998
- const request = event.payload;
1999
- setPendingTxRequests((prev) => [...prev, request]);
2000
- onTxRequest == null ? void 0 : onTxRequest(request);
2001
- }
2002
- );
2003
- return unsubscribe;
2004
- }, [subscribe2, onTxRequest]);
2005
- useEffect3(() => {
2006
- const unsubscribe = subscribe2(
2007
- "user_state_request",
2008
- (event) => {
2009
- sendOutbound({
2010
- type: "user_state_response",
2011
- sessionId,
2012
- payload: getUserState()
2013
- });
2014
- }
2015
- );
2016
- return unsubscribe;
2017
- }, [subscribe2, onTxRequest]);
2018
- const sendTxComplete = useCallback6(
2019
- (tx) => {
2020
- sendOutbound({
2021
- type: "wallet:tx_complete",
2022
- sessionId,
2023
- payload: tx
2024
- });
2025
- },
2026
- [sendOutbound, sessionId]
2027
- );
2028
- const sendConnectionChange = useCallback6(
2029
- (status, address, chainId) => {
2030
- if (status === "connected") {
2031
- setUser({
2032
- isConnected: true,
2033
- address,
2034
- chainId
2035
- });
2036
- } else {
2037
- setUser({
2038
- isConnected: false,
2039
- address: void 0,
2040
- chainId: void 0
2041
- });
2042
- }
2043
- sendOutbound({
2044
- type: status === "connected" ? "wallet:connected" : "wallet:disconnected",
2045
- sessionId,
2046
- payload: { status, address }
2047
- });
2048
- },
2049
- [setUser, sendOutbound, sessionId]
2050
- );
2051
- const clearTxRequest = useCallback6((index) => {
2052
- setPendingTxRequests((prev) => prev.filter((_, i) => i !== index));
2053
- }, []);
2054
- return {
2055
- sendTxComplete,
2056
- sendConnectionChange,
2057
- pendingTxRequests,
2058
- clearTxRequest
2059
- };
2060
- }
2061
-
2062
- // src/handlers/notification-handler.ts
2063
- import { useCallback as useCallback7, useEffect as useEffect4, useState as useState6 } from "react";
2064
- var notificationIdCounter2 = 0;
2065
- function generateNotificationId() {
2066
- return `notif-${Date.now()}-${++notificationIdCounter2}`;
2067
- }
2068
- function useNotificationHandler({
2069
- onNotification
2070
- } = {}) {
2071
- const { subscribe: subscribe2 } = useEventContext();
2072
- const [notifications, setNotifications] = useState6([]);
2073
- useEffect4(() => {
2074
- const unsubscribe = subscribe2("notification", (event) => {
2075
- var _a, _b;
2076
- const payload = event.payload;
2077
- const notification = {
2078
- id: generateNotificationId(),
2079
- type: (_a = payload.type) != null ? _a : "notification",
2080
- title: (_b = payload.title) != null ? _b : "Notification",
2081
- body: payload.body,
2082
- handled: false,
2083
- timestamp: event.timestamp,
2084
- sessionId: event.sessionId
2085
- };
2086
- setNotifications((prev) => [notification, ...prev]);
2087
- onNotification == null ? void 0 : onNotification(notification);
2088
- });
2089
- return unsubscribe;
2090
- }, [subscribe2, onNotification]);
2091
- const unhandledCount = notifications.filter((n) => !n.handled).length;
2092
- const markHandled = useCallback7((id) => {
2093
- setNotifications(
2094
- (prev) => prev.map((n) => n.id === id ? __spreadProps(__spreadValues({}, n), { handled: true }) : n)
2095
- );
2096
- }, []);
2097
- return {
2098
- notifications,
2099
- unhandledCount,
2100
- markDone: markHandled
2101
- };
2102
- }
2103
- export {
2104
- AomiRuntimeProvider,
2105
- BackendApi,
2106
- EventContextProvider,
2107
- NotificationContextProvider,
2108
- ThreadContextProvider,
2109
- UserContextProvider,
2110
- cn,
2111
- formatAddress,
2112
- getNetworkName,
2113
- useAomiRuntime,
2114
- useCurrentThreadMessages,
2115
- useCurrentThreadMetadata,
2116
- useEventContext,
2117
- useNotification,
2118
- useNotificationHandler,
2119
- useThreadContext,
2120
- useUser,
2121
- useWalletHandler
2122
- };
2123
- //# sourceMappingURL=index.js.map