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