@aomi-labs/react 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -38,388 +38,642 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
38
38
  var index_exports = {};
39
39
  __export(index_exports, {
40
40
  AomiRuntimeProvider: () => AomiRuntimeProvider,
41
- AomiRuntimeProviderWithNotifications: () => AomiRuntimeProviderWithNotifications,
42
41
  BackendApi: () => BackendApi,
43
- NotificationProvider: () => NotificationProvider,
44
- RuntimeActionsProvider: () => RuntimeActionsProvider,
42
+ EventContextProvider: () => EventContextProvider,
43
+ NotificationContextProvider: () => NotificationContextProvider,
45
44
  ThreadContextProvider: () => ThreadContextProvider,
46
- WalletSystemMessageEmitter: () => WalletSystemMessageEmitter,
45
+ UserContextProvider: () => UserContextProvider,
47
46
  cn: () => cn,
48
- constructSystemMessage: () => toInboundSystem,
49
- constructThreadMessage: () => toInboundMessage,
50
47
  formatAddress: () => formatAddress,
51
48
  getNetworkName: () => getNetworkName,
52
- normalizeWalletError: () => normalizeWalletError,
53
- pickInjectedProvider: () => pickInjectedProvider,
54
- toHexQuantity: () => toHexQuantity,
49
+ useAomiRuntime: () => useAomiRuntime,
55
50
  useCurrentThreadMessages: () => useCurrentThreadMessages,
56
51
  useCurrentThreadMetadata: () => useCurrentThreadMetadata,
52
+ useEventContext: () => useEventContext,
57
53
  useNotification: () => useNotification,
58
- useRuntimeActions: () => useRuntimeActions,
59
- useThreadContext: () => useThreadContext
54
+ useNotificationHandler: () => useNotificationHandler,
55
+ useThreadContext: () => useThreadContext,
56
+ useUser: () => useUser,
57
+ useWalletHandler: () => useWalletHandler
60
58
  });
61
59
  module.exports = __toCommonJS(index_exports);
62
60
 
63
- // src/api/client.ts
64
- function toQueryString(payload) {
65
- const params = new URLSearchParams();
66
- for (const [key, value] of Object.entries(payload)) {
67
- if (value === void 0 || value === null) continue;
68
- params.set(key, String(value));
69
- }
70
- const qs = params.toString();
71
- return qs ? `?${qs}` : "";
72
- }
73
- async function postState(backendUrl, path, payload) {
74
- const query = toQueryString(payload);
75
- const url = `${backendUrl}${path}${query}`;
76
- console.log("\u{1F535} [postState] URL:", url);
77
- console.log("\u{1F535} [postState] Payload:", payload);
78
- const response = await fetch(url, {
79
- method: "POST"
80
- });
81
- console.log("\u{1F535} [postState] Response status:", response.status);
82
- if (!response.ok) {
83
- console.error("\u{1F534} [postState] Error:", response.status, response.statusText);
84
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
85
- }
86
- const data = await response.json();
87
- console.log("\u{1F7E2} [postState] Success:", data);
88
- return data;
61
+ // src/backend/sse.ts
62
+ function extractSseData(rawEvent) {
63
+ const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
64
+ if (!dataLines.length) return null;
65
+ return dataLines.join("\n");
89
66
  }
90
- var BackendApi = class {
91
- constructor(backendUrl) {
92
- this.backendUrl = backendUrl;
93
- this.connectionStatus = false;
94
- this.eventSource = null;
95
- this.updatesEventSources = /* @__PURE__ */ new Map();
96
- }
97
- async fetchState(sessionId, options) {
98
- console.log("\u{1F535} [fetchState] Called with sessionId:", sessionId);
99
- const url = `${this.backendUrl}/api/state?session_id=${encodeURIComponent(sessionId)}`;
100
- console.log("\u{1F535} [fetchState] URL:", url);
101
- const response = await fetch(url, { signal: options == null ? void 0 : options.signal });
102
- console.log("\u{1F535} [fetchState] Response status:", response.status, response.statusText);
103
- if (!response.ok) {
104
- console.error("\u{1F534} [fetchState] Error:", response.status, response.statusText);
105
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
106
- }
107
- const data = await response.json();
108
- console.log("\u{1F7E2} [fetchState] Success:", data);
109
- return data;
110
- }
111
- async postChatMessage(sessionId, message, publicKey) {
112
- console.log("\u{1F535} [postChatMessage] Called with sessionId:", sessionId, "message:", message);
113
- const result = await postState(this.backendUrl, "/api/chat", {
114
- message,
115
- session_id: sessionId,
116
- public_key: publicKey
117
- });
118
- console.log("\u{1F7E2} [postChatMessage] Success:", result);
119
- return result;
120
- }
121
- async postSystemMessage(sessionId, message) {
122
- console.log("\u{1F535} [postSystemMessage] Called with sessionId:", sessionId, "message:", message);
123
- const result = await postState(this.backendUrl, "/api/system", {
124
- message,
125
- session_id: sessionId
126
- });
127
- console.log("\u{1F7E2} [postSystemMessage] Success:", result);
128
- return result;
129
- }
130
- async postInterrupt(sessionId) {
131
- console.log("\u{1F535} [postInterrupt] Called with sessionId:", sessionId);
132
- const result = await postState(this.backendUrl, "/api/interrupt", {
133
- session_id: sessionId
134
- });
135
- console.log("\u{1F7E2} [postInterrupt] Success:", result);
136
- return result;
137
- }
138
- disconnectSSE() {
139
- if (this.eventSource) {
140
- this.eventSource.close();
141
- this.eventSource = null;
67
+ async function readSseStream(stream, signal, onMessage) {
68
+ const reader = stream.getReader();
69
+ const decoder = new TextDecoder();
70
+ let buffer = "";
71
+ try {
72
+ while (!signal.aborted) {
73
+ const { value, done } = await reader.read();
74
+ if (done) break;
75
+ buffer += decoder.decode(value, { stream: true });
76
+ buffer = buffer.replace(/\r/g, "");
77
+ let separatorIndex = buffer.indexOf("\n\n");
78
+ while (separatorIndex >= 0) {
79
+ const rawEvent = buffer.slice(0, separatorIndex);
80
+ buffer = buffer.slice(separatorIndex + 2);
81
+ const data = extractSseData(rawEvent);
82
+ if (data) {
83
+ onMessage(data);
84
+ }
85
+ separatorIndex = buffer.indexOf("\n\n");
86
+ }
142
87
  }
143
- this.setConnectionStatus(false);
144
- }
145
- setConnectionStatus(on) {
146
- this.connectionStatus = on;
88
+ } finally {
89
+ reader.releaseLock();
147
90
  }
148
- async connectSSE(sessionId, publicKey) {
149
- this.disconnectSSE();
150
- try {
151
- const url = new URL(`${this.backendUrl}/api/chat/stream`);
152
- url.searchParams.set("session_id", sessionId);
153
- if (publicKey) {
154
- url.searchParams.set("public_key", publicKey);
91
+ }
92
+ function createSseSubscriber({
93
+ backendUrl,
94
+ getHeaders,
95
+ shouldLog = process.env.NODE_ENV !== "production"
96
+ }) {
97
+ const subscriptions = /* @__PURE__ */ new Map();
98
+ const subscribe2 = (sessionId, onUpdate, onError) => {
99
+ const existing = subscriptions.get(sessionId);
100
+ const listener = { onUpdate, onError };
101
+ if (existing) {
102
+ existing.listeners.add(listener);
103
+ if (shouldLog) {
104
+ console.debug("[aomi][sse] listener added", {
105
+ sessionId,
106
+ listeners: existing.listeners.size
107
+ });
155
108
  }
156
- this.eventSource = new EventSource(url.toString());
157
- this.eventSource.onopen = () => {
158
- console.log("\u{1F310} SSE connection opened to:", url.toString());
159
- this.setConnectionStatus(true);
160
- };
161
- this.eventSource.onmessage = (event) => {
162
- try {
163
- JSON.parse(event.data);
164
- } catch (error) {
165
- console.error("Failed to parse SSE data:", error);
109
+ return () => {
110
+ existing.listeners.delete(listener);
111
+ if (shouldLog) {
112
+ console.debug("[aomi][sse] listener removed", {
113
+ sessionId,
114
+ listeners: existing.listeners.size
115
+ });
116
+ }
117
+ if (existing.listeners.size === 0) {
118
+ existing.stop("unsubscribe");
119
+ if (subscriptions.get(sessionId) === existing) {
120
+ subscriptions.delete(sessionId);
121
+ }
166
122
  }
167
123
  };
168
- this.eventSource.onerror = (error) => {
169
- console.error("SSE connection error:", error);
170
- };
171
- } catch (error) {
172
- console.error("Failed to establish SSE connection:", error);
173
- this.handleConnectionError(sessionId, publicKey);
174
- }
175
- }
176
- handleConnectionError(sessionId, publicKey) {
177
- this.setConnectionStatus(false);
178
- let attempt = 0;
179
- const total = 3;
180
- if (attempt < total) {
181
- attempt++;
182
- console.log(`Attempting to reconnect (${attempt}/${total})...`);
183
- setTimeout(() => {
184
- this.connectSSE(sessionId, publicKey);
185
- }, 100);
186
- } else {
187
- console.error("Max reconnection attempts reached");
188
- this.setConnectionStatus(false);
189
- }
190
- }
191
- subscribeToUpdatesInternal(sessionId, onUpdate, onError, logLabel) {
192
- const updatesUrl = new URL("/api/updates", this.backendUrl);
193
- updatesUrl.searchParams.set("session_id", sessionId);
194
- const updatesUrlString = updatesUrl.toString();
195
- const existing = this.updatesEventSources.get(sessionId);
196
- if (existing) {
197
- existing.cleanup();
198
- this.updatesEventSources.delete(sessionId);
199
124
  }
200
125
  const subscription = {
201
- eventSource: null,
126
+ abortController: null,
202
127
  retries: 0,
203
128
  retryTimer: null,
204
129
  stopped: false,
205
- cleanup: () => {
130
+ listeners: /* @__PURE__ */ new Set([listener]),
131
+ stop: (reason) => {
132
+ var _a;
206
133
  subscription.stopped = true;
207
134
  if (subscription.retryTimer) {
208
135
  clearTimeout(subscription.retryTimer);
209
136
  subscription.retryTimer = null;
210
137
  }
211
- if (subscription.eventSource) {
212
- subscription.eventSource.close();
213
- subscription.eventSource = null;
138
+ (_a = subscription.abortController) == null ? void 0 : _a.abort();
139
+ subscription.abortController = null;
140
+ if (shouldLog) {
141
+ console.debug("[aomi][sse] stop", {
142
+ sessionId,
143
+ reason,
144
+ retries: subscription.retries
145
+ });
214
146
  }
215
147
  }
216
148
  };
217
149
  const scheduleRetry = () => {
150
+ if (subscription.stopped) return;
218
151
  subscription.retries += 1;
219
152
  const delayMs = Math.min(500 * 2 ** (subscription.retries - 1), 1e4);
220
- console.warn(
221
- `\u{1F501} [${logLabel}] retrying in ${delayMs}ms (attempt ${subscription.retries})`,
222
- { sessionId }
223
- );
153
+ if (shouldLog) {
154
+ console.debug("[aomi][sse] retry scheduled", {
155
+ sessionId,
156
+ delayMs,
157
+ retries: subscription.retries
158
+ });
159
+ }
224
160
  subscription.retryTimer = setTimeout(() => {
225
- open();
161
+ void open();
226
162
  }, delayMs);
227
163
  };
228
- const open = () => {
164
+ const open = async () => {
165
+ var _a;
229
166
  if (subscription.stopped) return;
230
167
  if (subscription.retryTimer) {
231
168
  clearTimeout(subscription.retryTimer);
232
169
  subscription.retryTimer = null;
233
170
  }
234
- if (subscription.eventSource) {
235
- subscription.eventSource.close();
236
- }
237
- const updatesEventSource = new EventSource(updatesUrlString);
238
- subscription.eventSource = updatesEventSource;
239
- console.log(`\u{1F514} [updates] subscribed`, updatesUrlString);
240
- updatesEventSource.onopen = () => {
241
- subscription.retries = 0;
242
- console.log("\u{1F514} [updates] open", updatesUrlString);
243
- };
244
- updatesEventSource.onmessage = (event) => {
245
- try {
246
- console.log("\u{1F514} [updates] message", { url: updatesUrlString, data: event.data });
247
- const parsed = JSON.parse(event.data);
248
- onUpdate(parsed);
249
- } catch (error) {
250
- console.error("Failed to parse system update SSE:", error);
251
- onError == null ? void 0 : onError(error);
171
+ const controller = new AbortController();
172
+ subscription.abortController = controller;
173
+ const openedAt = Date.now();
174
+ try {
175
+ const response = await fetch(`${backendUrl}/api/updates`, {
176
+ headers: getHeaders(sessionId),
177
+ signal: controller.signal
178
+ });
179
+ if (!response.ok) {
180
+ throw new Error(
181
+ `SSE HTTP ${response.status}: ${response.statusText}`
182
+ );
252
183
  }
253
- };
254
- updatesEventSource.onerror = (error) => {
255
- console.error("System updates SSE error:", {
256
- url: updatesUrlString,
257
- readyState: updatesEventSource.readyState,
258
- error
184
+ if (!response.body) {
185
+ throw new Error("SSE response missing body");
186
+ }
187
+ subscription.retries = 0;
188
+ await readSseStream(response.body, controller.signal, (data) => {
189
+ var _a2, _b;
190
+ let parsed;
191
+ try {
192
+ parsed = JSON.parse(data);
193
+ } catch (error) {
194
+ for (const item of subscription.listeners) {
195
+ (_a2 = item.onError) == null ? void 0 : _a2.call(item, error);
196
+ }
197
+ return;
198
+ }
199
+ for (const item of subscription.listeners) {
200
+ try {
201
+ item.onUpdate(parsed);
202
+ } catch (error) {
203
+ (_b = item.onError) == null ? void 0 : _b.call(item, error);
204
+ }
205
+ }
259
206
  });
260
- onError == null ? void 0 : onError(error);
261
- if (subscription.stopped) return;
262
- updatesEventSource.close();
207
+ if (shouldLog) {
208
+ console.debug("[aomi][sse] stream ended", {
209
+ sessionId,
210
+ aborted: controller.signal.aborted,
211
+ stopped: subscription.stopped,
212
+ durationMs: Date.now() - openedAt
213
+ });
214
+ }
215
+ } catch (error) {
216
+ if (!controller.signal.aborted && !subscription.stopped) {
217
+ for (const item of subscription.listeners) {
218
+ (_a = item.onError) == null ? void 0 : _a.call(item, error);
219
+ }
220
+ }
221
+ }
222
+ if (!subscription.stopped) {
263
223
  scheduleRetry();
264
- };
224
+ }
265
225
  };
266
- this.updatesEventSources.set(sessionId, subscription);
267
- open();
226
+ subscriptions.set(sessionId, subscription);
227
+ void open();
268
228
  return () => {
269
- const current = this.updatesEventSources.get(sessionId);
270
- if (current === subscription) {
271
- current.cleanup();
272
- this.updatesEventSources.delete(sessionId);
273
- } else {
274
- subscription.cleanup();
229
+ subscription.listeners.delete(listener);
230
+ if (shouldLog) {
231
+ console.debug("[aomi][sse] listener removed", {
232
+ sessionId,
233
+ listeners: subscription.listeners.size
234
+ });
235
+ }
236
+ if (subscription.listeners.size === 0) {
237
+ subscription.stop("unsubscribe");
238
+ if (subscriptions.get(sessionId) === subscription) {
239
+ subscriptions.delete(sessionId);
240
+ }
275
241
  }
276
242
  };
243
+ };
244
+ return { subscribe: subscribe2 };
245
+ }
246
+
247
+ // src/backend/client.ts
248
+ var SESSION_ID_HEADER = "X-Session-Id";
249
+ function toQueryString(payload) {
250
+ const params = new URLSearchParams();
251
+ for (const [key, value] of Object.entries(payload)) {
252
+ if (value === void 0 || value === null) continue;
253
+ params.set(key, String(value));
277
254
  }
278
- subscribeToUpdates(sessionId, onUpdate, onError) {
279
- return this.subscribeToUpdatesInternal(
280
- sessionId,
281
- onUpdate,
282
- onError,
283
- "subscribeToUpdates"
255
+ const qs = params.toString();
256
+ return qs ? `?${qs}` : "";
257
+ }
258
+ function withSessionHeader(sessionId, init) {
259
+ const headers = new Headers(init);
260
+ headers.set(SESSION_ID_HEADER, sessionId);
261
+ return headers;
262
+ }
263
+ async function postState(backendUrl, path, payload, sessionId) {
264
+ const query = toQueryString(payload);
265
+ const url = `${backendUrl}${path}${query}`;
266
+ const response = await fetch(url, {
267
+ method: "POST",
268
+ headers: withSessionHeader(sessionId)
269
+ });
270
+ if (!response.ok) {
271
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
272
+ }
273
+ return await response.json();
274
+ }
275
+ var BackendApi = class {
276
+ constructor(backendUrl) {
277
+ this.backendUrl = backendUrl;
278
+ this.sseSubscriber = createSseSubscriber({
279
+ backendUrl,
280
+ getHeaders: (sessionId) => withSessionHeader(sessionId, { Accept: "text/event-stream" })
281
+ });
282
+ }
283
+ async fetchState(sessionId, userState) {
284
+ const url = new URL("/api/state", this.backendUrl);
285
+ if (userState) {
286
+ url.searchParams.set("user_state", JSON.stringify(userState));
287
+ }
288
+ const response = await fetch(url.toString(), {
289
+ headers: withSessionHeader(sessionId)
290
+ });
291
+ if (!response.ok) {
292
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
293
+ }
294
+ return await response.json();
295
+ }
296
+ async postChatMessage(sessionId, message, publicKey) {
297
+ return postState(
298
+ this.backendUrl,
299
+ "/api/chat",
300
+ {
301
+ message,
302
+ public_key: publicKey
303
+ },
304
+ sessionId
305
+ );
306
+ }
307
+ async postSystemMessage(sessionId, message) {
308
+ return postState(
309
+ this.backendUrl,
310
+ "/api/system",
311
+ {
312
+ message
313
+ },
314
+ sessionId
315
+ );
316
+ }
317
+ async postInterrupt(sessionId) {
318
+ return postState(
319
+ this.backendUrl,
320
+ "/api/interrupt",
321
+ {},
322
+ sessionId
284
323
  );
285
324
  }
325
+ /**
326
+ * Subscribe to SSE updates for a session.
327
+ * Uses fetch streaming and reconnects on disconnects.
328
+ * Returns an unsubscribe function.
329
+ */
330
+ subscribeSSE(sessionId, onUpdate, onError) {
331
+ return this.sseSubscriber.subscribe(sessionId, onUpdate, onError);
332
+ }
286
333
  async fetchThreads(publicKey) {
287
- console.log("\u{1F535} [fetchThreads] Called with publicKey:", publicKey);
288
334
  const url = `${this.backendUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
289
- console.log("\u{1F535} [fetchThreads] URL:", url);
290
335
  const response = await fetch(url);
291
- console.log("\u{1F535} [fetchThreads] Response status:", response.status);
292
336
  if (!response.ok) {
293
- console.error("\u{1F534} [fetchThreads] Error:", response.status);
294
337
  throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
295
338
  }
296
- const data = await response.json();
297
- console.log("\u{1F7E2} [fetchThreads] Success:", data);
298
- return data;
339
+ return await response.json();
299
340
  }
300
- async createThread(publicKey, title) {
301
- console.log("\u{1F535} [createThread] Called with publicKey:", publicKey, "title:", title);
302
- const body = {};
303
- if (publicKey) {
304
- body.public_key = publicKey;
305
- }
306
- if (title) {
307
- body.title = title;
341
+ async fetchThread(sessionId) {
342
+ const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
343
+ const response = await fetch(url, {
344
+ headers: withSessionHeader(sessionId)
345
+ });
346
+ if (!response.ok) {
347
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
308
348
  }
309
- console.log("\u{1F535} [createThread] Request body:", body);
349
+ return await response.json();
350
+ }
351
+ async createThread(threadId, publicKey) {
352
+ const body = {};
353
+ if (publicKey) body.public_key = publicKey;
310
354
  const url = `${this.backendUrl}/api/sessions`;
311
- console.log("\u{1F535} [createThread] URL:", url);
312
355
  const response = await fetch(url, {
313
356
  method: "POST",
314
- headers: { "Content-Type": "application/json" },
357
+ headers: withSessionHeader(threadId, {
358
+ "Content-Type": "application/json"
359
+ }),
315
360
  body: JSON.stringify(body)
316
361
  });
317
- console.log("\u{1F535} [createThread] Response status:", response.status);
318
362
  if (!response.ok) {
319
- console.error("\u{1F534} [createThread] Error:", response.status);
320
363
  throw new Error(`Failed to create thread: HTTP ${response.status}`);
321
364
  }
322
- const data = await response.json();
323
- console.log("\u{1F7E2} [createThread] Success:", data);
324
- return data;
365
+ return await response.json();
325
366
  }
326
367
  async archiveThread(sessionId) {
327
- console.log("\u{1F535} [archiveThread] Called with sessionId:", sessionId);
328
368
  const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
329
- console.log("\u{1F535} [archiveThread] URL:", url);
330
- const response = await fetch(url, { method: "POST" });
331
- console.log("\u{1F535} [archiveThread] Response status:", response.status);
369
+ const response = await fetch(url, {
370
+ method: "POST",
371
+ headers: withSessionHeader(sessionId)
372
+ });
332
373
  if (!response.ok) {
333
- console.error("\u{1F534} [archiveThread] Error:", response.status);
334
374
  throw new Error(`Failed to archive thread: HTTP ${response.status}`);
335
375
  }
336
- console.log("\u{1F7E2} [archiveThread] Success");
337
376
  }
338
377
  async unarchiveThread(sessionId) {
339
- console.log("\u{1F535} [unarchiveThread] Called with sessionId:", sessionId);
340
378
  const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
341
- console.log("\u{1F535} [unarchiveThread] URL:", url);
342
- const response = await fetch(url, { method: "POST" });
343
- console.log("\u{1F535} [unarchiveThread] Response status:", response.status);
379
+ const response = await fetch(url, {
380
+ method: "POST",
381
+ headers: withSessionHeader(sessionId)
382
+ });
344
383
  if (!response.ok) {
345
- console.error("\u{1F534} [unarchiveThread] Error:", response.status);
346
384
  throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
347
385
  }
348
- console.log("\u{1F7E2} [unarchiveThread] Success");
349
386
  }
350
387
  async deleteThread(sessionId) {
351
- console.log("\u{1F535} [deleteThread] Called with sessionId:", sessionId);
352
388
  const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
353
- console.log("\u{1F535} [deleteThread] URL:", url);
354
- const response = await fetch(url, { method: "DELETE" });
355
- console.log("\u{1F535} [deleteThread] Response status:", response.status);
389
+ const response = await fetch(url, {
390
+ method: "DELETE",
391
+ headers: withSessionHeader(sessionId)
392
+ });
356
393
  if (!response.ok) {
357
- console.error("\u{1F534} [deleteThread] Error:", response.status);
358
394
  throw new Error(`Failed to delete thread: HTTP ${response.status}`);
359
395
  }
360
- console.log("\u{1F7E2} [deleteThread] Success");
361
396
  }
362
397
  async renameThread(sessionId, newTitle) {
363
- console.log("\u{1F535} [renameThread] Called with sessionId:", sessionId, "newTitle:", newTitle);
364
398
  const url = `${this.backendUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
365
- console.log("\u{1F535} [renameThread] URL:", url);
366
399
  const response = await fetch(url, {
367
400
  method: "PATCH",
368
- headers: { "Content-Type": "application/json" },
401
+ headers: withSessionHeader(sessionId, {
402
+ "Content-Type": "application/json"
403
+ }),
369
404
  body: JSON.stringify({ title: newTitle })
370
405
  });
371
- console.log("\u{1F535} [renameThread] Response status:", response.status);
372
406
  if (!response.ok) {
373
- console.error("\u{1F534} [renameThread] Error:", response.status);
374
407
  throw new Error(`Failed to rename thread: HTTP ${response.status}`);
375
408
  }
376
- console.log("\u{1F7E2} [renameThread] Success");
377
409
  }
378
- async fetchEventsAfter(sessionId, afterId = 0, limit = 100) {
410
+ async getSystemEvents(sessionId, count) {
379
411
  const url = new URL("/api/events", this.backendUrl);
380
- url.searchParams.set("session_id", sessionId);
381
- if (afterId > 0) url.searchParams.set("after_id", String(afterId));
382
- if (limit) url.searchParams.set("limit", String(limit));
383
- console.log("\u{1F535} [fetchEventsAfter] URL:", url.toString());
384
- const response = await fetch(url.toString());
412
+ if (count !== void 0) {
413
+ url.searchParams.set("count", String(count));
414
+ }
415
+ const response = await fetch(url.toString(), {
416
+ headers: withSessionHeader(sessionId)
417
+ });
385
418
  if (!response.ok) {
386
- throw new Error(`Failed to fetch events: HTTP ${response.status}`);
419
+ if (response.status === 404) return [];
420
+ throw new Error(`Failed to get system events: HTTP ${response.status}`);
387
421
  }
388
422
  return await response.json();
389
423
  }
390
- subscribeToUpdatesWithNotification(sessionId, onUpdate, onError) {
391
- return this.subscribeToUpdatesInternal(
392
- sessionId,
393
- onUpdate,
394
- onError,
395
- "subscribeToUpdatesWithNotification"
396
- );
397
- }
424
+ // fetchEventsAfter removed: /api/events only supports count now
398
425
  };
399
426
 
400
427
  // src/runtime/aomi-runtime.tsx
401
- var import_react6 = require("react");
402
- var import_react7 = require("@assistant-ui/react");
428
+ var import_react9 = require("react");
403
429
 
404
- // src/runtime/hooks.ts
430
+ // src/contexts/event-context.tsx
405
431
  var import_react = require("react");
406
- var RuntimeActionsContext = (0, import_react.createContext)(void 0);
407
- var RuntimeActionsProvider = RuntimeActionsContext.Provider;
408
- function useRuntimeActions() {
409
- const context = (0, import_react.useContext)(RuntimeActionsContext);
432
+
433
+ // src/backend/types.ts
434
+ function isInlineCall(event) {
435
+ return "InlineCall" in event;
436
+ }
437
+ function isSystemNotice(event) {
438
+ return "SystemNotice" in event;
439
+ }
440
+ function isSystemError(event) {
441
+ return "SystemError" in event;
442
+ }
443
+ function isAsyncCallback(event) {
444
+ return "AsyncCallback" in event;
445
+ }
446
+
447
+ // src/state/event-buffer.ts
448
+ function createEventBuffer() {
449
+ return {
450
+ inboundQueue: [],
451
+ outboundQueue: [],
452
+ sseStatus: "disconnected",
453
+ lastEventId: null,
454
+ subscribers: /* @__PURE__ */ new Map()
455
+ };
456
+ }
457
+ function enqueueInbound(state, event) {
458
+ state.inboundQueue.push(__spreadProps(__spreadValues({}, event), {
459
+ status: "pending",
460
+ timestamp: Date.now()
461
+ }));
462
+ }
463
+ function subscribe(state, type, callback) {
464
+ if (!state.subscribers.has(type)) {
465
+ state.subscribers.set(type, /* @__PURE__ */ new Set());
466
+ }
467
+ state.subscribers.get(type).add(callback);
468
+ return () => {
469
+ var _a;
470
+ (_a = state.subscribers.get(type)) == null ? void 0 : _a.delete(callback);
471
+ };
472
+ }
473
+ function dispatch(state, event) {
474
+ const typeSubscribers = state.subscribers.get(event.type);
475
+ if (typeSubscribers) {
476
+ for (const callback of typeSubscribers) {
477
+ callback(event);
478
+ }
479
+ }
480
+ const allSubscribers = state.subscribers.get("*");
481
+ if (allSubscribers) {
482
+ for (const callback of allSubscribers) {
483
+ callback(event);
484
+ }
485
+ }
486
+ }
487
+ function setSSEStatus(state, status) {
488
+ state.sseStatus = status;
489
+ }
490
+
491
+ // src/contexts/event-context.tsx
492
+ var import_jsx_runtime = require("react/jsx-runtime");
493
+ var EventContextState = (0, import_react.createContext)(null);
494
+ function useEventContext() {
495
+ const context = (0, import_react.useContext)(EventContextState);
410
496
  if (!context) {
411
- throw new Error("useRuntimeActions must be used within AomiRuntimeProvider");
497
+ throw new Error(
498
+ "useEventContext must be used within EventContextProvider. Wrap your app with <EventContextProvider>...</EventContextProvider>"
499
+ );
412
500
  }
413
501
  return context;
414
502
  }
503
+ function EventContextProvider({
504
+ children,
505
+ backendApi,
506
+ sessionId
507
+ }) {
508
+ const bufferRef = (0, import_react.useRef)(null);
509
+ if (!bufferRef.current) {
510
+ bufferRef.current = createEventBuffer();
511
+ }
512
+ const buffer = bufferRef.current;
513
+ const [sseStatus, setSseStatus] = (0, import_react.useState)("disconnected");
514
+ (0, import_react.useEffect)(() => {
515
+ setSSEStatus(buffer, "connecting");
516
+ setSseStatus("connecting");
517
+ const unsubscribe = backendApi.subscribeSSE(
518
+ sessionId,
519
+ (event) => {
520
+ enqueueInbound(buffer, {
521
+ type: event.type,
522
+ sessionId: event.session_id,
523
+ payload: event
524
+ });
525
+ const inboundEvent = {
526
+ type: event.type,
527
+ sessionId: event.session_id,
528
+ payload: event,
529
+ status: "fetched",
530
+ timestamp: Date.now()
531
+ };
532
+ dispatch(buffer, inboundEvent);
533
+ },
534
+ (error) => {
535
+ console.error("SSE error:", error);
536
+ setSSEStatus(buffer, "disconnected");
537
+ setSseStatus("disconnected");
538
+ }
539
+ );
540
+ setSSEStatus(buffer, "connected");
541
+ setSseStatus("connected");
542
+ return () => {
543
+ unsubscribe();
544
+ setSSEStatus(buffer, "disconnected");
545
+ setSseStatus("disconnected");
546
+ };
547
+ }, [backendApi, sessionId, buffer]);
548
+ const subscribeCallback = (0, import_react.useCallback)(
549
+ (type, callback) => {
550
+ return subscribe(buffer, type, callback);
551
+ },
552
+ [buffer]
553
+ );
554
+ const sendOutbound = (0, import_react.useCallback)(
555
+ async (event) => {
556
+ try {
557
+ const message = JSON.stringify({
558
+ type: event.type,
559
+ payload: event.payload
560
+ });
561
+ await backendApi.postSystemMessage(event.sessionId, message);
562
+ } catch (error) {
563
+ console.error("Failed to send outbound event:", error);
564
+ }
565
+ },
566
+ [backendApi]
567
+ );
568
+ const dispatchSystemEvents = (0, import_react.useCallback)(
569
+ (sessionId2, events) => {
570
+ var _a;
571
+ for (const event of events) {
572
+ let eventType;
573
+ let payload;
574
+ if (isInlineCall(event)) {
575
+ eventType = event.InlineCall.type;
576
+ payload = (_a = event.InlineCall.payload) != null ? _a : event.InlineCall;
577
+ } else if (isSystemNotice(event)) {
578
+ eventType = "system_notice";
579
+ payload = { message: event.SystemNotice };
580
+ } else if (isSystemError(event)) {
581
+ eventType = "system_error";
582
+ payload = { message: event.SystemError };
583
+ } else if (isAsyncCallback(event)) {
584
+ eventType = "async_callback";
585
+ payload = event.AsyncCallback;
586
+ } else {
587
+ console.warn("Unknown system event type:", event);
588
+ continue;
589
+ }
590
+ const inboundEvent = {
591
+ type: eventType,
592
+ sessionId: sessionId2,
593
+ payload,
594
+ status: "fetched",
595
+ timestamp: Date.now()
596
+ };
597
+ enqueueInbound(buffer, {
598
+ type: eventType,
599
+ sessionId: sessionId2,
600
+ payload
601
+ });
602
+ dispatch(buffer, inboundEvent);
603
+ }
604
+ },
605
+ [buffer]
606
+ );
607
+ const contextValue = {
608
+ subscribe: subscribeCallback,
609
+ sendOutboundSystem: sendOutbound,
610
+ dispatchInboundSystem: dispatchSystemEvents,
611
+ sseStatus
612
+ };
613
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(EventContextState.Provider, { value: contextValue, children });
614
+ }
415
615
 
416
- // src/runtime/orchestrator.ts
417
- var import_react3 = require("react");
418
-
419
- // src/state/thread-context.tsx
616
+ // src/contexts/notification-context.tsx
420
617
  var import_react2 = require("react");
618
+ var import_jsx_runtime2 = require("react/jsx-runtime");
619
+ var NotificationContext = (0, import_react2.createContext)(null);
620
+ function useNotification() {
621
+ const context = (0, import_react2.useContext)(NotificationContext);
622
+ if (!context) {
623
+ throw new Error(
624
+ "useNotification must be used within NotificationContextProvider"
625
+ );
626
+ }
627
+ return context;
628
+ }
629
+ var notificationIdCounter = 0;
630
+ function generateId() {
631
+ return `notif-${Date.now()}-${++notificationIdCounter}`;
632
+ }
633
+ function NotificationContextProvider({
634
+ children
635
+ }) {
636
+ const [notifications, setNotifications] = (0, import_react2.useState)([]);
637
+ const showNotification = (0, import_react2.useCallback)((params) => {
638
+ const id = generateId();
639
+ const notification = __spreadProps(__spreadValues({}, params), {
640
+ id,
641
+ timestamp: Date.now()
642
+ });
643
+ setNotifications((prev) => [notification, ...prev]);
644
+ return id;
645
+ }, []);
646
+ const dismissNotification = (0, import_react2.useCallback)((id) => {
647
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
648
+ }, []);
649
+ const clearAll = (0, import_react2.useCallback)(() => {
650
+ setNotifications([]);
651
+ }, []);
652
+ const value = {
653
+ notifications,
654
+ showNotification,
655
+ dismissNotification,
656
+ clearAll
657
+ };
658
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NotificationContext.Provider, { value, children });
659
+ }
660
+
661
+ // src/contexts/thread-context.tsx
662
+ var import_react3 = require("react");
421
663
 
422
664
  // src/state/thread-store.ts
665
+ var shouldLogThreadUpdates = process.env.NODE_ENV !== "production";
666
+ var logThreadMetadataChange = (source, threadId, prev, next) => {
667
+ if (!shouldLogThreadUpdates) return;
668
+ if (!prev && !next) return;
669
+ if (!prev || !next) {
670
+ console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
671
+ return;
672
+ }
673
+ if (prev.title !== next.title || prev.status !== next.status || prev.lastActiveAt !== next.lastActiveAt) {
674
+ console.debug(`[aomi][thread:${source}]`, { threadId, prev, next });
675
+ }
676
+ };
423
677
  var ThreadStore = class {
424
678
  constructor(options) {
425
679
  this.listeners = /* @__PURE__ */ new Set();
@@ -446,7 +700,21 @@ var ThreadStore = class {
446
700
  this.updateState({ threads: new Map(nextThreads) });
447
701
  };
448
702
  this.setThreadMetadata = (updater) => {
449
- const nextMetadata = this.resolveStateAction(updater, this.state.threadMetadata);
703
+ const prevMetadata = this.state.threadMetadata;
704
+ const nextMetadata = this.resolveStateAction(updater, prevMetadata);
705
+ for (const [threadId, next] of nextMetadata.entries()) {
706
+ logThreadMetadataChange(
707
+ "setThreadMetadata",
708
+ threadId,
709
+ prevMetadata.get(threadId),
710
+ next
711
+ );
712
+ }
713
+ for (const [threadId, prev] of prevMetadata.entries()) {
714
+ if (!nextMetadata.has(threadId)) {
715
+ logThreadMetadataChange("setThreadMetadata", threadId, prev, void 0);
716
+ }
717
+ }
450
718
  this.updateState({ threadMetadata: new Map(nextMetadata) });
451
719
  };
452
720
  this.setThreadMessages = (threadId, messages) => {
@@ -467,8 +735,10 @@ var ThreadStore = class {
467
735
  if (!existing) {
468
736
  return;
469
737
  }
738
+ const next = __spreadValues(__spreadValues({}, existing), updates);
470
739
  const nextMetadata = new Map(this.state.threadMetadata);
471
- nextMetadata.set(threadId, __spreadValues(__spreadValues({}, existing), updates));
740
+ nextMetadata.set(threadId, next);
741
+ logThreadMetadataChange("updateThreadMetadata", threadId, existing, next);
472
742
  this.updateState({ threadMetadata: nextMetadata });
473
743
  };
474
744
  var _a;
@@ -526,9 +796,9 @@ var ThreadStore = class {
526
796
  setCurrentThreadId: this.setCurrentThreadId,
527
797
  threadViewKey: this.state.threadViewKey,
528
798
  bumpThreadViewKey: this.bumpThreadViewKey,
529
- threads: this.state.threads,
799
+ allThreads: this.state.threads,
530
800
  setThreads: this.setThreads,
531
- threadMetadata: this.state.threadMetadata,
801
+ allThreadsMetadata: this.state.threadMetadata,
532
802
  setThreadMetadata: this.setThreadMetadata,
533
803
  threadCnt: this.state.threadCnt,
534
804
  setThreadCnt: this.setThreadCnt,
@@ -540,11 +810,11 @@ var ThreadStore = class {
540
810
  }
541
811
  };
542
812
 
543
- // src/state/thread-context.tsx
544
- var import_jsx_runtime = require("react/jsx-runtime");
545
- var ThreadContextState = (0, import_react2.createContext)(null);
813
+ // src/contexts/thread-context.tsx
814
+ var import_jsx_runtime3 = require("react/jsx-runtime");
815
+ var ThreadContextState = (0, import_react3.createContext)(null);
546
816
  function useThreadContext() {
547
- const context = (0, import_react2.useContext)(ThreadContextState);
817
+ const context = (0, import_react3.useContext)(ThreadContextState);
548
818
  if (!context) {
549
819
  throw new Error(
550
820
  "useThreadContext must be used within ThreadContextProvider. Wrap your app with <ThreadContextProvider>...</ThreadContextProvider>"
@@ -556,33 +826,133 @@ function ThreadContextProvider({
556
826
  children,
557
827
  initialThreadId
558
828
  }) {
559
- const storeRef = (0, import_react2.useRef)(null);
829
+ const storeRef = (0, import_react3.useRef)(null);
560
830
  if (!storeRef.current) {
561
831
  storeRef.current = new ThreadStore({ initialThreadId });
562
832
  }
563
833
  const store = storeRef.current;
564
- const value = (0, import_react2.useSyncExternalStore)(store.subscribe, store.getSnapshot);
565
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ThreadContextState.Provider, { value, children });
834
+ const value = (0, import_react3.useSyncExternalStore)(
835
+ store.subscribe,
836
+ store.getSnapshot,
837
+ store.getSnapshot
838
+ );
839
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ThreadContextState.Provider, { value, children });
566
840
  }
567
841
  function useCurrentThreadMessages() {
568
842
  const { currentThreadId, getThreadMessages } = useThreadContext();
569
- return (0, import_react2.useMemo)(() => getThreadMessages(currentThreadId), [currentThreadId, getThreadMessages]);
843
+ return (0, import_react3.useMemo)(
844
+ () => getThreadMessages(currentThreadId),
845
+ [currentThreadId, getThreadMessages]
846
+ );
570
847
  }
571
848
  function useCurrentThreadMetadata() {
572
849
  const { currentThreadId, getThreadMetadata } = useThreadContext();
573
- return (0, import_react2.useMemo)(() => getThreadMetadata(currentThreadId), [currentThreadId, getThreadMetadata]);
850
+ return (0, import_react3.useMemo)(
851
+ () => getThreadMetadata(currentThreadId),
852
+ [currentThreadId, getThreadMetadata]
853
+ );
574
854
  }
575
855
 
576
- // src/utils/conversion.ts
577
- function toInboundMessage(msg) {
578
- var _a;
579
- if (msg.sender === "system") return null;
580
- const content = [];
856
+ // src/contexts/user-context.tsx
857
+ var import_react4 = require("react");
858
+ var import_jsx_runtime4 = require("react/jsx-runtime");
859
+ var UserContext = (0, import_react4.createContext)(void 0);
860
+ function useUser() {
861
+ const context = (0, import_react4.useContext)(UserContext);
862
+ if (!context) {
863
+ throw new Error("useUser must be used within UserContextProvider");
864
+ }
865
+ return {
866
+ user: context.user,
867
+ setUser: context.setUser,
868
+ getUserState: context.getUserState,
869
+ onUserStateChange: context.onUserStateChange
870
+ };
871
+ }
872
+ function UserContextProvider({ children }) {
873
+ const [user, setUserState] = (0, import_react4.useState)({
874
+ isConnected: false,
875
+ address: void 0,
876
+ chainId: void 0,
877
+ ensName: void 0
878
+ });
879
+ const userRef = (0, import_react4.useRef)(user);
880
+ userRef.current = user;
881
+ const StateChangeCallbacks = (0, import_react4.useRef)(
882
+ /* @__PURE__ */ new Set()
883
+ );
884
+ const setUser = (0, import_react4.useCallback)((data) => {
885
+ setUserState((prev) => {
886
+ const next = __spreadValues(__spreadValues({}, prev), data);
887
+ StateChangeCallbacks.current.forEach((callback) => {
888
+ callback(next);
889
+ });
890
+ return next;
891
+ });
892
+ }, []);
893
+ const getUserState = (0, import_react4.useCallback)(() => userRef.current, []);
894
+ const onUserStateChange = (0, import_react4.useCallback)(
895
+ (callback) => {
896
+ StateChangeCallbacks.current.add(callback);
897
+ return () => {
898
+ StateChangeCallbacks.current.delete(callback);
899
+ };
900
+ },
901
+ []
902
+ );
903
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
904
+ UserContext.Provider,
905
+ {
906
+ value: {
907
+ user,
908
+ setUser,
909
+ getUserState,
910
+ onUserStateChange
911
+ },
912
+ children
913
+ }
914
+ );
915
+ }
916
+
917
+ // src/runtime/core.tsx
918
+ var import_react7 = require("react");
919
+ var import_react8 = require("@assistant-ui/react");
920
+
921
+ // src/runtime/orchestrator.ts
922
+ var import_react5 = require("react");
923
+
924
+ // src/runtime/utils.ts
925
+ var import_clsx = require("clsx");
926
+ var import_tailwind_merge = require("tailwind-merge");
927
+ function cn(...inputs) {
928
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
929
+ }
930
+ var parseTimestamp = (value) => {
931
+ if (value === void 0 || value === null) return 0;
932
+ if (typeof value === "number") {
933
+ return Number.isFinite(value) ? value < 1e12 ? value * 1e3 : value : 0;
934
+ }
935
+ const numeric = Number(value);
936
+ if (!Number.isNaN(numeric)) {
937
+ return numeric < 1e12 ? numeric * 1e3 : numeric;
938
+ }
939
+ const ts = Date.parse(value);
940
+ return Number.isNaN(ts) ? 0 : ts;
941
+ };
942
+ var isPlaceholderTitle = (title) => {
943
+ var _a;
944
+ const normalized = (_a = title == null ? void 0 : title.trim()) != null ? _a : "";
945
+ return !normalized || normalized.startsWith("#[");
946
+ };
947
+ function toInboundMessage(msg) {
948
+ var _a;
949
+ if (msg.sender === "system") return null;
950
+ const content = [];
581
951
  const role = msg.sender === "user" ? "user" : "assistant";
582
952
  if (msg.content) {
583
953
  content.push({ type: "text", text: msg.content });
584
954
  }
585
- const [topic, toolContent] = (_a = parseToolStream(msg.tool_stream)) != null ? _a : [];
955
+ const [topic, toolContent] = (_a = parseToolPayload(msg)) != null ? _a : [];
586
956
  if (topic && toolContent) {
587
957
  content.push({
588
958
  type: "tool-call",
@@ -604,84 +974,61 @@ function toInboundMessage(msg) {
604
974
  }, msg.timestamp && { createdAt: new Date(msg.timestamp) });
605
975
  return threadMessage;
606
976
  }
607
- function toInboundSystem(msg) {
608
- var _a;
609
- const [topic] = (_a = parseToolStream(msg.tool_stream)) != null ? _a : [];
610
- const messageText = topic || msg.content || "";
611
- const timestamp = parseTimestamp(msg.timestamp);
612
- if (!messageText.trim()) return null;
613
- return __spreadValues({
614
- role: "system",
615
- content: [{ type: "text", text: messageText }]
616
- }, timestamp && { createdAt: timestamp });
617
- }
618
- function parseTimestamp(timestamp) {
619
- if (!timestamp) return void 0;
620
- const parsed = new Date(timestamp);
621
- return Number.isNaN(parsed.valueOf()) ? void 0 : parsed;
977
+ function parseToolPayload(msg) {
978
+ return parseToolResult(msg.tool_result);
622
979
  }
623
- function parseToolStream(toolStream) {
624
- if (!toolStream) return null;
625
- if (Array.isArray(toolStream) && toolStream.length === 2) {
626
- const [topic, content] = toolStream;
627
- return [String(topic), content];
628
- }
629
- if (typeof toolStream === "object") {
630
- const topic = toolStream.topic;
631
- const content = toolStream.content;
632
- return topic ? [String(topic), String(content)] : null;
980
+ function parseToolResult(toolResult) {
981
+ if (!toolResult) return null;
982
+ if (Array.isArray(toolResult) && toolResult.length === 2) {
983
+ const [topic, content] = toolResult;
984
+ return [String(topic), String(content != null ? content : "")];
633
985
  }
634
986
  return null;
635
987
  }
636
-
637
- // src/runtime/utils.ts
638
- var isTempThreadId = (id) => id.startsWith("temp-");
639
- var parseTimestamp2 = (value) => {
640
- if (value === void 0 || value === null) return 0;
641
- if (typeof value === "number") {
642
- return Number.isFinite(value) ? value < 1e12 ? value * 1e3 : value : 0;
643
- }
644
- const numeric = Number(value);
645
- if (!Number.isNaN(numeric)) {
646
- return numeric < 1e12 ? numeric * 1e3 : numeric;
988
+ var getNetworkName = (chainId) => {
989
+ if (chainId === void 0) return "";
990
+ const id = typeof chainId === "string" ? Number(chainId) : chainId;
991
+ switch (id) {
992
+ case 1:
993
+ return "ethereum";
994
+ case 137:
995
+ return "polygon";
996
+ case 42161:
997
+ return "arbitrum";
998
+ case 8453:
999
+ return "base";
1000
+ case 10:
1001
+ return "optimism";
1002
+ case 11155111:
1003
+ return "sepolia";
1004
+ case 1337:
1005
+ case 31337:
1006
+ return "testnet";
1007
+ case 59140:
1008
+ return "linea-sepolia";
1009
+ case 59144:
1010
+ return "linea";
1011
+ default:
1012
+ return "testnet";
647
1013
  }
648
- const ts = Date.parse(value);
649
- return Number.isNaN(ts) ? 0 : ts;
650
- };
651
- var isPlaceholderTitle = (title) => {
652
- var _a;
653
- const normalized = (_a = title == null ? void 0 : title.trim()) != null ? _a : "";
654
- return !normalized || normalized.startsWith("#[");
655
1014
  };
1015
+ var formatAddress = (addr) => addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : "Connect Wallet";
656
1016
 
657
- // src/runtime/backend-state.ts
658
- function createBakendState() {
1017
+ // src/state/backend-state.ts
1018
+ function createBackendState() {
659
1019
  return {
660
- tempToBackendId: /* @__PURE__ */ new Map(),
661
1020
  skipInitialFetch: /* @__PURE__ */ new Set(),
662
1021
  pendingChat: /* @__PURE__ */ new Map(),
663
- pendingSystem: /* @__PURE__ */ new Map(),
664
1022
  runningThreads: /* @__PURE__ */ new Set(),
665
1023
  creatingThreadId: null,
666
1024
  createThreadPromise: null
667
1025
  };
668
1026
  }
669
1027
  function resolveThreadId(state, threadId) {
670
- var _a;
671
- return (_a = state.tempToBackendId.get(threadId)) != null ? _a : threadId;
1028
+ return threadId;
672
1029
  }
673
1030
  function isThreadReady(state, threadId) {
674
- if (!isTempThreadId(threadId)) return true;
675
- return state.tempToBackendId.has(threadId);
676
- }
677
- function setBackendMapping(state, tempId, backendId) {
678
- state.tempToBackendId.set(tempId, backendId);
679
- }
680
- function findTempIdForBackendId(state, backendId) {
681
- for (const [tempId, id] of state.tempToBackendId.entries()) {
682
- if (id === backendId) return tempId;
683
- }
684
- return void 0;
1031
+ return state.creatingThreadId !== threadId;
685
1032
  }
686
1033
  function markSkipInitialFetch(state, threadId) {
687
1034
  state.skipInitialFetch.add(threadId);
@@ -717,17 +1064,6 @@ function hasPendingChat(state, threadId) {
717
1064
  var _a, _b;
718
1065
  return ((_b = (_a = state.pendingChat.get(threadId)) == null ? void 0 : _a.length) != null ? _b : 0) > 0;
719
1066
  }
720
- function enqueuePendingSystem(state, threadId, text) {
721
- var _a;
722
- const existing = (_a = state.pendingSystem.get(threadId)) != null ? _a : [];
723
- state.pendingSystem.set(threadId, [...existing, text]);
724
- }
725
- function dequeuePendingSystem(state, threadId) {
726
- var _a;
727
- const pending = (_a = state.pendingSystem.get(threadId)) != null ? _a : [];
728
- state.pendingSystem.delete(threadId);
729
- return pending;
730
- }
731
1067
 
732
1068
  // src/runtime/message-controller.ts
733
1069
  var MessageController = class {
@@ -742,13 +1078,6 @@ var MessageController = class {
742
1078
  }
743
1079
  const threadMessages = [];
744
1080
  for (const msg of msgs) {
745
- if (msg.sender === "system") {
746
- const systemMessage = toInboundSystem(msg);
747
- if (systemMessage) {
748
- threadMessages.push(systemMessage);
749
- }
750
- continue;
751
- }
752
1081
  const threadMessage = toInboundMessage(msg);
753
1082
  if (threadMessage) {
754
1083
  threadMessages.push(threadMessage);
@@ -757,9 +1086,13 @@ var MessageController = class {
757
1086
  this.getThreadContextApi().setThreadMessages(threadId, threadMessages);
758
1087
  }
759
1088
  async outbound(message, threadId) {
760
- var _a, _b;
1089
+ var _a, _b, _c;
761
1090
  const backendState = this.config.backendStateRef.current;
762
- const text = message.content.filter((part) => part.type === "text").map((part) => part.text).join("\n");
1091
+ const text = message.content.filter(
1092
+ (part) => part.type === "text"
1093
+ ).map(
1094
+ (part) => part.text
1095
+ ).join("\n");
763
1096
  if (!text) return;
764
1097
  const threadState = this.getThreadContextApi();
765
1098
  const existingMessages = threadState.getThreadMessages(threadId);
@@ -769,7 +1102,9 @@ var MessageController = class {
769
1102
  createdAt: /* @__PURE__ */ new Date()
770
1103
  };
771
1104
  threadState.setThreadMessages(threadId, [...existingMessages, userMessage]);
772
- threadState.updateThreadMetadata(threadId, { lastActiveAt: (/* @__PURE__ */ new Date()).toISOString() });
1105
+ threadState.updateThreadMetadata(threadId, {
1106
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1107
+ });
773
1108
  if (!isThreadReady(backendState, threadId)) {
774
1109
  this.markRunning(threadId, true);
775
1110
  enqueuePendingChat(backendState, threadId, text);
@@ -779,62 +1114,30 @@ var MessageController = class {
779
1114
  const publicKey = (_b = (_a = this.config).getPublicKey) == null ? void 0 : _b.call(_a);
780
1115
  try {
781
1116
  this.markRunning(threadId, true);
782
- if (publicKey) {
783
- await this.config.backendApiRef.current.postChatMessage(
784
- backendThreadId,
785
- text,
786
- publicKey
787
- );
788
- } else {
789
- await this.config.backendApiRef.current.postChatMessage(backendThreadId, text);
1117
+ const response = publicKey ? await this.config.backendApiRef.current.postChatMessage(
1118
+ backendThreadId,
1119
+ text,
1120
+ publicKey
1121
+ ) : await this.config.backendApiRef.current.postChatMessage(
1122
+ backendThreadId,
1123
+ text
1124
+ );
1125
+ if (response == null ? void 0 : response.messages) {
1126
+ this.inbound(threadId, response.messages);
790
1127
  }
791
- await this.flushPendingSystem(threadId);
792
- this.config.polling.start(threadId);
793
- } catch (error) {
794
- console.error("Failed to send message:", error);
795
- this.markRunning(threadId, false);
796
- }
797
- }
798
- async outboundSystem(threadId, text) {
799
- const backendState = this.config.backendStateRef.current;
800
- if (!isThreadReady(backendState, threadId)) return;
801
- const threadMessages = this.getThreadContextApi().getThreadMessages(threadId);
802
- const hasUserMessages = threadMessages.some((msg) => msg.role === "user");
803
- if (!hasUserMessages) {
804
- enqueuePendingSystem(backendState, threadId, text);
805
- return;
806
- }
807
- await this.outboundSystemInner(threadId, text);
808
- }
809
- async outboundSystemInner(threadId, text) {
810
- const backendState = this.config.backendStateRef.current;
811
- const threadState = this.getThreadContextApi();
812
- const backendThreadId = resolveThreadId(backendState, threadId);
813
- this.markRunning(threadId, true);
814
- try {
815
- const response = await this.config.backendApiRef.current.postSystemMessage(backendThreadId, text);
816
- if (response.res) {
817
- const systemMessage = toInboundSystem(response.res);
818
- if (systemMessage) {
819
- const updatedMessages = [...threadState.getThreadMessages(threadId), systemMessage];
820
- threadState.setThreadMessages(threadId, updatedMessages);
821
- }
1128
+ if (((_c = response == null ? void 0 : response.system_events) == null ? void 0 : _c.length) && this.config.onSyncEvents) {
1129
+ this.config.onSyncEvents(backendThreadId, response.system_events);
1130
+ }
1131
+ if (response == null ? void 0 : response.is_processing) {
1132
+ this.config.polling.start(threadId);
1133
+ } else if (!this.config.polling.isPolling(threadId)) {
1134
+ this.markRunning(threadId, false);
822
1135
  }
823
- await this.flushPendingSystem(threadId);
824
- this.config.polling.start(threadId);
825
1136
  } catch (error) {
826
- console.error("Failed to send system message:", error);
1137
+ console.error("Failed to send message:", error);
827
1138
  this.markRunning(threadId, false);
828
1139
  }
829
1140
  }
830
- async flushPendingSystem(threadId) {
831
- const backendState = this.config.backendStateRef.current;
832
- const pending = dequeuePendingSystem(backendState, threadId);
833
- if (!pending.length) return;
834
- for (const pendingMessage of pending) {
835
- await this.outboundSystemInner(threadId, pendingMessage);
836
- }
837
- }
838
1141
  async flushPendingChat(threadId) {
839
1142
  var _a, _b;
840
1143
  const backendState = this.config.backendStateRef.current;
@@ -851,7 +1154,10 @@ var MessageController = class {
851
1154
  publicKey
852
1155
  );
853
1156
  } else {
854
- await this.config.backendApiRef.current.postChatMessage(backendThreadId, text);
1157
+ await this.config.backendApiRef.current.postChatMessage(
1158
+ backendThreadId,
1159
+ text
1160
+ );
855
1161
  }
856
1162
  } catch (error) {
857
1163
  console.error("Failed to send queued message:", error);
@@ -860,12 +1166,19 @@ var MessageController = class {
860
1166
  this.config.polling.start(threadId);
861
1167
  }
862
1168
  async cancel(threadId) {
1169
+ var _a;
863
1170
  const backendState = this.config.backendStateRef.current;
864
1171
  if (!isThreadReady(backendState, threadId)) return;
865
1172
  this.config.polling.stop(threadId);
866
1173
  const backendThreadId = resolveThreadId(backendState, threadId);
867
1174
  try {
868
- await this.config.backendApiRef.current.postInterrupt(backendThreadId);
1175
+ const response = await this.config.backendApiRef.current.postInterrupt(backendThreadId);
1176
+ if (response == null ? void 0 : response.messages) {
1177
+ this.inbound(threadId, response.messages);
1178
+ }
1179
+ if (((_a = response == null ? void 0 : response.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1180
+ this.config.onSyncEvents(backendThreadId, response.system_events);
1181
+ }
869
1182
  this.markRunning(threadId, false);
870
1183
  } catch (error) {
871
1184
  console.error("Failed to cancel:", error);
@@ -891,29 +1204,28 @@ var PollingController = class {
891
1204
  this.intervals = /* @__PURE__ */ new Map();
892
1205
  var _a;
893
1206
  this.intervalMs = (_a = config.intervalMs) != null ? _a : 500;
894
- this.handleSystemEvents = config.handleSystemEvents;
895
- }
896
- setSystemEventsHandler(handler) {
897
- this.handleSystemEvents = handler;
898
1207
  }
899
1208
  start(threadId) {
1209
+ var _a, _b;
900
1210
  const backendState = this.config.backendStateRef.current;
901
1211
  if (!isThreadReady(backendState, threadId)) return;
902
- if (this.intervals.has(threadId)) {
903
- return;
904
- }
1212
+ if (this.intervals.has(threadId)) return;
905
1213
  const backendThreadId = resolveThreadId(backendState, threadId);
906
1214
  setThreadRunning(backendState, threadId, true);
907
1215
  const tick = async () => {
908
- if (!this.intervals.has(threadId)) {
909
- return;
910
- }
1216
+ var _a2, _b2;
1217
+ if (!this.intervals.has(threadId)) return;
911
1218
  try {
912
- console.log("\u{1F535} [PollingController] Fetching state for threadId:", threadId);
913
- const state = await this.config.backendApiRef.current.fetchState(backendThreadId);
914
- if (!this.intervals.has(threadId)) {
915
- return;
916
- }
1219
+ console.log(
1220
+ "[PollingController] Fetching state for threadId:",
1221
+ threadId
1222
+ );
1223
+ const userState = (_b2 = (_a2 = this.config).getUserState) == null ? void 0 : _b2.call(_a2);
1224
+ const state = await this.config.backendApiRef.current.fetchState(
1225
+ backendThreadId,
1226
+ userState
1227
+ );
1228
+ if (!this.intervals.has(threadId)) return;
917
1229
  this.handleState(threadId, state);
918
1230
  } catch (error) {
919
1231
  console.error("Polling error:", error);
@@ -922,9 +1234,7 @@ var PollingController = class {
922
1234
  };
923
1235
  const intervalId = setInterval(tick, this.intervalMs);
924
1236
  this.intervals.set(threadId, intervalId);
925
- if (this.config.onStart) {
926
- this.config.onStart(threadId);
927
- }
1237
+ (_b = (_a = this.config).onStart) == null ? void 0 : _b.call(_a, threadId);
928
1238
  }
929
1239
  stop(threadId) {
930
1240
  var _a, _b;
@@ -945,14 +1255,11 @@ var PollingController = class {
945
1255
  }
946
1256
  }
947
1257
  handleState(threadId, state) {
948
- if (state.session_exists === false) {
949
- this.stop(threadId);
950
- return;
951
- }
952
- const backendState = this.config.backendStateRef.current;
953
- const backendThreadId = resolveThreadId(backendState, threadId);
954
- if (this.handleSystemEvents && state.system_events) {
955
- this.handleSystemEvents(backendThreadId, threadId, state.system_events);
1258
+ var _a;
1259
+ if (((_a = state.system_events) == null ? void 0 : _a.length) && this.config.onSyncEvents) {
1260
+ const backendState = this.config.backendStateRef.current;
1261
+ const sessionId = resolveThreadId(backendState, threadId);
1262
+ this.config.onSyncEvents(sessionId, state.system_events);
956
1263
  }
957
1264
  this.config.applyMessages(threadId, state.messages);
958
1265
  if (!state.is_processing) {
@@ -962,17 +1269,17 @@ var PollingController = class {
962
1269
  };
963
1270
 
964
1271
  // src/runtime/orchestrator.ts
965
- function useRuntimeOrchestrator(backendUrl, options) {
1272
+ function useRuntimeOrchestrator(backendApi, options) {
966
1273
  const threadContext = useThreadContext();
967
- const threadContextRef = (0, import_react3.useRef)(threadContext);
1274
+ const threadContextRef = (0, import_react5.useRef)(threadContext);
968
1275
  threadContextRef.current = threadContext;
969
- const backendApiRef = (0, import_react3.useRef)(new BackendApi(backendUrl));
970
- const backendStateRef = (0, import_react3.useRef)(createBakendState());
971
- const [isRunning, setIsRunning] = (0, import_react3.useState)(false);
972
- const messageControllerRef = (0, import_react3.useRef)(null);
973
- const pollingRef = (0, import_react3.useRef)(null);
974
- const systemEventsHandlerRef = (0, import_react3.useRef)(null);
975
- const inFlightRef = (0, import_react3.useRef)(/* @__PURE__ */ new Map());
1276
+ const backendApiRef = (0, import_react5.useRef)(backendApi);
1277
+ backendApiRef.current = backendApi;
1278
+ const backendStateRef = (0, import_react5.useRef)(createBackendState());
1279
+ const [isRunning, setIsRunning] = (0, import_react5.useState)(false);
1280
+ const messageControllerRef = (0, import_react5.useRef)(null);
1281
+ const pollingRef = (0, import_react5.useRef)(null);
1282
+ const pendingFetches = (0, import_react5.useRef)(/* @__PURE__ */ new Set());
976
1283
  if (!pollingRef.current) {
977
1284
  pollingRef.current = new PollingController({
978
1285
  backendApiRef,
@@ -981,6 +1288,8 @@ function useRuntimeOrchestrator(backendUrl, options) {
981
1288
  var _a;
982
1289
  (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, msgs);
983
1290
  },
1291
+ onSyncEvents: options == null ? void 0 : options.onSyncEvents,
1292
+ getUserState: options == null ? void 0 : options.getUserState,
984
1293
  onStart: (threadId) => {
985
1294
  if (threadContextRef.current.currentThreadId === threadId) {
986
1295
  setIsRunning(true);
@@ -1000,80 +1309,56 @@ function useRuntimeOrchestrator(backendUrl, options) {
1000
1309
  threadContextRef,
1001
1310
  polling: pollingRef.current,
1002
1311
  setGlobalIsRunning: setIsRunning,
1003
- getPublicKey: options == null ? void 0 : options.getPublicKey
1312
+ getPublicKey: options == null ? void 0 : options.getPublicKey,
1313
+ onSyncEvents: options == null ? void 0 : options.onSyncEvents
1004
1314
  });
1005
1315
  }
1006
- const setSystemEventsHandler = (0, import_react3.useCallback)((handler) => {
1007
- var _a;
1008
- systemEventsHandlerRef.current = handler;
1009
- (_a = pollingRef.current) == null ? void 0 : _a.setSystemEventsHandler(handler != null ? handler : void 0);
1010
- }, []);
1011
- (0, import_react3.useEffect)(() => {
1012
- return () => {
1013
- for (const controller of inFlightRef.current.values()) {
1014
- controller.abort();
1015
- }
1016
- inFlightRef.current.clear();
1017
- };
1018
- }, []);
1019
- const ensureInitialState = (0, import_react3.useCallback)(
1020
- async (threadId) => {
1021
- var _a, _b;
1022
- const backendState = backendStateRef.current;
1023
- if (shouldSkipInitialFetch(backendState, threadId)) {
1024
- clearSkipInitialFetch(backendState, threadId);
1025
- if (threadContextRef.current.currentThreadId === threadId) {
1026
- setIsRunning(false);
1027
- }
1028
- return;
1316
+ const ensureInitialState = (0, import_react5.useCallback)(async (threadId) => {
1317
+ var _a, _b, _c, _d;
1318
+ const backendState = backendStateRef.current;
1319
+ if (shouldSkipInitialFetch(backendState, threadId)) {
1320
+ clearSkipInitialFetch(backendState, threadId);
1321
+ if (threadContextRef.current.currentThreadId === threadId) {
1322
+ setIsRunning(false);
1029
1323
  }
1030
- if (!isThreadReady(backendState, threadId)) {
1031
- if (threadContextRef.current.currentThreadId === threadId) {
1032
- setIsRunning(false);
1033
- }
1034
- return;
1324
+ return;
1325
+ }
1326
+ if (!isThreadReady(backendState, threadId)) {
1327
+ if (threadContextRef.current.currentThreadId === threadId) {
1328
+ setIsRunning(false);
1035
1329
  }
1036
- if (inFlightRef.current.has(threadId)) {
1037
- return;
1330
+ return;
1331
+ }
1332
+ if (pendingFetches.current.has(threadId)) return;
1333
+ const backendThreadId = resolveThreadId(backendState, threadId);
1334
+ pendingFetches.current.add(threadId);
1335
+ try {
1336
+ const userState = (_a = options == null ? void 0 : options.getUserState) == null ? void 0 : _a.call(options);
1337
+ const state = await backendApiRef.current.fetchState(
1338
+ backendThreadId,
1339
+ userState
1340
+ );
1341
+ (_b = messageControllerRef.current) == null ? void 0 : _b.inbound(threadId, state.messages);
1342
+ if (((_c = state.system_events) == null ? void 0 : _c.length) && (options == null ? void 0 : options.onSyncEvents)) {
1343
+ options.onSyncEvents(backendThreadId, state.system_events);
1038
1344
  }
1039
- const backendThreadId = resolveThreadId(backendState, threadId);
1040
- const controller = new AbortController();
1041
- inFlightRef.current.set(threadId, controller);
1042
- try {
1043
- console.log("\u{1F535} [Orchestrator] Fetching initial state for threadId:", threadId);
1044
- const state = await backendApiRef.current.fetchState(backendThreadId, {
1045
- signal: controller.signal
1046
- });
1047
- (_a = messageControllerRef.current) == null ? void 0 : _a.inbound(threadId, state.messages);
1048
- if (systemEventsHandlerRef.current && state.system_events) {
1049
- systemEventsHandlerRef.current(backendThreadId, threadId, state.system_events);
1050
- }
1345
+ if (threadContextRef.current.currentThreadId === threadId) {
1051
1346
  if (state.is_processing) {
1052
- if (threadContextRef.current.currentThreadId === threadId) {
1053
- setIsRunning(true);
1054
- }
1055
- (_b = pollingRef.current) == null ? void 0 : _b.start(threadId);
1347
+ setIsRunning(true);
1348
+ (_d = pollingRef.current) == null ? void 0 : _d.start(threadId);
1056
1349
  } else {
1057
- if (threadContextRef.current.currentThreadId === threadId) {
1058
- setIsRunning(false);
1059
- }
1060
- }
1061
- } catch (error) {
1062
- if (controller.signal.aborted) {
1063
- return;
1064
- }
1065
- console.error("Failed to fetch initial state:", error);
1066
- if (threadContextRef.current.currentThreadId === threadId) {
1067
1350
  setIsRunning(false);
1068
1351
  }
1069
- } finally {
1070
- if (inFlightRef.current.get(threadId) === controller) {
1071
- inFlightRef.current.delete(threadId);
1072
- }
1073
1352
  }
1074
- },
1075
- [backendApiRef, backendStateRef, pollingRef, messageControllerRef, setIsRunning]
1076
- );
1353
+ } catch (error) {
1354
+ console.error("Failed to fetch initial state:", error);
1355
+ if (threadContextRef.current.currentThreadId === threadId) {
1356
+ setIsRunning(false);
1357
+ }
1358
+ } finally {
1359
+ pendingFetches.current.delete(threadId);
1360
+ }
1361
+ }, []);
1077
1362
  return {
1078
1363
  backendStateRef,
1079
1364
  polling: pollingRef.current,
@@ -1081,590 +1366,275 @@ function useRuntimeOrchestrator(backendUrl, options) {
1081
1366
  isRunning,
1082
1367
  setIsRunning,
1083
1368
  ensureInitialState,
1084
- setSystemEventsHandler,
1085
1369
  backendApiRef
1086
1370
  };
1087
1371
  }
1088
1372
 
1089
- // src/lib/notification-context.tsx
1090
- var import_react4 = require("react");
1091
- var import_jsx_runtime2 = require("react/jsx-runtime");
1092
- var NotificationContext = (0, import_react4.createContext)(
1093
- void 0
1094
- );
1095
- function useNotification() {
1096
- const context = (0, import_react4.useContext)(NotificationContext);
1097
- if (!context) {
1098
- throw new Error("useNotification must be used within NotificationProvider");
1099
- }
1100
- return context;
1101
- }
1102
- function NotificationProvider({ children }) {
1103
- const [notifications, setNotifications] = (0, import_react4.useState)([]);
1104
- const showNotification = (0, import_react4.useCallback)(
1105
- (notification) => {
1106
- var _a, _b;
1107
- const id = `notification-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
1108
- const newNotification = __spreadProps(__spreadValues({}, notification), {
1109
- id,
1110
- duration: (_a = notification.duration) != null ? _a : 5e3
1111
- });
1112
- setNotifications((prev) => [newNotification, ...prev]);
1113
- const duration = (_b = newNotification.duration) != null ? _b : 5e3;
1114
- if (duration > 0) {
1115
- setTimeout(() => {
1116
- setNotifications((prev) => prev.filter((n) => n.id !== id));
1117
- }, duration);
1118
- }
1119
- },
1120
- []
1373
+ // src/runtime/threadlist-adapter.ts
1374
+ var sortByLastActiveDesc = ([, metaA], [, metaB]) => {
1375
+ const tsA = parseTimestamp(metaA.lastActiveAt);
1376
+ const tsB = parseTimestamp(metaB.lastActiveAt);
1377
+ return tsB - tsA;
1378
+ };
1379
+ function buildThreadLists(threadMetadata) {
1380
+ const entries = Array.from(threadMetadata.entries()).filter(
1381
+ ([, meta]) => !isPlaceholderTitle(meta.title)
1121
1382
  );
1122
- const dismissNotification = (0, import_react4.useCallback)((id) => {
1123
- setNotifications((prev) => prev.filter((n) => n.id !== id));
1124
- }, []);
1125
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
1126
- NotificationContext.Provider,
1127
- {
1128
- value: { showNotification, notifications, dismissNotification },
1129
- children
1130
- }
1383
+ const regularThreads = entries.filter(([, meta]) => meta.status !== "archived").sort(sortByLastActiveDesc).map(
1384
+ ([id, meta]) => ({
1385
+ id,
1386
+ title: meta.title || "New Chat",
1387
+ status: "regular"
1388
+ })
1131
1389
  );
1390
+ const archivedThreads = entries.filter(([, meta]) => meta.status === "archived").sort(sortByLastActiveDesc).map(
1391
+ ([id, meta]) => ({
1392
+ id,
1393
+ title: meta.title || "New Chat",
1394
+ status: "archived"
1395
+ })
1396
+ );
1397
+ return { regularThreads, archivedThreads };
1132
1398
  }
1133
-
1134
- // src/utils/wallet.ts
1135
- var import_react5 = require("react");
1136
- var getNetworkName = (chainId) => {
1137
- if (chainId === void 0) return "";
1138
- const id = typeof chainId === "string" ? Number(chainId) : chainId;
1139
- switch (id) {
1140
- case 1:
1141
- return "ethereum";
1142
- case 137:
1143
- return "polygon";
1144
- case 42161:
1145
- return "arbitrum";
1146
- case 8453:
1147
- return "base";
1148
- case 10:
1149
- return "optimism";
1150
- case 11155111:
1151
- return "sepolia";
1152
- case 1337:
1153
- case 31337:
1154
- return "testnet";
1155
- case 59140:
1156
- return "linea-sepolia";
1157
- case 59144:
1158
- return "linea";
1159
- default:
1160
- return "testnet";
1161
- }
1162
- };
1163
- var formatAddress = (addr) => addr ? `${addr.slice(0, 6)}...${addr.slice(-4)}` : "Connect Wallet";
1164
- function normalizeWalletError(error) {
1165
- var _a, _b, _c, _d, _e, _f, _g;
1166
- const e = error;
1167
- const cause = (_a = e == null ? void 0 : e.cause) != null ? _a : null;
1168
- const code = (_b = typeof (e == null ? void 0 : e.code) === "number" ? e.code : void 0) != null ? _b : typeof (cause == null ? void 0 : cause.code) === "number" ? cause.code : void 0;
1169
- const name = (_c = typeof (e == null ? void 0 : e.name) === "string" ? e.name : void 0) != null ? _c : typeof (cause == null ? void 0 : cause.name) === "string" ? cause.name : void 0;
1170
- const msg = (_g = (_f = (_e = (_d = typeof (e == null ? void 0 : e.shortMessage) === "string" ? e.shortMessage : void 0) != null ? _d : typeof (cause == null ? void 0 : cause.shortMessage) === "string" ? cause.shortMessage : void 0) != null ? _e : typeof (e == null ? void 0 : e.message) === "string" ? e.message : void 0) != null ? _f : typeof (cause == null ? void 0 : cause.message) === "string" ? cause.message : void 0) != null ? _g : "Unknown wallet error";
1171
- const rejected = code === 4001 || name === "UserRejectedRequestError" || name === "RejectedRequestError" || /user rejected|rejected the request|denied|request rejected|canceled|cancelled/i.test(
1172
- msg
1173
- );
1174
- return { rejected, message: msg };
1175
- }
1176
- function toHexQuantity(value) {
1177
- const trimmed = value.trim();
1178
- const asBigInt = BigInt(trimmed);
1179
- return `0x${asBigInt.toString(16)}`;
1180
- }
1181
- async function pickInjectedProvider(publicKey) {
1182
- const ethereum = globalThis.ethereum;
1183
- if (!(ethereum == null ? void 0 : ethereum.request)) return void 0;
1184
- const candidates = Array.isArray(ethereum.providers) ? ethereum.providers.filter(
1185
- (p) => !!(p == null ? void 0 : p.request)
1186
- ) : [ethereum];
1187
- const target = publicKey == null ? void 0 : publicKey.toLowerCase();
1188
- if (target) {
1189
- for (const candidate of candidates) {
1190
- try {
1191
- const accounts = await candidate.request({
1192
- method: "eth_accounts"
1193
- });
1194
- const list = Array.isArray(accounts) ? accounts.map((a) => String(a).toLowerCase()) : [];
1195
- if (list.includes(target)) return candidate;
1196
- } catch (e) {
1197
- }
1198
- }
1199
- }
1200
- return candidates[0];
1201
- }
1202
- function WalletSystemMessageEmitter({
1203
- wallet
1399
+ function buildThreadListAdapter({
1400
+ backendStateRef,
1401
+ backendApiRef,
1402
+ threadContext,
1403
+ currentThreadIdRef,
1404
+ polling,
1405
+ userAddress,
1406
+ setIsRunning
1204
1407
  }) {
1205
- const { sendSystemMessage } = useRuntimeActions();
1206
- const lastWalletRef = (0, import_react5.useRef)({ isConnected: false });
1207
- (0, import_react5.useEffect)(() => {
1208
- const prev = lastWalletRef.current;
1209
- const { address, chainId, isConnected } = wallet;
1210
- const normalizedAddress = address == null ? void 0 : address.toLowerCase();
1211
- if (isConnected && normalizedAddress && chainId && (!prev.isConnected || prev.address !== normalizedAddress)) {
1212
- const networkName = getNetworkName(chainId);
1213
- const message = `User connected wallet with address ${normalizedAddress} on ${networkName} network (Chain ID: ${chainId}). Ready to help with transactions.`;
1214
- console.log(message);
1215
- void sendSystemMessage(message);
1216
- lastWalletRef.current = {
1217
- isConnected: true,
1218
- address: normalizedAddress,
1219
- chainId
1220
- };
1221
- return;
1222
- }
1223
- if (!isConnected && prev.isConnected) {
1224
- void sendSystemMessage("Wallet disconnected by user.");
1225
- console.log("Wallet disconnected by user.");
1226
- lastWalletRef.current = { isConnected: false };
1227
- return;
1408
+ const backendState = backendStateRef.current;
1409
+ const { regularThreads, archivedThreads } = buildThreadLists(
1410
+ threadContext.allThreadsMetadata
1411
+ );
1412
+ const preparePendingThread = (threadId) => {
1413
+ const previousPendingId = backendState.creatingThreadId;
1414
+ if (previousPendingId && previousPendingId !== threadId) {
1415
+ threadContext.setThreadMetadata((prev) => {
1416
+ const next = new Map(prev);
1417
+ next.delete(previousPendingId);
1418
+ return next;
1419
+ });
1420
+ threadContext.setThreads((prev) => {
1421
+ const next = new Map(prev);
1422
+ next.delete(previousPendingId);
1423
+ return next;
1424
+ });
1425
+ backendState.pendingChat.delete(previousPendingId);
1426
+ backendState.skipInitialFetch.delete(previousPendingId);
1228
1427
  }
1229
- if (isConnected && normalizedAddress && chainId && prev.isConnected && prev.address === normalizedAddress && prev.chainId !== chainId) {
1230
- const networkName = getNetworkName(chainId);
1231
- const message = `User switched wallet to ${networkName} network (Chain ID: ${chainId}).`;
1232
- console.log(message);
1233
- void sendSystemMessage(message);
1234
- lastWalletRef.current = {
1235
- isConnected: true,
1236
- address: normalizedAddress,
1237
- chainId
1238
- };
1428
+ backendState.creatingThreadId = threadId;
1429
+ backendState.pendingChat.delete(threadId);
1430
+ threadContext.setThreadMetadata(
1431
+ (prev) => new Map(prev).set(threadId, {
1432
+ title: "New Chat",
1433
+ status: "pending",
1434
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1435
+ })
1436
+ );
1437
+ threadContext.setThreadMessages(threadId, []);
1438
+ threadContext.setCurrentThreadId(threadId);
1439
+ setIsRunning(false);
1440
+ threadContext.bumpThreadViewKey();
1441
+ };
1442
+ const findPendingThreadId = () => {
1443
+ if (backendState.creatingThreadId) return backendState.creatingThreadId;
1444
+ for (const [id, meta] of threadContext.allThreadsMetadata.entries()) {
1445
+ if (meta.status === "pending") return id;
1239
1446
  }
1240
- }, [wallet, sendSystemMessage]);
1241
- return null;
1242
- }
1243
-
1244
- // src/runtime/wallet-handler.ts
1245
- var WalletHandler = class {
1246
- constructor(config) {
1247
- this.config = config;
1248
- this.handledRequests = /* @__PURE__ */ new Set();
1249
- this.queue = [];
1250
- this.inFlight = false;
1251
- }
1252
- handleRequest(sessionId, threadId, request) {
1253
- if (this.config.getCurrentThreadId() !== threadId) return;
1254
- const description = request.description || request.topic || "Wallet transaction requested";
1255
- this.config.showNotification({
1256
- type: "notice",
1257
- iconType: "wallet",
1258
- title: "Transaction Request",
1259
- message: description
1260
- });
1261
- this.enqueue(sessionId, threadId, request);
1262
- void this.drain();
1263
- }
1264
- enqueue(sessionId, threadId, request) {
1265
- var _a;
1266
- const key = `${sessionId}:${(_a = request.timestamp) != null ? _a : JSON.stringify(request)}`;
1267
- if (this.handledRequests.has(key)) return;
1268
- this.handledRequests.add(key);
1269
- this.queue.push({ sessionId, threadId, request });
1270
- }
1271
- async drain() {
1272
- var _a, _b, _c;
1273
- if (this.inFlight) return;
1274
- const next = this.queue.shift();
1275
- if (!next) return;
1276
- this.inFlight = true;
1277
- try {
1278
- if (this.config.onWalletTxRequest) {
1279
- const txHash2 = await this.config.onWalletTxRequest(next.request, {
1280
- sessionId: next.sessionId,
1281
- threadId: next.threadId,
1282
- publicKey: this.config.publicKey
1283
- });
1284
- this.config.showNotification({
1285
- type: "success",
1286
- iconType: "transaction",
1287
- title: "Transaction Sent",
1288
- message: `Hash: ${txHash2}`
1289
- });
1290
- await this.config.backendApiRef.current.postSystemMessage(
1291
- next.sessionId,
1292
- `Transaction sent: ${txHash2}`
1293
- );
1294
- await this.refreshThreadIfCurrent(next.sessionId, next.threadId);
1447
+ return null;
1448
+ };
1449
+ return {
1450
+ threadId: threadContext.currentThreadId,
1451
+ threads: regularThreads,
1452
+ archivedThreads,
1453
+ onSwitchToNewThread: async () => {
1454
+ var _a;
1455
+ const pendingId = findPendingThreadId();
1456
+ if (pendingId) {
1457
+ preparePendingThread(pendingId);
1295
1458
  return;
1296
1459
  }
1297
- const activeProvider = await pickInjectedProvider(this.config.publicKey);
1298
- if (!(activeProvider == null ? void 0 : activeProvider.request)) {
1299
- this.config.showNotification({
1300
- type: "error",
1301
- iconType: "wallet",
1302
- title: "Wallet Not Found",
1303
- message: "No wallet provider found (window.ethereum missing)."
1304
- });
1305
- await this.config.backendApiRef.current.postSystemMessage(
1306
- next.sessionId,
1307
- "No wallet provider found (window.ethereum missing)."
1460
+ if (backendState.createThreadPromise) {
1461
+ preparePendingThread(
1462
+ (_a = backendState.creatingThreadId) != null ? _a : crypto.randomUUID()
1308
1463
  );
1309
1464
  return;
1310
1465
  }
1311
- const accounts = await activeProvider.request({
1312
- method: "eth_accounts"
1313
- });
1314
- const addresses = Array.isArray(accounts) ? accounts.map(String) : [];
1315
- const from = this.config.publicKey || addresses[0];
1316
- if (!from) {
1317
- await activeProvider.request({ method: "eth_requestAccounts" });
1318
- }
1319
- const fromAddress = this.config.publicKey || await activeProvider.request({ method: "eth_accounts" });
1320
- const resolvedFrom = this.config.publicKey || (Array.isArray(fromAddress) ? String((_a = fromAddress[0]) != null ? _a : "") : "");
1321
- if (!resolvedFrom) {
1322
- this.config.showNotification({
1323
- type: "error",
1324
- iconType: "wallet",
1325
- title: "Wallet Not Connected",
1326
- message: "Please connect a wallet to sign the requested transaction."
1466
+ const threadId = crypto.randomUUID();
1467
+ preparePendingThread(threadId);
1468
+ const createPromise = backendApiRef.current.createThread(threadId, userAddress).then(async (newThread) => {
1469
+ var _a2;
1470
+ const uiThreadId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
1471
+ const backendId = newThread.session_id;
1472
+ if (uiThreadId !== backendId) {
1473
+ console.warn("[aomi][thread] backend id mismatch", {
1474
+ uiThreadId,
1475
+ backendId
1476
+ });
1477
+ }
1478
+ markSkipInitialFetch(backendState, uiThreadId);
1479
+ threadContext.setThreadMetadata((prev) => {
1480
+ var _a3, _b;
1481
+ const next = new Map(prev);
1482
+ const existing = next.get(uiThreadId);
1483
+ const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1484
+ next.set(uiThreadId, {
1485
+ title: (_a3 = existing == null ? void 0 : existing.title) != null ? _a3 : "New Chat",
1486
+ status: nextStatus,
1487
+ lastActiveAt: (_b = existing == null ? void 0 : existing.lastActiveAt) != null ? _b : (/* @__PURE__ */ new Date()).toISOString()
1488
+ });
1489
+ return next;
1327
1490
  });
1328
- await this.config.backendApiRef.current.postSystemMessage(
1329
- next.sessionId,
1330
- "Wallet is not connected; please connect a wallet to sign the requested transaction."
1331
- );
1332
- return;
1333
- }
1334
- const gas = (_c = (_b = next.request.gas) != null ? _b : next.request.gas_limit) != null ? _c : void 0;
1335
- let valueHex;
1336
- let gasHex;
1491
+ if (backendState.creatingThreadId === uiThreadId) {
1492
+ backendState.creatingThreadId = null;
1493
+ }
1494
+ const pendingMessages = backendState.pendingChat.get(uiThreadId);
1495
+ if (pendingMessages == null ? void 0 : pendingMessages.length) {
1496
+ backendState.pendingChat.delete(uiThreadId);
1497
+ for (const text of pendingMessages) {
1498
+ try {
1499
+ await backendApiRef.current.postChatMessage(backendId, text);
1500
+ } catch (error) {
1501
+ console.error("Failed to send queued message:", error);
1502
+ }
1503
+ }
1504
+ if (currentThreadIdRef.current === uiThreadId) {
1505
+ polling == null ? void 0 : polling.start(uiThreadId);
1506
+ }
1507
+ }
1508
+ }).catch((error) => {
1509
+ var _a2;
1510
+ console.error("Failed to create new thread:", error);
1511
+ const failedId = (_a2 = backendState.creatingThreadId) != null ? _a2 : threadId;
1512
+ threadContext.setThreadMetadata((prev) => {
1513
+ const next = new Map(prev);
1514
+ next.delete(failedId);
1515
+ return next;
1516
+ });
1517
+ threadContext.setThreads((prev) => {
1518
+ const next = new Map(prev);
1519
+ next.delete(failedId);
1520
+ return next;
1521
+ });
1522
+ if (backendState.creatingThreadId === failedId) {
1523
+ backendState.creatingThreadId = null;
1524
+ }
1525
+ }).finally(() => {
1526
+ backendState.createThreadPromise = null;
1527
+ });
1528
+ backendState.createThreadPromise = createPromise;
1529
+ },
1530
+ onSwitchToThread: (threadId) => {
1531
+ threadContext.setCurrentThreadId(threadId);
1532
+ },
1533
+ onRename: async (threadId, newTitle) => {
1534
+ var _a, _b;
1535
+ const previousTitle = (_b = (_a = threadContext.getThreadMetadata(threadId)) == null ? void 0 : _a.title) != null ? _b : "";
1536
+ const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1537
+ threadContext.updateThreadMetadata(threadId, {
1538
+ title: normalizedTitle
1539
+ });
1337
1540
  try {
1338
- valueHex = toHexQuantity(next.request.value);
1339
- if (gas) gasHex = toHexQuantity(gas);
1541
+ await backendApiRef.current.renameThread(threadId, newTitle);
1340
1542
  } catch (error) {
1341
- this.config.showNotification({
1342
- type: "error",
1343
- iconType: "transaction",
1344
- title: "Invalid Transaction",
1345
- message: error.message
1543
+ console.error("Failed to rename thread:", error);
1544
+ threadContext.updateThreadMetadata(threadId, {
1545
+ title: previousTitle
1346
1546
  });
1347
- await this.config.backendApiRef.current.postSystemMessage(
1348
- next.sessionId,
1349
- `Invalid wallet transaction request payload: ${error.message}`
1350
- );
1351
- return;
1352
1547
  }
1353
- const txParams = __spreadValues({
1354
- from: resolvedFrom,
1355
- to: next.request.to,
1356
- value: valueHex,
1357
- data: next.request.data
1358
- }, gasHex ? { gas: gasHex } : {});
1359
- const txHash = await activeProvider.request({
1360
- method: "eth_sendTransaction",
1361
- params: [txParams]
1362
- });
1363
- this.config.showNotification({
1364
- type: "success",
1365
- title: "Transaction sent",
1366
- message: `Transaction hash: ${txHash}`
1367
- });
1368
- await this.config.backendApiRef.current.postSystemMessage(
1369
- next.sessionId,
1370
- `Transaction sent: ${txHash}`
1371
- );
1372
- await this.refreshThreadIfCurrent(next.sessionId, next.threadId);
1373
- } catch (error) {
1374
- const normalized = normalizeWalletError(error);
1375
- const final = normalized.rejected ? "Transaction rejected by user." : `Transaction failed: ${normalized.message}`;
1376
- this.config.showNotification({
1377
- type: normalized.rejected ? "notice" : "error",
1378
- iconType: normalized.rejected ? "transaction" : "error",
1379
- title: normalized.rejected ? "Transaction Rejected" : "Transaction Failed",
1380
- message: normalized.rejected ? "Transaction was rejected by user." : normalized.message
1381
- });
1548
+ },
1549
+ onArchive: async (threadId) => {
1550
+ threadContext.updateThreadMetadata(threadId, { status: "archived" });
1382
1551
  try {
1383
- await this.config.backendApiRef.current.postSystemMessage(
1384
- next.sessionId,
1385
- final
1386
- );
1387
- await this.refreshThreadIfCurrent(next.sessionId, next.threadId);
1388
- } catch (postError) {
1389
- console.error("Failed to report wallet tx result to backend:", postError);
1552
+ await backendApiRef.current.archiveThread(threadId);
1553
+ } catch (error) {
1554
+ console.error("Failed to archive thread:", error);
1555
+ threadContext.updateThreadMetadata(threadId, { status: "regular" });
1390
1556
  }
1391
- } finally {
1392
- this.inFlight = false;
1393
- void this.drain();
1394
- }
1395
- }
1396
- async refreshThreadIfCurrent(sessionId, threadId) {
1397
- if (this.config.getCurrentThreadId() !== threadId) return;
1398
- try {
1399
- const state = await this.config.backendApiRef.current.fetchState(sessionId);
1400
- this.config.applySessionMessagesToThread(threadId, state.messages);
1401
- } catch (refreshError) {
1402
- console.error("Failed to refresh state after wallet tx:", refreshError);
1403
- }
1404
- }
1405
- };
1406
-
1407
- // src/runtime/event-controller.ts
1408
- var EventController = class {
1409
- constructor(config) {
1410
- this.config = config;
1411
- this.lastEventIdBySession = /* @__PURE__ */ new Map();
1412
- this.eventsInFlight = /* @__PURE__ */ new Set();
1413
- this.updateSubscriptions = /* @__PURE__ */ new Map();
1414
- this.subscribableSessionId = null;
1415
- }
1416
- setSubscribableSessionId(sessionId) {
1417
- console.log("\u{1F514} [updates] setSubscribableSessionId", { sessionId });
1418
- this.subscribableSessionId = sessionId;
1419
- this.syncSubscriptions();
1420
- }
1421
- syncSubscriptions() {
1422
- console.log("\u{1F514} [updates] syncSubscriptions", {
1423
- subscribableSessionId: this.subscribableSessionId,
1424
- active: Array.from(this.updateSubscriptions.keys())
1425
- });
1426
- const nextSessions = /* @__PURE__ */ new Set();
1427
- if (this.subscribableSessionId) {
1428
- nextSessions.add(this.subscribableSessionId);
1429
- }
1430
- for (const sessionId of this.updateSubscriptions.keys()) {
1431
- if (!nextSessions.has(sessionId)) {
1432
- this.removeSubscription(sessionId);
1557
+ },
1558
+ onUnarchive: async (threadId) => {
1559
+ threadContext.updateThreadMetadata(threadId, { status: "regular" });
1560
+ try {
1561
+ await backendApiRef.current.unarchiveThread(threadId);
1562
+ } catch (error) {
1563
+ console.error("Failed to unarchive thread:", error);
1564
+ threadContext.updateThreadMetadata(threadId, { status: "archived" });
1433
1565
  }
1434
- }
1435
- for (const sessionId of nextSessions) {
1436
- this.ensureSubscription(sessionId);
1437
- }
1438
- }
1439
- ensureSubscription(sessionId) {
1440
- if (this.updateSubscriptions.has(sessionId)) return;
1441
- console.log("\u{1F514} [updates] ensureSubscription", { sessionId });
1442
- const unsubscribe = this.config.backendApiRef.current.subscribeToUpdates(
1443
- sessionId,
1444
- (update) => {
1445
- console.log("\u{1F514} [updates] event_available", {
1446
- sessionId: update.session_id,
1447
- eventId: update.event_id,
1448
- eventType: update.event_type
1566
+ },
1567
+ onDelete: async (threadId) => {
1568
+ try {
1569
+ await backendApiRef.current.deleteThread(threadId);
1570
+ threadContext.setThreadMetadata((prev) => {
1571
+ const next = new Map(prev);
1572
+ next.delete(threadId);
1573
+ return next;
1449
1574
  });
1450
- if (update.type !== "event_available") return;
1451
- void this.drainEvents(update.session_id);
1452
- },
1453
- (error) => {
1454
- console.error("Failed to handle system update SSE:", error);
1455
- }
1456
- );
1457
- this.updateSubscriptions.set(sessionId, unsubscribe);
1458
- }
1459
- removeSubscription(sessionId) {
1460
- const unsubscribe = this.updateSubscriptions.get(sessionId);
1461
- if (!unsubscribe) return;
1462
- unsubscribe();
1463
- this.updateSubscriptions.delete(sessionId);
1464
- }
1465
- async drainEvents(sessionId) {
1466
- var _a;
1467
- if (this.eventsInFlight.has(sessionId)) {
1468
- console.log("\u23F3 [events] drain already in flight", { sessionId });
1469
- return;
1470
- }
1471
- this.eventsInFlight.add(sessionId);
1472
- try {
1473
- let afterId = (_a = this.lastEventIdBySession.get(sessionId)) != null ? _a : 0;
1474
- console.log("\u{1F4E5} [events] start drain", { sessionId, afterId });
1475
- for (; ; ) {
1476
- const requestAfterId = afterId;
1477
- const events = await this.config.backendApiRef.current.fetchEventsAfter(
1478
- sessionId,
1479
- requestAfterId,
1480
- 200
1481
- );
1482
- console.log("\u{1F4E5} [events] fetched batch", {
1483
- sessionId,
1484
- afterId: requestAfterId,
1485
- count: events.length
1575
+ threadContext.setThreads((prev) => {
1576
+ const next = new Map(prev);
1577
+ next.delete(threadId);
1578
+ return next;
1486
1579
  });
1487
- if (!events.length) break;
1488
- for (const event of events) {
1489
- const eventId = typeof event.event_id === "number" ? event.event_id : Number(event.event_id);
1490
- if (Number.isFinite(eventId)) afterId = Math.max(afterId, eventId);
1491
- if (event.type === "title_changed" && typeof event.new_title === "string") {
1492
- console.log("\u{1F3F7}\uFE0F [events] title_changed", {
1493
- sessionId,
1494
- eventId,
1495
- newTitle: event.new_title
1496
- });
1497
- this.applyTitleChanged(sessionId, event.new_title);
1498
- }
1499
- if (event.type === "wallet_tx_request") {
1500
- const payload = event.payload;
1501
- if (payload && typeof payload === "object") {
1502
- const req = payload;
1503
- if (typeof req.to === "string" && typeof req.value === "string" && typeof req.data === "string") {
1504
- const threadId = findTempIdForBackendId(
1505
- this.config.backendStateRef.current,
1506
- sessionId
1507
- ) || sessionId;
1508
- this.config.handleWalletTxRequest(
1509
- sessionId,
1510
- threadId,
1511
- req
1512
- );
1513
- }
1514
- }
1515
- }
1580
+ backendState.pendingChat.delete(threadId);
1581
+ backendState.skipInitialFetch.delete(threadId);
1582
+ backendState.runningThreads.delete(threadId);
1583
+ if (backendState.creatingThreadId === threadId) {
1584
+ backendState.creatingThreadId = null;
1516
1585
  }
1517
- if (events.length < 200) break;
1518
- }
1519
- console.log("\u{1F4E5} [events] drain complete", { sessionId, afterId });
1520
- this.lastEventIdBySession.set(sessionId, afterId);
1521
- } catch (error) {
1522
- console.error("Failed to fetch async events:", error);
1523
- } finally {
1524
- this.eventsInFlight.delete(sessionId);
1525
- }
1526
- }
1527
- applyTitleChanged(sessionId, newTitle) {
1528
- const backendState = this.config.backendStateRef.current;
1529
- const tempId = findTempIdForBackendId(backendState, sessionId);
1530
- const threadIdToUpdate = tempId || sessionId;
1531
- console.log("\u{1F3F7}\uFE0F [title] applying", {
1532
- sessionId,
1533
- threadId: threadIdToUpdate,
1534
- newTitle
1535
- });
1536
- this.config.setThreadMetadata((prev) => {
1537
- var _a;
1538
- const next = new Map(prev);
1539
- const existing = next.get(threadIdToUpdate);
1540
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1541
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1542
- next.set(threadIdToUpdate, {
1543
- title: normalizedTitle,
1544
- status: nextStatus,
1545
- lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString()
1546
- });
1547
- return next;
1548
- });
1549
- if (!isPlaceholderTitle(newTitle) && backendState.creatingThreadId === threadIdToUpdate) {
1550
- backendState.creatingThreadId = null;
1551
- }
1552
- }
1553
- handleBackendSystemEvents(sessionId, threadId, rawEvents) {
1554
- if (!(rawEvents == null ? void 0 : rawEvents.length)) return;
1555
- for (const raw of rawEvents) {
1556
- const parsed = this.parseBackendSystemEvent(raw);
1557
- if (!parsed) continue;
1558
- if ("InlineDisplay" in parsed) {
1559
- const payload = parsed.InlineDisplay;
1560
- if (!payload || typeof payload !== "object") continue;
1561
- const type = payload.type;
1562
- if (type !== "wallet_tx_request") continue;
1563
- const requestValue = payload.payload;
1564
- if (!requestValue || typeof requestValue !== "object") continue;
1565
- const req = requestValue;
1566
- if (typeof req.to !== "string" || typeof req.value !== "string" || typeof req.data !== "string") {
1567
- continue;
1586
+ if (threadContext.currentThreadId === threadId) {
1587
+ const firstRegularThread = Array.from(
1588
+ threadContext.allThreadsMetadata.entries()
1589
+ ).find(([id, meta]) => meta.status === "regular" && id !== threadId);
1590
+ if (firstRegularThread) {
1591
+ threadContext.setCurrentThreadId(firstRegularThread[0]);
1592
+ } else {
1593
+ const defaultId = "default-session";
1594
+ threadContext.setThreadMetadata(
1595
+ (prev) => new Map(prev).set(defaultId, {
1596
+ title: "New Chat",
1597
+ status: "regular",
1598
+ lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1599
+ })
1600
+ );
1601
+ threadContext.setThreadMessages(defaultId, []);
1602
+ threadContext.setCurrentThreadId(defaultId);
1603
+ }
1568
1604
  }
1569
- this.config.handleWalletTxRequest(
1570
- sessionId,
1571
- threadId,
1572
- req
1573
- );
1574
- }
1575
- if ("SystemError" in parsed) {
1576
- this.config.showNotification({
1577
- type: "error",
1578
- iconType: "error",
1579
- title: "Error",
1580
- message: parsed.SystemError
1581
- });
1582
- }
1583
- if ("SystemNotice" in parsed) {
1584
- this.config.showNotification({
1585
- type: "notice",
1586
- iconType: "notice",
1587
- title: "Notice",
1588
- message: parsed.SystemNotice
1589
- });
1605
+ } catch (error) {
1606
+ console.error("Failed to delete thread:", error);
1607
+ throw error;
1590
1608
  }
1591
1609
  }
1592
- }
1593
- parseBackendSystemEvent(value) {
1594
- if (!value || typeof value !== "object") return null;
1595
- const entries = Object.entries(value);
1596
- if (entries.length !== 1) return null;
1597
- const [key, payload] = entries[0];
1598
- switch (key) {
1599
- case "InlineDisplay":
1600
- return { InlineDisplay: payload };
1601
- case "SystemNotice":
1602
- return {
1603
- SystemNotice: typeof payload === "string" ? payload : String(payload)
1604
- };
1605
- case "SystemError":
1606
- return {
1607
- SystemError: typeof payload === "string" ? payload : String(payload)
1608
- };
1609
- case "AsyncUpdate":
1610
- return { AsyncUpdate: payload };
1611
- default:
1612
- return null;
1613
- }
1614
- }
1615
- cleanup() {
1616
- for (const unsubscribe of this.updateSubscriptions.values()) {
1617
- unsubscribe();
1618
- }
1619
- this.updateSubscriptions.clear();
1620
- }
1621
- };
1610
+ };
1611
+ }
1622
1612
 
1623
- // src/runtime/aomi-runtime.tsx
1624
- var import_jsx_runtime3 = require("react/jsx-runtime");
1625
- var sortByLastActiveDesc = ([, metaA], [, metaB]) => {
1626
- const tsA = parseTimestamp2(metaA.lastActiveAt);
1627
- const tsB = parseTimestamp2(metaB.lastActiveAt);
1628
- return tsB - tsA;
1629
- };
1630
- function buildThreadLists(threadMetadata) {
1631
- const entries = Array.from(threadMetadata.entries()).filter(
1632
- ([, meta]) => !isPlaceholderTitle(meta.title)
1633
- );
1634
- const regularThreads = entries.filter(([, meta]) => meta.status === "regular").sort(sortByLastActiveDesc).map(([id, meta]) => ({
1635
- id,
1636
- title: meta.title || "New Chat",
1637
- status: "regular"
1638
- }));
1639
- const archivedThreads = entries.filter(([, meta]) => meta.status === "archived").sort(sortByLastActiveDesc).map(([id, meta]) => ({
1640
- id,
1641
- title: meta.title || "New Chat",
1642
- status: "archived"
1643
- }));
1644
- return { regularThreads, archivedThreads };
1613
+ // src/interface.tsx
1614
+ var import_react6 = require("react");
1615
+ var AomiRuntimeContext = (0, import_react6.createContext)(null);
1616
+ var AomiRuntimeApiProvider = AomiRuntimeContext.Provider;
1617
+ function useAomiRuntime() {
1618
+ const context = (0, import_react6.useContext)(AomiRuntimeContext);
1619
+ if (!context) {
1620
+ throw new Error(
1621
+ "useAomiRuntime must be used within AomiRuntimeProvider. Wrap your app with <AomiRuntimeProvider>...</AomiRuntimeProvider>"
1622
+ );
1623
+ }
1624
+ return context;
1645
1625
  }
1646
- function AomiRuntimeProvider({
1626
+
1627
+ // src/runtime/core.tsx
1628
+ var import_jsx_runtime5 = require("react/jsx-runtime");
1629
+ function AomiRuntimeCore({
1647
1630
  children,
1648
- backendUrl = "http://localhost:8080",
1649
- publicKey,
1650
- onWalletTxRequest
1631
+ backendApi
1651
1632
  }) {
1652
1633
  const threadContext = useThreadContext();
1653
- const threadContextRef = (0, import_react6.useRef)(threadContext);
1654
- threadContextRef.current = threadContext;
1655
- const currentThreadIdRef = (0, import_react6.useRef)(threadContext.currentThreadId);
1656
- (0, import_react6.useEffect)(() => {
1657
- currentThreadIdRef.current = threadContext.currentThreadId;
1658
- }, [threadContext.currentThreadId]);
1659
- const publicKeyRef = (0, import_react6.useRef)(publicKey);
1660
- (0, import_react6.useEffect)(() => {
1661
- publicKeyRef.current = publicKey;
1662
- }, [publicKey]);
1663
- const getPublicKey = (0, import_react6.useCallback)(() => publicKeyRef.current, []);
1664
- const lastSubscribedThreadRef = (0, import_react6.useRef)(null);
1665
- const { showNotification } = useNotification();
1666
- const eventControllerRef = (0, import_react6.useRef)(null);
1667
- const walletHandlerRef = (0, import_react6.useRef)(null);
1634
+ const eventContext = useEventContext();
1635
+ const notificationContext = useNotification();
1636
+ const { dispatchInboundSystem: dispatchSystemEvents } = eventContext;
1637
+ const { user, onUserStateChange, getUserState } = useUser();
1668
1638
  const {
1669
1639
  backendStateRef,
1670
1640
  polling,
@@ -1672,72 +1642,66 @@ function AomiRuntimeProvider({
1672
1642
  isRunning,
1673
1643
  setIsRunning,
1674
1644
  ensureInitialState,
1675
- setSystemEventsHandler,
1676
1645
  backendApiRef
1677
- } = useRuntimeOrchestrator(backendUrl, { getPublicKey });
1678
- if (!walletHandlerRef.current) {
1679
- walletHandlerRef.current = new WalletHandler({
1680
- backendApiRef,
1681
- onWalletTxRequest,
1682
- publicKey,
1683
- showNotification,
1684
- applySessionMessagesToThread: (threadId, msgs) => {
1685
- messageController.inbound(threadId, msgs);
1686
- },
1687
- getCurrentThreadId: () => currentThreadIdRef.current
1688
- });
1689
- }
1690
- if (!eventControllerRef.current) {
1691
- eventControllerRef.current = new EventController({
1692
- backendApiRef,
1693
- backendStateRef,
1694
- showNotification,
1695
- handleWalletTxRequest: (sessionId, threadId, request) => {
1696
- var _a;
1697
- (_a = walletHandlerRef.current) == null ? void 0 : _a.handleRequest(sessionId, threadId, request);
1698
- },
1699
- setThreadMetadata: threadContext.setThreadMetadata
1646
+ } = useRuntimeOrchestrator(backendApi, {
1647
+ onSyncEvents: dispatchSystemEvents,
1648
+ getPublicKey: () => getUserState().address,
1649
+ getUserState
1650
+ });
1651
+ (0, import_react7.useEffect)(() => {
1652
+ const unsubscribe = onUserStateChange(async (newUser) => {
1653
+ const sessionId = threadContext.currentThreadId;
1654
+ const message = JSON.stringify({
1655
+ type: "wallet:state_changed",
1656
+ payload: {
1657
+ address: newUser.address,
1658
+ chainId: newUser.chainId,
1659
+ isConnected: newUser.isConnected,
1660
+ ensName: newUser.ensName
1661
+ }
1662
+ });
1663
+ await backendApiRef.current.postSystemMessage(sessionId, message);
1700
1664
  });
1701
- }
1702
- const handleSystemEvents = (0, import_react6.useCallback)(
1703
- (sessionId, threadId, events) => {
1704
- var _a;
1705
- (_a = eventControllerRef.current) == null ? void 0 : _a.handleBackendSystemEvents(sessionId, threadId, events);
1706
- },
1707
- []
1708
- );
1709
- (0, import_react6.useEffect)(() => {
1710
- setSystemEventsHandler(handleSystemEvents);
1711
- return () => {
1712
- setSystemEventsHandler(null);
1713
- };
1714
- }, [handleSystemEvents, setSystemEventsHandler]);
1715
- const [updateSubscriptionsTick, setUpdateSubscriptionsTick] = (0, import_react6.useState)(0);
1716
- const bumpUpdateSubscriptions = (0, import_react6.useCallback)(() => {
1717
- setUpdateSubscriptionsTick((prev) => prev + 1);
1718
- }, []);
1719
- (0, import_react6.useEffect)(() => {
1665
+ return unsubscribe;
1666
+ }, [onUserStateChange, backendApiRef, threadContext.currentThreadId]);
1667
+ const threadContextRef = (0, import_react7.useRef)(threadContext);
1668
+ threadContextRef.current = threadContext;
1669
+ const currentThreadIdRef = (0, import_react7.useRef)(threadContext.currentThreadId);
1670
+ (0, import_react7.useEffect)(() => {
1671
+ currentThreadIdRef.current = threadContext.currentThreadId;
1672
+ }, [threadContext.currentThreadId]);
1673
+ (0, import_react7.useEffect)(() => {
1720
1674
  void ensureInitialState(threadContext.currentThreadId);
1721
1675
  }, [ensureInitialState, threadContext.currentThreadId]);
1722
- (0, import_react6.useEffect)(() => {
1676
+ (0, import_react7.useEffect)(() => {
1723
1677
  const threadId = threadContext.currentThreadId;
1724
- const isCurrentlyRunning = isThreadRunning(backendStateRef.current, threadId);
1725
- setIsRunning(isCurrentlyRunning);
1726
- }, [threadContext.currentThreadId, setIsRunning]);
1727
- const currentMessages = threadContext.getThreadMessages(threadContext.currentThreadId);
1728
- (0, import_react6.useEffect)(() => {
1729
- if (!publicKey) return;
1678
+ setIsRunning(isThreadRunning(backendStateRef.current, threadId));
1679
+ }, [backendStateRef, setIsRunning, threadContext.currentThreadId]);
1680
+ const currentMessages = threadContext.getThreadMessages(
1681
+ threadContext.currentThreadId
1682
+ );
1683
+ const resolvedSessionId = (0, import_react7.useMemo)(
1684
+ () => resolveThreadId(backendStateRef.current, threadContext.currentThreadId),
1685
+ [
1686
+ backendStateRef,
1687
+ threadContext.currentThreadId,
1688
+ threadContext.allThreadsMetadata
1689
+ ]
1690
+ );
1691
+ (0, import_react7.useEffect)(() => {
1692
+ const userAddress = user.address;
1693
+ if (!userAddress) return;
1730
1694
  const fetchThreadList = async () => {
1731
1695
  var _a, _b;
1732
1696
  try {
1733
- const threadList = await backendApiRef.current.fetchThreads(publicKey);
1697
+ const threadList = await backendApiRef.current.fetchThreads(userAddress);
1734
1698
  const currentContext = threadContextRef.current;
1735
- const newMetadata = new Map(currentContext.threadMetadata);
1699
+ const newMetadata = new Map(currentContext.allThreadsMetadata);
1736
1700
  let maxChatNum = currentContext.threadCnt;
1737
1701
  for (const thread of threadList) {
1738
1702
  const rawTitle = (_a = thread.title) != null ? _a : "";
1739
1703
  const title = isPlaceholderTitle(rawTitle) ? "" : rawTitle;
1740
- const lastActive = thread.last_active_at || thread.updated_at || thread.created_at || ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
1704
+ const lastActive = ((_b = newMetadata.get(thread.session_id)) == null ? void 0 : _b.lastActiveAt) || (/* @__PURE__ */ new Date()).toISOString();
1741
1705
  newMetadata.set(thread.session_id, {
1742
1706
  title,
1743
1707
  status: thread.is_archived ? "archived" : "regular",
@@ -1760,248 +1724,128 @@ function AomiRuntimeProvider({
1760
1724
  }
1761
1725
  };
1762
1726
  void fetchThreadList();
1763
- }, [publicKey]);
1764
- const threadListAdapter = (0, import_react6.useMemo)(() => {
1727
+ }, [user.address, backendApiRef]);
1728
+ const threadListAdapter = (0, import_react7.useMemo)(
1729
+ () => buildThreadListAdapter({
1730
+ backendStateRef,
1731
+ backendApiRef,
1732
+ threadContext,
1733
+ currentThreadIdRef,
1734
+ polling,
1735
+ userAddress: user.address,
1736
+ setIsRunning
1737
+ }),
1738
+ [
1739
+ backendApiRef,
1740
+ polling,
1741
+ user.address,
1742
+ backendStateRef,
1743
+ setIsRunning,
1744
+ threadContext,
1745
+ threadContext.currentThreadId,
1746
+ threadContext.allThreadsMetadata
1747
+ ]
1748
+ );
1749
+ (0, import_react7.useEffect)(() => {
1765
1750
  const backendState = backendStateRef.current;
1766
- const { regularThreads, archivedThreads } = buildThreadLists(threadContext.threadMetadata);
1767
- const preparePendingThread = (threadId) => {
1768
- const previousPendingId = backendState.creatingThreadId;
1769
- if (previousPendingId && previousPendingId !== threadId) {
1770
- threadContext.setThreadMetadata((prev) => {
1771
- const next = new Map(prev);
1772
- next.delete(previousPendingId);
1773
- return next;
1774
- });
1775
- threadContext.setThreads((prev) => {
1776
- const next = new Map(prev);
1777
- next.delete(previousPendingId);
1778
- return next;
1779
- });
1780
- backendState.pendingChat.delete(previousPendingId);
1781
- backendState.pendingSystem.delete(previousPendingId);
1782
- backendState.tempToBackendId.delete(previousPendingId);
1783
- backendState.skipInitialFetch.delete(previousPendingId);
1784
- }
1785
- backendState.creatingThreadId = threadId;
1786
- backendState.pendingChat.delete(threadId);
1787
- backendState.pendingSystem.delete(threadId);
1788
- threadContext.setThreadMetadata(
1789
- (prev) => new Map(prev).set(threadId, {
1790
- title: "New Chat",
1791
- status: "pending",
1792
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1793
- })
1794
- );
1795
- threadContext.setThreadMessages(threadId, []);
1796
- threadContext.setCurrentThreadId(threadId);
1797
- setIsRunning(false);
1798
- threadContext.bumpThreadViewKey();
1799
- };
1800
- const findPendingThreadId = () => {
1801
- if (backendState.creatingThreadId) return backendState.creatingThreadId;
1802
- for (const [id, meta] of threadContext.threadMetadata.entries()) {
1803
- if (meta.status === "pending") return id;
1804
- }
1805
- return null;
1806
- };
1807
- return {
1808
- threadId: threadContext.currentThreadId,
1809
- threads: regularThreads,
1810
- archivedThreads,
1811
- onSwitchToNewThread: async () => {
1812
- var _a;
1813
- const previousThreadId = currentThreadIdRef.current;
1814
- polling.stopAll();
1815
- if (isRunning && isThreadReady(backendState, previousThreadId)) {
1816
- const backendId = resolveThreadId(backendState, previousThreadId);
1817
- void backendApiRef.current.postInterrupt(backendId);
1818
- }
1819
- const pendingId = findPendingThreadId();
1820
- if (pendingId) {
1821
- preparePendingThread(pendingId);
1822
- return;
1823
- }
1824
- if (backendState.createThreadPromise) {
1825
- preparePendingThread((_a = backendState.creatingThreadId) != null ? _a : `temp-${crypto.randomUUID()}`);
1826
- return;
1827
- }
1828
- const tempId = `temp-${crypto.randomUUID()}`;
1829
- preparePendingThread(tempId);
1830
- const createPromise = backendApiRef.current.createThread(publicKey, void 0).then(async (newThread) => {
1831
- var _a2;
1832
- const uiThreadId = (_a2 = backendState.creatingThreadId) != null ? _a2 : tempId;
1833
- const backendId = newThread.session_id;
1834
- setBackendMapping(backendState, uiThreadId, backendId);
1835
- markSkipInitialFetch(backendState, uiThreadId);
1836
- bumpUpdateSubscriptions();
1837
- const backendTitle = newThread.title;
1838
- if (backendTitle && !isPlaceholderTitle(backendTitle)) {
1839
- threadContext.setThreadMetadata((prev) => {
1840
- var _a3;
1841
- const next = new Map(prev);
1842
- const existing = next.get(uiThreadId);
1843
- const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1844
- next.set(uiThreadId, {
1845
- title: backendTitle,
1846
- status: nextStatus,
1847
- lastActiveAt: (_a3 = existing == null ? void 0 : existing.lastActiveAt) != null ? _a3 : (/* @__PURE__ */ new Date()).toISOString()
1848
- });
1849
- return next;
1751
+ const currentSessionId = threadContext.currentThreadId;
1752
+ if (process.env.NODE_ENV !== "production") {
1753
+ console.debug("[aomi][sse] subscribe", {
1754
+ currentSessionId,
1755
+ resolvedSessionId,
1756
+ hasMapping: currentSessionId !== resolvedSessionId
1757
+ });
1758
+ }
1759
+ const unsubscribe = backendApiRef.current.subscribeSSE(
1760
+ resolvedSessionId,
1761
+ (event) => {
1762
+ const eventType = event.type;
1763
+ const sessionId = event.session_id;
1764
+ if (eventType === "title_changed") {
1765
+ const newTitle = event.new_title;
1766
+ const targetThreadId = resolveThreadId(backendState, sessionId);
1767
+ const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1768
+ if (process.env.NODE_ENV !== "production") {
1769
+ console.debug("[aomi][sse] title_changed", {
1770
+ sessionId,
1771
+ newTitle,
1772
+ normalizedTitle,
1773
+ currentThreadId: threadContextRef.current.currentThreadId,
1774
+ targetThreadId,
1775
+ hasMapping: sessionId !== targetThreadId,
1776
+ creatingThreadId: backendState.creatingThreadId
1850
1777
  });
1851
1778
  }
1852
- if (backendState.creatingThreadId === uiThreadId) {
1853
- backendState.creatingThreadId = null;
1854
- }
1855
- const pendingMessages = backendState.pendingChat.get(uiThreadId);
1856
- if (pendingMessages == null ? void 0 : pendingMessages.length) {
1857
- backendState.pendingChat.delete(uiThreadId);
1858
- for (const text of pendingMessages) {
1859
- try {
1860
- const activePublicKey = publicKeyRef.current;
1861
- if (activePublicKey) {
1862
- await backendApiRef.current.postChatMessage(
1863
- backendId,
1864
- text,
1865
- activePublicKey
1866
- );
1867
- } else {
1868
- await backendApiRef.current.postChatMessage(backendId, text);
1869
- }
1870
- } catch (error) {
1871
- console.error("Failed to send queued message:", error);
1872
- }
1873
- }
1874
- if (currentThreadIdRef.current === uiThreadId) {
1875
- polling == null ? void 0 : polling.start(uiThreadId);
1876
- }
1877
- }
1878
- }).catch((error) => {
1879
- var _a2;
1880
- console.error("Failed to create new thread:", error);
1881
- const failedId = (_a2 = backendState.creatingThreadId) != null ? _a2 : tempId;
1882
- threadContext.setThreadMetadata((prev) => {
1883
- const next = new Map(prev);
1884
- next.delete(failedId);
1885
- return next;
1886
- });
1887
- threadContext.setThreads((prev) => {
1888
- const next = new Map(prev);
1889
- next.delete(failedId);
1890
- return next;
1891
- });
1892
- if (backendState.creatingThreadId === failedId) {
1893
- backendState.creatingThreadId = null;
1894
- }
1895
- }).finally(() => {
1896
- backendState.createThreadPromise = null;
1897
- });
1898
- backendState.createThreadPromise = createPromise;
1899
- },
1900
- onSwitchToThread: (threadId) => {
1901
- const previousThreadId = currentThreadIdRef.current;
1902
- polling.stopAll();
1903
- if (isRunning && isThreadReady(backendState, previousThreadId)) {
1904
- const backendId = resolveThreadId(backendState, previousThreadId);
1905
- void backendApiRef.current.postInterrupt(backendId);
1906
- }
1907
- threadContext.setCurrentThreadId(threadId);
1908
- },
1909
- onRename: async (threadId, newTitle) => {
1910
- var _a, _b;
1911
- const previousTitle = (_b = (_a = threadContext.getThreadMetadata(threadId)) == null ? void 0 : _a.title) != null ? _b : "";
1912
- const normalizedTitle = isPlaceholderTitle(newTitle) ? "" : newTitle;
1913
- threadContext.updateThreadMetadata(threadId, {
1914
- title: normalizedTitle
1915
- });
1916
- try {
1917
- await backendApiRef.current.renameThread(threadId, newTitle);
1918
- } catch (error) {
1919
- console.error("Failed to rename thread:", error);
1920
- threadContext.updateThreadMetadata(threadId, { title: previousTitle });
1921
- }
1922
- },
1923
- onArchive: async (threadId) => {
1924
- threadContext.updateThreadMetadata(threadId, { status: "archived" });
1925
- try {
1926
- await backendApiRef.current.archiveThread(threadId);
1927
- } catch (error) {
1928
- console.error("Failed to archive thread:", error);
1929
- threadContext.updateThreadMetadata(threadId, { status: "regular" });
1930
- }
1931
- },
1932
- onUnarchive: async (threadId) => {
1933
- threadContext.updateThreadMetadata(threadId, { status: "regular" });
1934
- try {
1935
- await backendApiRef.current.unarchiveThread(threadId);
1936
- } catch (error) {
1937
- console.error("Failed to unarchive thread:", error);
1938
- threadContext.updateThreadMetadata(threadId, { status: "archived" });
1939
- }
1940
- },
1941
- onDelete: async (threadId) => {
1942
- try {
1943
- await backendApiRef.current.deleteThread(threadId);
1944
- threadContext.setThreadMetadata((prev) => {
1945
- const next = new Map(prev);
1946
- next.delete(threadId);
1947
- return next;
1948
- });
1949
- threadContext.setThreads((prev) => {
1779
+ threadContextRef.current.setThreadMetadata((prev) => {
1780
+ var _a;
1950
1781
  const next = new Map(prev);
1951
- next.delete(threadId);
1782
+ const existing = next.get(targetThreadId);
1783
+ const nextStatus = (existing == null ? void 0 : existing.status) === "archived" ? "archived" : "regular";
1784
+ next.set(targetThreadId, {
1785
+ title: normalizedTitle,
1786
+ status: nextStatus,
1787
+ lastActiveAt: (_a = existing == null ? void 0 : existing.lastActiveAt) != null ? _a : (/* @__PURE__ */ new Date()).toISOString()
1788
+ });
1952
1789
  return next;
1953
1790
  });
1954
- backendState.pendingChat.delete(threadId);
1955
- backendState.pendingSystem.delete(threadId);
1956
- backendState.tempToBackendId.delete(threadId);
1957
- backendState.skipInitialFetch.delete(threadId);
1958
- backendState.runningThreads.delete(threadId);
1959
- if (backendState.creatingThreadId === threadId) {
1791
+ if (!isPlaceholderTitle(newTitle) && backendState.creatingThreadId === targetThreadId) {
1960
1792
  backendState.creatingThreadId = null;
1961
1793
  }
1962
- if (threadContext.currentThreadId === threadId) {
1963
- const firstRegularThread = Array.from(threadContext.threadMetadata.entries()).find(
1964
- ([id, meta]) => meta.status === "regular" && id !== threadId
1965
- );
1966
- if (firstRegularThread) {
1967
- threadContext.setCurrentThreadId(firstRegularThread[0]);
1968
- } else {
1969
- const defaultId = "default-session";
1970
- threadContext.setThreadMetadata(
1971
- (prev) => new Map(prev).set(defaultId, {
1972
- title: "New Chat",
1973
- status: "regular",
1974
- lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1975
- })
1976
- );
1977
- threadContext.setThreadMessages(defaultId, []);
1978
- threadContext.setCurrentThreadId(defaultId);
1979
- }
1980
- }
1981
- } catch (error) {
1982
- console.error("Failed to delete thread:", error);
1983
- throw error;
1984
1794
  }
1985
1795
  }
1796
+ );
1797
+ return () => {
1798
+ unsubscribe == null ? void 0 : unsubscribe();
1986
1799
  };
1987
1800
  }, [
1988
1801
  backendApiRef,
1989
- polling,
1990
- publicKey,
1991
1802
  backendStateRef,
1992
- setIsRunning,
1993
- threadContext,
1994
1803
  threadContext.currentThreadId,
1995
- threadContext.threadMetadata,
1996
- bumpUpdateSubscriptions
1804
+ resolvedSessionId
1997
1805
  ]);
1998
- (0, import_react6.useEffect)(() => {
1806
+ (0, import_react7.useEffect)(() => {
1999
1807
  const threadId = threadContext.currentThreadId;
2000
- if (!isTempThreadId(threadId)) return;
2001
1808
  if (!isThreadReady(backendStateRef.current, threadId)) return;
2002
1809
  void messageController.flushPendingChat(threadId);
2003
1810
  }, [messageController, backendStateRef, threadContext.currentThreadId]);
2004
- const runtime = (0, import_react7.useExternalStoreRuntime)({
1811
+ (0, import_react7.useEffect)(() => {
1812
+ const showToolNotification = (eventType) => (event) => {
1813
+ const payload = event.payload;
1814
+ const toolName = typeof (payload == null ? void 0 : payload.tool_name) === "string" ? payload.tool_name : void 0;
1815
+ const title = toolName ? `${eventType === "tool_update" ? "Tool update" : "Tool complete"}: ${toolName}` : eventType === "tool_update" ? "Tool update" : "Tool complete";
1816
+ const message = typeof (payload == null ? void 0 : payload.message) === "string" ? payload.message : typeof (payload == null ? void 0 : payload.result) === "string" ? payload.result : void 0;
1817
+ notificationContext.showNotification({
1818
+ type: "notice",
1819
+ title,
1820
+ message
1821
+ });
1822
+ };
1823
+ const unsubscribeUpdate = eventContext.subscribe(
1824
+ "tool_update",
1825
+ showToolNotification("tool_update")
1826
+ );
1827
+ const unsubscribeComplete = eventContext.subscribe(
1828
+ "tool_complete",
1829
+ showToolNotification("tool_complete")
1830
+ );
1831
+ return () => {
1832
+ unsubscribeUpdate();
1833
+ unsubscribeComplete();
1834
+ };
1835
+ }, [eventContext, notificationContext]);
1836
+ (0, import_react7.useEffect)(() => {
1837
+ const unsubscribe = eventContext.subscribe("system_notice", (event) => {
1838
+ const payload = event.payload;
1839
+ const message = payload == null ? void 0 : payload.message;
1840
+ notificationContext.showNotification({
1841
+ type: "notice",
1842
+ title: "System notice",
1843
+ message
1844
+ });
1845
+ });
1846
+ return unsubscribe;
1847
+ }, [eventContext, notificationContext]);
1848
+ const runtime = (0, import_react8.useExternalStoreRuntime)({
2005
1849
  messages: currentMessages,
2006
1850
  setMessages: (msgs) => threadContext.setThreadMessages(threadContext.currentThreadId, [...msgs]),
2007
1851
  isRunning,
@@ -2010,83 +1854,284 @@ function AomiRuntimeProvider({
2010
1854
  convertMessage: (msg) => msg,
2011
1855
  adapters: { threadList: threadListAdapter }
2012
1856
  });
2013
- (0, import_react6.useEffect)(() => {
2014
- const threadId = threadContext.currentThreadId;
2015
- if (isTempThreadId(threadId)) return;
2016
- const hasUserMessages = currentMessages.some((msg) => msg.role === "user");
2017
- if (hasUserMessages) {
2018
- void messageController.flushPendingSystem(threadId);
2019
- }
2020
- }, [currentMessages, messageController, threadContext.currentThreadId]);
2021
- (0, import_react6.useEffect)(() => {
1857
+ (0, import_react7.useEffect)(() => {
2022
1858
  return () => {
2023
- var _a;
2024
1859
  polling.stopAll();
2025
- (_a = eventControllerRef.current) == null ? void 0 : _a.cleanup();
2026
1860
  };
2027
1861
  }, [polling]);
2028
- (0, import_react6.useEffect)(() => {
2029
- var _a, _b, _c;
2030
- const backendState = backendStateRef.current;
2031
- const threadId = threadContext.currentThreadId;
2032
- const isReady = isThreadReady(backendState, threadId);
2033
- if (!isReady) {
2034
- lastSubscribedThreadRef.current = null;
2035
- (_a = eventControllerRef.current) == null ? void 0 : _a.setSubscribableSessionId(null);
2036
- return;
2037
- }
2038
- const isCurrentlyRunning = isThreadRunning(backendState, threadId);
2039
- if (isCurrentlyRunning) {
2040
- const sessionId = resolveThreadId(backendState, threadId);
2041
- (_b = eventControllerRef.current) == null ? void 0 : _b.setSubscribableSessionId(sessionId);
2042
- lastSubscribedThreadRef.current = threadId;
2043
- return;
2044
- }
2045
- if (lastSubscribedThreadRef.current !== threadId) {
2046
- (_c = eventControllerRef.current) == null ? void 0 : _c.setSubscribableSessionId(null);
2047
- }
2048
- }, [backendStateRef, threadContext.currentThreadId, updateSubscriptionsTick, isRunning]);
2049
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2050
- RuntimeActionsProvider,
1862
+ const userContext = useUser();
1863
+ const sendMessage = (0, import_react7.useCallback)(
1864
+ async (text) => {
1865
+ const appendMessage = {
1866
+ role: "user",
1867
+ content: [{ type: "text", text }]
1868
+ };
1869
+ await messageController.outbound(
1870
+ appendMessage,
1871
+ threadContext.currentThreadId
1872
+ );
1873
+ },
1874
+ [messageController, threadContext.currentThreadId]
1875
+ );
1876
+ const cancelGeneration = (0, import_react7.useCallback)(() => {
1877
+ messageController.cancel(threadContext.currentThreadId);
1878
+ }, [messageController, threadContext.currentThreadId]);
1879
+ const getMessages = (0, import_react7.useCallback)(
1880
+ (threadId) => {
1881
+ const id = threadId != null ? threadId : threadContext.currentThreadId;
1882
+ return threadContext.getThreadMessages(id);
1883
+ },
1884
+ [threadContext]
1885
+ );
1886
+ const createThread = (0, import_react7.useCallback)(async () => {
1887
+ await threadListAdapter.onSwitchToNewThread();
1888
+ return threadContextRef.current.currentThreadId;
1889
+ }, [threadListAdapter]);
1890
+ const deleteThread = (0, import_react7.useCallback)(
1891
+ async (threadId) => {
1892
+ await threadListAdapter.onDelete(threadId);
1893
+ },
1894
+ [threadListAdapter]
1895
+ );
1896
+ const renameThread = (0, import_react7.useCallback)(
1897
+ async (threadId, title) => {
1898
+ await threadListAdapter.onRename(threadId, title);
1899
+ },
1900
+ [threadListAdapter]
1901
+ );
1902
+ const archiveThread = (0, import_react7.useCallback)(
1903
+ async (threadId) => {
1904
+ await threadListAdapter.onArchive(threadId);
1905
+ },
1906
+ [threadListAdapter]
1907
+ );
1908
+ const selectThread = (0, import_react7.useCallback)(
1909
+ (threadId) => {
1910
+ if (threadContext.allThreadsMetadata.has(threadId)) {
1911
+ threadListAdapter.onSwitchToThread(threadId);
1912
+ } else {
1913
+ void threadListAdapter.onSwitchToNewThread();
1914
+ }
1915
+ },
1916
+ [threadContext.allThreadsMetadata, threadListAdapter]
1917
+ );
1918
+ const aomiRuntimeApi = (0, import_react7.useMemo)(
1919
+ () => ({
1920
+ // User API
1921
+ user: userContext.user,
1922
+ getUserState: userContext.getUserState,
1923
+ setUser: userContext.setUser,
1924
+ onUserStateChange: userContext.onUserStateChange,
1925
+ // Thread API
1926
+ currentThreadId: threadContext.currentThreadId,
1927
+ threadViewKey: threadContext.threadViewKey,
1928
+ threadMetadata: threadContext.allThreadsMetadata,
1929
+ getThreadMetadata: threadContext.getThreadMetadata,
1930
+ createThread,
1931
+ deleteThread,
1932
+ renameThread,
1933
+ archiveThread,
1934
+ selectThread,
1935
+ // Chat API
1936
+ isRunning,
1937
+ getMessages,
1938
+ sendMessage,
1939
+ cancelGeneration,
1940
+ // Notification API
1941
+ notifications: notificationContext.notifications,
1942
+ showNotification: notificationContext.showNotification,
1943
+ dismissNotification: notificationContext.dismissNotification,
1944
+ clearAllNotifications: notificationContext.clearAll,
1945
+ // Event API
1946
+ subscribe: eventContext.subscribe,
1947
+ sendSystemCommand: eventContext.sendOutboundSystem,
1948
+ sseStatus: eventContext.sseStatus
1949
+ }),
1950
+ [
1951
+ userContext,
1952
+ threadContext.currentThreadId,
1953
+ threadContext.threadViewKey,
1954
+ threadContext.allThreadsMetadata,
1955
+ threadContext.getThreadMetadata,
1956
+ createThread,
1957
+ deleteThread,
1958
+ renameThread,
1959
+ archiveThread,
1960
+ selectThread,
1961
+ isRunning,
1962
+ getMessages,
1963
+ sendMessage,
1964
+ cancelGeneration,
1965
+ notificationContext,
1966
+ eventContext
1967
+ ]
1968
+ );
1969
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(AomiRuntimeApiProvider, { value: aomiRuntimeApi, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react8.AssistantRuntimeProvider, { runtime, children }) });
1970
+ }
1971
+
1972
+ // src/runtime/aomi-runtime.tsx
1973
+ var import_jsx_runtime6 = require("react/jsx-runtime");
1974
+ function AomiRuntimeProvider({
1975
+ children,
1976
+ backendUrl = "http://localhost:8080"
1977
+ }) {
1978
+ const backendApi = (0, import_react9.useMemo)(() => new BackendApi(backendUrl), [backendUrl]);
1979
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ThreadContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(NotificationContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(UserContextProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AomiRuntimeInner, { backendApi, children }) }) }) });
1980
+ }
1981
+ function AomiRuntimeInner({
1982
+ children,
1983
+ backendApi
1984
+ }) {
1985
+ const threadContext = useThreadContext();
1986
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1987
+ EventContextProvider,
2051
1988
  {
2052
- value: {
2053
- sendSystemMessage: (message) => messageController.outboundSystem(threadContext.currentThreadId, message)
2054
- },
2055
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react7.AssistantRuntimeProvider, { runtime, children })
1989
+ backendApi,
1990
+ sessionId: threadContext.currentThreadId,
1991
+ children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(AomiRuntimeCore, { backendApi, children })
2056
1992
  }
2057
1993
  );
2058
1994
  }
2059
- function AomiRuntimeProviderWithNotifications(props) {
2060
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NotificationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AomiRuntimeProvider, __spreadValues({}, props)) });
1995
+
1996
+ // src/handlers/wallet-handler.ts
1997
+ var import_react10 = require("react");
1998
+ function useWalletHandler({
1999
+ sessionId,
2000
+ onTxRequest
2001
+ }) {
2002
+ const { subscribe: subscribe2, sendOutboundSystem: sendOutbound } = useEventContext();
2003
+ const { setUser, getUserState } = useUser();
2004
+ const [pendingTxRequests, setPendingTxRequests] = (0, import_react10.useState)(
2005
+ []
2006
+ );
2007
+ (0, import_react10.useEffect)(() => {
2008
+ const unsubscribe = subscribe2(
2009
+ "wallet_tx_request",
2010
+ (event) => {
2011
+ const request = event.payload;
2012
+ setPendingTxRequests((prev) => [...prev, request]);
2013
+ onTxRequest == null ? void 0 : onTxRequest(request);
2014
+ }
2015
+ );
2016
+ return unsubscribe;
2017
+ }, [subscribe2, onTxRequest]);
2018
+ (0, import_react10.useEffect)(() => {
2019
+ const unsubscribe = subscribe2(
2020
+ "user_state_request",
2021
+ (event) => {
2022
+ sendOutbound({
2023
+ type: "user_state_response",
2024
+ sessionId,
2025
+ payload: getUserState()
2026
+ });
2027
+ }
2028
+ );
2029
+ return unsubscribe;
2030
+ }, [subscribe2, onTxRequest]);
2031
+ const sendTxComplete = (0, import_react10.useCallback)(
2032
+ (tx) => {
2033
+ sendOutbound({
2034
+ type: "wallet:tx_complete",
2035
+ sessionId,
2036
+ payload: tx
2037
+ });
2038
+ },
2039
+ [sendOutbound, sessionId]
2040
+ );
2041
+ const sendConnectionChange = (0, import_react10.useCallback)(
2042
+ (status, address, chainId) => {
2043
+ if (status === "connected") {
2044
+ setUser({
2045
+ isConnected: true,
2046
+ address,
2047
+ chainId
2048
+ });
2049
+ } else {
2050
+ setUser({
2051
+ isConnected: false,
2052
+ address: void 0,
2053
+ chainId: void 0
2054
+ });
2055
+ }
2056
+ sendOutbound({
2057
+ type: status === "connected" ? "wallet:connected" : "wallet:disconnected",
2058
+ sessionId,
2059
+ payload: { status, address }
2060
+ });
2061
+ },
2062
+ [setUser, sendOutbound, sessionId]
2063
+ );
2064
+ const clearTxRequest = (0, import_react10.useCallback)((index) => {
2065
+ setPendingTxRequests((prev) => prev.filter((_, i) => i !== index));
2066
+ }, []);
2067
+ return {
2068
+ sendTxComplete,
2069
+ sendConnectionChange,
2070
+ pendingTxRequests,
2071
+ clearTxRequest
2072
+ };
2061
2073
  }
2062
2074
 
2063
- // src/lib/utils.ts
2064
- var import_clsx = require("clsx");
2065
- var import_tailwind_merge = require("tailwind-merge");
2066
- function cn(...inputs) {
2067
- return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
2075
+ // src/handlers/notification-handler.ts
2076
+ var import_react11 = require("react");
2077
+ var notificationIdCounter2 = 0;
2078
+ function generateNotificationId() {
2079
+ return `notif-${Date.now()}-${++notificationIdCounter2}`;
2080
+ }
2081
+ function useNotificationHandler({
2082
+ onNotification
2083
+ } = {}) {
2084
+ const { subscribe: subscribe2 } = useEventContext();
2085
+ const [notifications, setNotifications] = (0, import_react11.useState)([]);
2086
+ (0, import_react11.useEffect)(() => {
2087
+ const unsubscribe = subscribe2("notification", (event) => {
2088
+ var _a, _b;
2089
+ const payload = event.payload;
2090
+ const notification = {
2091
+ id: generateNotificationId(),
2092
+ type: (_a = payload.type) != null ? _a : "notification",
2093
+ title: (_b = payload.title) != null ? _b : "Notification",
2094
+ body: payload.body,
2095
+ handled: false,
2096
+ timestamp: event.timestamp,
2097
+ sessionId: event.sessionId
2098
+ };
2099
+ setNotifications((prev) => [notification, ...prev]);
2100
+ onNotification == null ? void 0 : onNotification(notification);
2101
+ });
2102
+ return unsubscribe;
2103
+ }, [subscribe2, onNotification]);
2104
+ const unhandledCount = notifications.filter((n) => !n.handled).length;
2105
+ const markHandled = (0, import_react11.useCallback)((id) => {
2106
+ setNotifications(
2107
+ (prev) => prev.map((n) => n.id === id ? __spreadProps(__spreadValues({}, n), { handled: true }) : n)
2108
+ );
2109
+ }, []);
2110
+ return {
2111
+ notifications,
2112
+ unhandledCount,
2113
+ markDone: markHandled
2114
+ };
2068
2115
  }
2069
2116
  // Annotate the CommonJS export names for ESM import in node:
2070
2117
  0 && (module.exports = {
2071
2118
  AomiRuntimeProvider,
2072
- AomiRuntimeProviderWithNotifications,
2073
2119
  BackendApi,
2074
- NotificationProvider,
2075
- RuntimeActionsProvider,
2120
+ EventContextProvider,
2121
+ NotificationContextProvider,
2076
2122
  ThreadContextProvider,
2077
- WalletSystemMessageEmitter,
2123
+ UserContextProvider,
2078
2124
  cn,
2079
- constructSystemMessage,
2080
- constructThreadMessage,
2081
2125
  formatAddress,
2082
2126
  getNetworkName,
2083
- normalizeWalletError,
2084
- pickInjectedProvider,
2085
- toHexQuantity,
2127
+ useAomiRuntime,
2086
2128
  useCurrentThreadMessages,
2087
2129
  useCurrentThreadMetadata,
2130
+ useEventContext,
2088
2131
  useNotification,
2089
- useRuntimeActions,
2090
- useThreadContext
2132
+ useNotificationHandler,
2133
+ useThreadContext,
2134
+ useUser,
2135
+ useWalletHandler
2091
2136
  });
2092
2137
  //# sourceMappingURL=index.cjs.map