@aomi-labs/client 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +272 -0
- package/dist/cli.js +1657 -0
- package/dist/index.cjs +486 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +255 -26
- package/dist/index.d.ts +255 -26
- package/dist/index.js +480 -1
- package/dist/index.js.map +1 -1
- package/package.json +15 -2
- package/skills/aomi-transact/SKILL.md +171 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,1657 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
5
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
8
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
9
|
+
var __spreadValues = (a, b) => {
|
|
10
|
+
for (var prop in b || (b = {}))
|
|
11
|
+
if (__hasOwnProp.call(b, prop))
|
|
12
|
+
__defNormalProp(a, prop, b[prop]);
|
|
13
|
+
if (__getOwnPropSymbols)
|
|
14
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
15
|
+
if (__propIsEnum.call(b, prop))
|
|
16
|
+
__defNormalProp(a, prop, b[prop]);
|
|
17
|
+
}
|
|
18
|
+
return a;
|
|
19
|
+
};
|
|
20
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
21
|
+
|
|
22
|
+
// src/sse.ts
|
|
23
|
+
function extractSseData(rawEvent) {
|
|
24
|
+
const dataLines = rawEvent.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trimStart());
|
|
25
|
+
if (!dataLines.length) return null;
|
|
26
|
+
return dataLines.join("\n");
|
|
27
|
+
}
|
|
28
|
+
async function readSseStream(stream, signal, onMessage) {
|
|
29
|
+
const reader = stream.getReader();
|
|
30
|
+
const decoder = new TextDecoder();
|
|
31
|
+
let buffer = "";
|
|
32
|
+
try {
|
|
33
|
+
while (!signal.aborted) {
|
|
34
|
+
const { value, done } = await reader.read();
|
|
35
|
+
if (done) break;
|
|
36
|
+
buffer += decoder.decode(value, { stream: true });
|
|
37
|
+
buffer = buffer.replace(/\r/g, "");
|
|
38
|
+
let separatorIndex = buffer.indexOf("\n\n");
|
|
39
|
+
while (separatorIndex >= 0) {
|
|
40
|
+
const rawEvent = buffer.slice(0, separatorIndex);
|
|
41
|
+
buffer = buffer.slice(separatorIndex + 2);
|
|
42
|
+
const data = extractSseData(rawEvent);
|
|
43
|
+
if (data) {
|
|
44
|
+
onMessage(data);
|
|
45
|
+
}
|
|
46
|
+
separatorIndex = buffer.indexOf("\n\n");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} finally {
|
|
50
|
+
reader.releaseLock();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function createSseSubscriber({
|
|
54
|
+
backendUrl,
|
|
55
|
+
getHeaders,
|
|
56
|
+
logger
|
|
57
|
+
}) {
|
|
58
|
+
const subscriptions = /* @__PURE__ */ new Map();
|
|
59
|
+
const subscribe = (sessionId, onUpdate, onError) => {
|
|
60
|
+
const existing = subscriptions.get(sessionId);
|
|
61
|
+
const listener = { onUpdate, onError };
|
|
62
|
+
if (existing) {
|
|
63
|
+
existing.listeners.add(listener);
|
|
64
|
+
logger == null ? void 0 : logger.debug("[aomi][sse] listener added", {
|
|
65
|
+
sessionId,
|
|
66
|
+
listeners: existing.listeners.size
|
|
67
|
+
});
|
|
68
|
+
return () => {
|
|
69
|
+
existing.listeners.delete(listener);
|
|
70
|
+
logger == null ? void 0 : logger.debug("[aomi][sse] listener removed", {
|
|
71
|
+
sessionId,
|
|
72
|
+
listeners: existing.listeners.size
|
|
73
|
+
});
|
|
74
|
+
if (existing.listeners.size === 0) {
|
|
75
|
+
existing.stop("unsubscribe");
|
|
76
|
+
if (subscriptions.get(sessionId) === existing) {
|
|
77
|
+
subscriptions.delete(sessionId);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
const subscription = {
|
|
83
|
+
abortController: null,
|
|
84
|
+
retries: 0,
|
|
85
|
+
retryTimer: null,
|
|
86
|
+
stopped: false,
|
|
87
|
+
listeners: /* @__PURE__ */ new Set([listener]),
|
|
88
|
+
stop: (reason) => {
|
|
89
|
+
var _a2;
|
|
90
|
+
subscription.stopped = true;
|
|
91
|
+
if (subscription.retryTimer) {
|
|
92
|
+
clearTimeout(subscription.retryTimer);
|
|
93
|
+
subscription.retryTimer = null;
|
|
94
|
+
}
|
|
95
|
+
(_a2 = subscription.abortController) == null ? void 0 : _a2.abort();
|
|
96
|
+
subscription.abortController = null;
|
|
97
|
+
logger == null ? void 0 : logger.debug("[aomi][sse] stop", {
|
|
98
|
+
sessionId,
|
|
99
|
+
reason,
|
|
100
|
+
retries: subscription.retries
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const scheduleRetry = () => {
|
|
105
|
+
if (subscription.stopped) return;
|
|
106
|
+
subscription.retries += 1;
|
|
107
|
+
const delayMs = Math.min(500 * 2 ** (subscription.retries - 1), 1e4);
|
|
108
|
+
logger == null ? void 0 : logger.debug("[aomi][sse] retry scheduled", {
|
|
109
|
+
sessionId,
|
|
110
|
+
delayMs,
|
|
111
|
+
retries: subscription.retries
|
|
112
|
+
});
|
|
113
|
+
subscription.retryTimer = setTimeout(() => {
|
|
114
|
+
void open();
|
|
115
|
+
}, delayMs);
|
|
116
|
+
};
|
|
117
|
+
const open = async () => {
|
|
118
|
+
var _a2;
|
|
119
|
+
if (subscription.stopped) return;
|
|
120
|
+
if (subscription.retryTimer) {
|
|
121
|
+
clearTimeout(subscription.retryTimer);
|
|
122
|
+
subscription.retryTimer = null;
|
|
123
|
+
}
|
|
124
|
+
const controller = new AbortController();
|
|
125
|
+
subscription.abortController = controller;
|
|
126
|
+
const openedAt = Date.now();
|
|
127
|
+
try {
|
|
128
|
+
const response = await fetch(`${backendUrl}/api/updates`, {
|
|
129
|
+
headers: getHeaders(sessionId),
|
|
130
|
+
signal: controller.signal
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`SSE HTTP ${response.status}: ${response.statusText}`
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
if (!response.body) {
|
|
138
|
+
throw new Error("SSE response missing body");
|
|
139
|
+
}
|
|
140
|
+
subscription.retries = 0;
|
|
141
|
+
await readSseStream(response.body, controller.signal, (data) => {
|
|
142
|
+
var _a3, _b;
|
|
143
|
+
let parsed2;
|
|
144
|
+
try {
|
|
145
|
+
parsed2 = JSON.parse(data);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
for (const item of subscription.listeners) {
|
|
148
|
+
(_a3 = item.onError) == null ? void 0 : _a3.call(item, error);
|
|
149
|
+
}
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
for (const item of subscription.listeners) {
|
|
153
|
+
try {
|
|
154
|
+
item.onUpdate(parsed2);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
(_b = item.onError) == null ? void 0 : _b.call(item, error);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
logger == null ? void 0 : logger.debug("[aomi][sse] stream ended", {
|
|
161
|
+
sessionId,
|
|
162
|
+
aborted: controller.signal.aborted,
|
|
163
|
+
stopped: subscription.stopped,
|
|
164
|
+
durationMs: Date.now() - openedAt
|
|
165
|
+
});
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (!controller.signal.aborted && !subscription.stopped) {
|
|
168
|
+
for (const item of subscription.listeners) {
|
|
169
|
+
(_a2 = item.onError) == null ? void 0 : _a2.call(item, error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!subscription.stopped) {
|
|
174
|
+
scheduleRetry();
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
subscriptions.set(sessionId, subscription);
|
|
178
|
+
void open();
|
|
179
|
+
return () => {
|
|
180
|
+
subscription.listeners.delete(listener);
|
|
181
|
+
logger == null ? void 0 : logger.debug("[aomi][sse] listener removed", {
|
|
182
|
+
sessionId,
|
|
183
|
+
listeners: subscription.listeners.size
|
|
184
|
+
});
|
|
185
|
+
if (subscription.listeners.size === 0) {
|
|
186
|
+
subscription.stop("unsubscribe");
|
|
187
|
+
if (subscriptions.get(sessionId) === subscription) {
|
|
188
|
+
subscriptions.delete(sessionId);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
return { subscribe };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// src/client.ts
|
|
197
|
+
var SESSION_ID_HEADER = "X-Session-Id";
|
|
198
|
+
var API_KEY_HEADER = "X-API-Key";
|
|
199
|
+
function toQueryString(payload) {
|
|
200
|
+
const params = new URLSearchParams();
|
|
201
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
202
|
+
if (value === void 0 || value === null) continue;
|
|
203
|
+
params.set(key, String(value));
|
|
204
|
+
}
|
|
205
|
+
const qs = params.toString();
|
|
206
|
+
return qs ? `?${qs}` : "";
|
|
207
|
+
}
|
|
208
|
+
function withSessionHeader(sessionId, init) {
|
|
209
|
+
const headers = new Headers(init);
|
|
210
|
+
headers.set(SESSION_ID_HEADER, sessionId);
|
|
211
|
+
return headers;
|
|
212
|
+
}
|
|
213
|
+
async function postState(baseUrl, path, payload, sessionId, apiKey) {
|
|
214
|
+
const query = toQueryString(payload);
|
|
215
|
+
const url = `${baseUrl}${path}${query}`;
|
|
216
|
+
const headers = new Headers(withSessionHeader(sessionId));
|
|
217
|
+
if (apiKey) {
|
|
218
|
+
headers.set(API_KEY_HEADER, apiKey);
|
|
219
|
+
}
|
|
220
|
+
const response = await fetch(url, {
|
|
221
|
+
method: "POST",
|
|
222
|
+
headers
|
|
223
|
+
});
|
|
224
|
+
if (!response.ok) {
|
|
225
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
226
|
+
}
|
|
227
|
+
return await response.json();
|
|
228
|
+
}
|
|
229
|
+
var AomiClient = class {
|
|
230
|
+
constructor(options) {
|
|
231
|
+
this.baseUrl = options.baseUrl.replace(/\/+$/, "");
|
|
232
|
+
this.apiKey = options.apiKey;
|
|
233
|
+
this.logger = options.logger;
|
|
234
|
+
this.sseSubscriber = createSseSubscriber({
|
|
235
|
+
backendUrl: this.baseUrl,
|
|
236
|
+
getHeaders: (sessionId) => withSessionHeader(sessionId, { Accept: "text/event-stream" }),
|
|
237
|
+
logger: this.logger
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
// ===========================================================================
|
|
241
|
+
// Chat & State
|
|
242
|
+
// ===========================================================================
|
|
243
|
+
/**
|
|
244
|
+
* Fetch current session state (messages, processing status, title).
|
|
245
|
+
*/
|
|
246
|
+
async fetchState(sessionId, userState) {
|
|
247
|
+
const url = new URL("/api/state", this.baseUrl);
|
|
248
|
+
if (userState) {
|
|
249
|
+
url.searchParams.set("user_state", JSON.stringify(userState));
|
|
250
|
+
}
|
|
251
|
+
const response = await fetch(url.toString(), {
|
|
252
|
+
headers: withSessionHeader(sessionId)
|
|
253
|
+
});
|
|
254
|
+
if (!response.ok) {
|
|
255
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
256
|
+
}
|
|
257
|
+
return await response.json();
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Send a chat message and return updated session state.
|
|
261
|
+
*/
|
|
262
|
+
async sendMessage(sessionId, message, options) {
|
|
263
|
+
var _a2, _b;
|
|
264
|
+
const namespace = (_a2 = options == null ? void 0 : options.namespace) != null ? _a2 : "default";
|
|
265
|
+
const apiKey = (_b = options == null ? void 0 : options.apiKey) != null ? _b : this.apiKey;
|
|
266
|
+
const payload = { message, namespace };
|
|
267
|
+
if (options == null ? void 0 : options.publicKey) {
|
|
268
|
+
payload.public_key = options.publicKey;
|
|
269
|
+
}
|
|
270
|
+
if (options == null ? void 0 : options.userState) {
|
|
271
|
+
payload.user_state = JSON.stringify(options.userState);
|
|
272
|
+
}
|
|
273
|
+
return postState(
|
|
274
|
+
this.baseUrl,
|
|
275
|
+
"/api/chat",
|
|
276
|
+
payload,
|
|
277
|
+
sessionId,
|
|
278
|
+
apiKey
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Send a system-level message (e.g. wallet state changes, context switches).
|
|
283
|
+
*/
|
|
284
|
+
async sendSystemMessage(sessionId, message) {
|
|
285
|
+
return postState(
|
|
286
|
+
this.baseUrl,
|
|
287
|
+
"/api/system",
|
|
288
|
+
{ message },
|
|
289
|
+
sessionId
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Interrupt the AI's current response.
|
|
294
|
+
*/
|
|
295
|
+
async interrupt(sessionId) {
|
|
296
|
+
return postState(
|
|
297
|
+
this.baseUrl,
|
|
298
|
+
"/api/interrupt",
|
|
299
|
+
{},
|
|
300
|
+
sessionId
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
// ===========================================================================
|
|
304
|
+
// SSE (Real-time Updates)
|
|
305
|
+
// ===========================================================================
|
|
306
|
+
/**
|
|
307
|
+
* Subscribe to real-time SSE updates for a session.
|
|
308
|
+
* Automatically reconnects with exponential backoff on disconnects.
|
|
309
|
+
* Returns an unsubscribe function.
|
|
310
|
+
*/
|
|
311
|
+
subscribeSSE(sessionId, onUpdate, onError) {
|
|
312
|
+
return this.sseSubscriber.subscribe(sessionId, onUpdate, onError);
|
|
313
|
+
}
|
|
314
|
+
// ===========================================================================
|
|
315
|
+
// Thread / Session Management
|
|
316
|
+
// ===========================================================================
|
|
317
|
+
/**
|
|
318
|
+
* List all threads for a wallet address.
|
|
319
|
+
*/
|
|
320
|
+
async listThreads(publicKey) {
|
|
321
|
+
const url = `${this.baseUrl}/api/sessions?public_key=${encodeURIComponent(publicKey)}`;
|
|
322
|
+
const response = await fetch(url);
|
|
323
|
+
if (!response.ok) {
|
|
324
|
+
throw new Error(`Failed to fetch threads: HTTP ${response.status}`);
|
|
325
|
+
}
|
|
326
|
+
return await response.json();
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get a single thread by ID.
|
|
330
|
+
*/
|
|
331
|
+
async getThread(sessionId) {
|
|
332
|
+
const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
333
|
+
const response = await fetch(url, {
|
|
334
|
+
headers: withSessionHeader(sessionId)
|
|
335
|
+
});
|
|
336
|
+
if (!response.ok) {
|
|
337
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
338
|
+
}
|
|
339
|
+
return await response.json();
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Create a new thread. The client generates the session ID.
|
|
343
|
+
*/
|
|
344
|
+
async createThread(threadId, publicKey) {
|
|
345
|
+
const body = {};
|
|
346
|
+
if (publicKey) body.public_key = publicKey;
|
|
347
|
+
const url = `${this.baseUrl}/api/sessions`;
|
|
348
|
+
const response = await fetch(url, {
|
|
349
|
+
method: "POST",
|
|
350
|
+
headers: withSessionHeader(threadId, {
|
|
351
|
+
"Content-Type": "application/json"
|
|
352
|
+
}),
|
|
353
|
+
body: JSON.stringify(body)
|
|
354
|
+
});
|
|
355
|
+
if (!response.ok) {
|
|
356
|
+
throw new Error(`Failed to create thread: HTTP ${response.status}`);
|
|
357
|
+
}
|
|
358
|
+
return await response.json();
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Delete a thread by ID.
|
|
362
|
+
*/
|
|
363
|
+
async deleteThread(sessionId) {
|
|
364
|
+
const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
365
|
+
const response = await fetch(url, {
|
|
366
|
+
method: "DELETE",
|
|
367
|
+
headers: withSessionHeader(sessionId)
|
|
368
|
+
});
|
|
369
|
+
if (!response.ok) {
|
|
370
|
+
throw new Error(`Failed to delete thread: HTTP ${response.status}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Rename a thread.
|
|
375
|
+
*/
|
|
376
|
+
async renameThread(sessionId, newTitle) {
|
|
377
|
+
const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}`;
|
|
378
|
+
const response = await fetch(url, {
|
|
379
|
+
method: "PATCH",
|
|
380
|
+
headers: withSessionHeader(sessionId, {
|
|
381
|
+
"Content-Type": "application/json"
|
|
382
|
+
}),
|
|
383
|
+
body: JSON.stringify({ title: newTitle })
|
|
384
|
+
});
|
|
385
|
+
if (!response.ok) {
|
|
386
|
+
throw new Error(`Failed to rename thread: HTTP ${response.status}`);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
/**
|
|
390
|
+
* Archive a thread.
|
|
391
|
+
*/
|
|
392
|
+
async archiveThread(sessionId) {
|
|
393
|
+
const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}/archive`;
|
|
394
|
+
const response = await fetch(url, {
|
|
395
|
+
method: "POST",
|
|
396
|
+
headers: withSessionHeader(sessionId)
|
|
397
|
+
});
|
|
398
|
+
if (!response.ok) {
|
|
399
|
+
throw new Error(`Failed to archive thread: HTTP ${response.status}`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Unarchive a thread.
|
|
404
|
+
*/
|
|
405
|
+
async unarchiveThread(sessionId) {
|
|
406
|
+
const url = `${this.baseUrl}/api/sessions/${encodeURIComponent(sessionId)}/unarchive`;
|
|
407
|
+
const response = await fetch(url, {
|
|
408
|
+
method: "POST",
|
|
409
|
+
headers: withSessionHeader(sessionId)
|
|
410
|
+
});
|
|
411
|
+
if (!response.ok) {
|
|
412
|
+
throw new Error(`Failed to unarchive thread: HTTP ${response.status}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// ===========================================================================
|
|
416
|
+
// System Events
|
|
417
|
+
// ===========================================================================
|
|
418
|
+
/**
|
|
419
|
+
* Get system events for a session.
|
|
420
|
+
*/
|
|
421
|
+
async getSystemEvents(sessionId, count) {
|
|
422
|
+
const url = new URL("/api/events", this.baseUrl);
|
|
423
|
+
if (count !== void 0) {
|
|
424
|
+
url.searchParams.set("count", String(count));
|
|
425
|
+
}
|
|
426
|
+
const response = await fetch(url.toString(), {
|
|
427
|
+
headers: withSessionHeader(sessionId)
|
|
428
|
+
});
|
|
429
|
+
if (!response.ok) {
|
|
430
|
+
if (response.status === 404) return [];
|
|
431
|
+
throw new Error(`Failed to get system events: HTTP ${response.status}`);
|
|
432
|
+
}
|
|
433
|
+
return await response.json();
|
|
434
|
+
}
|
|
435
|
+
// ===========================================================================
|
|
436
|
+
// Control API
|
|
437
|
+
// ===========================================================================
|
|
438
|
+
/**
|
|
439
|
+
* Get available namespaces.
|
|
440
|
+
*/
|
|
441
|
+
async getNamespaces(sessionId, options) {
|
|
442
|
+
var _a2;
|
|
443
|
+
const url = new URL("/api/control/namespaces", this.baseUrl);
|
|
444
|
+
if (options == null ? void 0 : options.publicKey) {
|
|
445
|
+
url.searchParams.set("public_key", options.publicKey);
|
|
446
|
+
}
|
|
447
|
+
const apiKey = (_a2 = options == null ? void 0 : options.apiKey) != null ? _a2 : this.apiKey;
|
|
448
|
+
const headers = new Headers(withSessionHeader(sessionId));
|
|
449
|
+
if (apiKey) {
|
|
450
|
+
headers.set(API_KEY_HEADER, apiKey);
|
|
451
|
+
}
|
|
452
|
+
const response = await fetch(url.toString(), { headers });
|
|
453
|
+
if (!response.ok) {
|
|
454
|
+
throw new Error(`Failed to get namespaces: HTTP ${response.status}`);
|
|
455
|
+
}
|
|
456
|
+
return await response.json();
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Get available models.
|
|
460
|
+
*/
|
|
461
|
+
async getModels(sessionId) {
|
|
462
|
+
const url = new URL("/api/control/models", this.baseUrl);
|
|
463
|
+
const response = await fetch(url.toString(), {
|
|
464
|
+
headers: withSessionHeader(sessionId)
|
|
465
|
+
});
|
|
466
|
+
if (!response.ok) {
|
|
467
|
+
throw new Error(`Failed to get models: HTTP ${response.status}`);
|
|
468
|
+
}
|
|
469
|
+
return await response.json();
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Set the model for a session.
|
|
473
|
+
*/
|
|
474
|
+
async setModel(sessionId, rig, options) {
|
|
475
|
+
var _a2;
|
|
476
|
+
const apiKey = (_a2 = options == null ? void 0 : options.apiKey) != null ? _a2 : this.apiKey;
|
|
477
|
+
const payload = { rig };
|
|
478
|
+
if (options == null ? void 0 : options.namespace) {
|
|
479
|
+
payload.namespace = options.namespace;
|
|
480
|
+
}
|
|
481
|
+
return postState(this.baseUrl, "/api/control/model", payload, sessionId, apiKey);
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// src/event-emitter.ts
|
|
486
|
+
var TypedEventEmitter = class {
|
|
487
|
+
constructor() {
|
|
488
|
+
this.listeners = /* @__PURE__ */ new Map();
|
|
489
|
+
}
|
|
490
|
+
/**
|
|
491
|
+
* Subscribe to an event type. Returns an unsubscribe function.
|
|
492
|
+
*/
|
|
493
|
+
on(type, handler) {
|
|
494
|
+
let set = this.listeners.get(type);
|
|
495
|
+
if (!set) {
|
|
496
|
+
set = /* @__PURE__ */ new Set();
|
|
497
|
+
this.listeners.set(type, set);
|
|
498
|
+
}
|
|
499
|
+
set.add(handler);
|
|
500
|
+
return () => {
|
|
501
|
+
set.delete(handler);
|
|
502
|
+
if (set.size === 0) {
|
|
503
|
+
this.listeners.delete(type);
|
|
504
|
+
}
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Subscribe to an event type for a single emission, then auto-unsubscribe.
|
|
509
|
+
*/
|
|
510
|
+
once(type, handler) {
|
|
511
|
+
const wrapper = ((payload) => {
|
|
512
|
+
unsub();
|
|
513
|
+
handler(payload);
|
|
514
|
+
});
|
|
515
|
+
const unsub = this.on(type, wrapper);
|
|
516
|
+
return unsub;
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Emit an event to all listeners of `type` and wildcard `"*"` listeners.
|
|
520
|
+
*/
|
|
521
|
+
emit(type, payload) {
|
|
522
|
+
const typeSet = this.listeners.get(type);
|
|
523
|
+
if (typeSet) {
|
|
524
|
+
for (const handler of typeSet) {
|
|
525
|
+
handler(payload);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
if (type !== "*") {
|
|
529
|
+
const wildcardSet = this.listeners.get("*");
|
|
530
|
+
if (wildcardSet) {
|
|
531
|
+
for (const handler of wildcardSet) {
|
|
532
|
+
handler({ type, payload });
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Remove a specific handler from an event type.
|
|
539
|
+
*/
|
|
540
|
+
off(type, handler) {
|
|
541
|
+
const set = this.listeners.get(type);
|
|
542
|
+
if (set) {
|
|
543
|
+
set.delete(handler);
|
|
544
|
+
if (set.size === 0) {
|
|
545
|
+
this.listeners.delete(type);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Remove all listeners for all event types.
|
|
551
|
+
*/
|
|
552
|
+
removeAllListeners() {
|
|
553
|
+
this.listeners.clear();
|
|
554
|
+
}
|
|
555
|
+
};
|
|
556
|
+
|
|
557
|
+
// src/types.ts
|
|
558
|
+
function isInlineCall(event) {
|
|
559
|
+
return "InlineCall" in event;
|
|
560
|
+
}
|
|
561
|
+
function isSystemNotice(event) {
|
|
562
|
+
return "SystemNotice" in event;
|
|
563
|
+
}
|
|
564
|
+
function isSystemError(event) {
|
|
565
|
+
return "SystemError" in event;
|
|
566
|
+
}
|
|
567
|
+
function isAsyncCallback(event) {
|
|
568
|
+
return "AsyncCallback" in event;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
// src/event-unwrap.ts
|
|
572
|
+
function unwrapSystemEvent(event) {
|
|
573
|
+
var _a2;
|
|
574
|
+
if (isInlineCall(event)) {
|
|
575
|
+
return {
|
|
576
|
+
type: event.InlineCall.type,
|
|
577
|
+
payload: (_a2 = event.InlineCall.payload) != null ? _a2 : event.InlineCall
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
if (isSystemNotice(event)) {
|
|
581
|
+
return {
|
|
582
|
+
type: "system_notice",
|
|
583
|
+
payload: { message: event.SystemNotice }
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
if (isSystemError(event)) {
|
|
587
|
+
return {
|
|
588
|
+
type: "system_error",
|
|
589
|
+
payload: { message: event.SystemError }
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (isAsyncCallback(event)) {
|
|
593
|
+
return {
|
|
594
|
+
type: "async_callback",
|
|
595
|
+
payload: event.AsyncCallback
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/wallet-utils.ts
|
|
602
|
+
function asRecord(value) {
|
|
603
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
604
|
+
return void 0;
|
|
605
|
+
return value;
|
|
606
|
+
}
|
|
607
|
+
function getToolArgs(payload) {
|
|
608
|
+
var _a2;
|
|
609
|
+
const root = asRecord(payload);
|
|
610
|
+
const nestedArgs = asRecord(root == null ? void 0 : root.args);
|
|
611
|
+
return (_a2 = nestedArgs != null ? nestedArgs : root) != null ? _a2 : {};
|
|
612
|
+
}
|
|
613
|
+
function parseChainId(value) {
|
|
614
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
615
|
+
if (typeof value !== "string") return void 0;
|
|
616
|
+
const trimmed = value.trim();
|
|
617
|
+
if (!trimmed) return void 0;
|
|
618
|
+
if (trimmed.startsWith("0x")) {
|
|
619
|
+
const parsedHex = Number.parseInt(trimmed.slice(2), 16);
|
|
620
|
+
return Number.isFinite(parsedHex) ? parsedHex : void 0;
|
|
621
|
+
}
|
|
622
|
+
const parsed2 = Number.parseInt(trimmed, 10);
|
|
623
|
+
return Number.isFinite(parsed2) ? parsed2 : void 0;
|
|
624
|
+
}
|
|
625
|
+
function normalizeTxPayload(payload) {
|
|
626
|
+
var _a2, _b, _c;
|
|
627
|
+
const root = asRecord(payload);
|
|
628
|
+
const args = getToolArgs(payload);
|
|
629
|
+
const ctx = asRecord(root == null ? void 0 : root.ctx);
|
|
630
|
+
const to = typeof args.to === "string" ? args.to : void 0;
|
|
631
|
+
if (!to) return null;
|
|
632
|
+
const valueRaw = args.value;
|
|
633
|
+
const value = typeof valueRaw === "string" ? valueRaw : typeof valueRaw === "number" && Number.isFinite(valueRaw) ? String(Math.trunc(valueRaw)) : void 0;
|
|
634
|
+
const data = typeof args.data === "string" ? args.data : void 0;
|
|
635
|
+
const chainId = (_c = (_b = (_a2 = parseChainId(args.chainId)) != null ? _a2 : parseChainId(args.chain_id)) != null ? _b : parseChainId(ctx == null ? void 0 : ctx.user_chain_id)) != null ? _c : parseChainId(ctx == null ? void 0 : ctx.userChainId);
|
|
636
|
+
return { to, value, data, chainId };
|
|
637
|
+
}
|
|
638
|
+
function normalizeEip712Payload(payload) {
|
|
639
|
+
var _a2;
|
|
640
|
+
const args = getToolArgs(payload);
|
|
641
|
+
const typedDataRaw = (_a2 = args.typed_data) != null ? _a2 : args.typedData;
|
|
642
|
+
let typedData;
|
|
643
|
+
if (typeof typedDataRaw === "string") {
|
|
644
|
+
try {
|
|
645
|
+
const parsed2 = JSON.parse(typedDataRaw);
|
|
646
|
+
if (parsed2 && typeof parsed2 === "object" && !Array.isArray(parsed2)) {
|
|
647
|
+
typedData = parsed2;
|
|
648
|
+
}
|
|
649
|
+
} catch (e) {
|
|
650
|
+
typedData = void 0;
|
|
651
|
+
}
|
|
652
|
+
} else if (typedDataRaw && typeof typedDataRaw === "object" && !Array.isArray(typedDataRaw)) {
|
|
653
|
+
typedData = typedDataRaw;
|
|
654
|
+
}
|
|
655
|
+
const description = typeof args.description === "string" ? args.description : void 0;
|
|
656
|
+
return { typed_data: typedData, description };
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/session.ts
|
|
660
|
+
var Session = class extends TypedEventEmitter {
|
|
661
|
+
constructor(clientOrOptions, sessionOptions) {
|
|
662
|
+
var _a2, _b, _c;
|
|
663
|
+
super();
|
|
664
|
+
// Internal state
|
|
665
|
+
this.pollTimer = null;
|
|
666
|
+
this.unsubscribeSSE = null;
|
|
667
|
+
this._isProcessing = false;
|
|
668
|
+
this.walletRequests = [];
|
|
669
|
+
this.walletRequestNextId = 1;
|
|
670
|
+
this._messages = [];
|
|
671
|
+
this.closed = false;
|
|
672
|
+
// For send() blocking behavior
|
|
673
|
+
this.pendingResolve = null;
|
|
674
|
+
this.client = clientOrOptions instanceof AomiClient ? clientOrOptions : new AomiClient(clientOrOptions);
|
|
675
|
+
this.sessionId = (_a2 = sessionOptions == null ? void 0 : sessionOptions.sessionId) != null ? _a2 : crypto.randomUUID();
|
|
676
|
+
this.namespace = (_b = sessionOptions == null ? void 0 : sessionOptions.namespace) != null ? _b : "default";
|
|
677
|
+
this.publicKey = sessionOptions == null ? void 0 : sessionOptions.publicKey;
|
|
678
|
+
this.apiKey = sessionOptions == null ? void 0 : sessionOptions.apiKey;
|
|
679
|
+
this.userState = sessionOptions == null ? void 0 : sessionOptions.userState;
|
|
680
|
+
this.pollIntervalMs = (_c = sessionOptions == null ? void 0 : sessionOptions.pollIntervalMs) != null ? _c : 500;
|
|
681
|
+
this.logger = sessionOptions == null ? void 0 : sessionOptions.logger;
|
|
682
|
+
this.unsubscribeSSE = this.client.subscribeSSE(
|
|
683
|
+
this.sessionId,
|
|
684
|
+
(event) => this.handleSSEEvent(event),
|
|
685
|
+
(error) => this.emit("error", { error })
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
// ===========================================================================
|
|
689
|
+
// Public API — Chat
|
|
690
|
+
// ===========================================================================
|
|
691
|
+
/**
|
|
692
|
+
* Send a message and wait for the AI to finish processing.
|
|
693
|
+
*
|
|
694
|
+
* The returned promise resolves when `is_processing` becomes `false` AND
|
|
695
|
+
* there are no pending wallet requests. If a wallet request arrives
|
|
696
|
+
* mid-processing, polling continues but the promise pauses until the
|
|
697
|
+
* request is resolved or rejected via `resolve()` / `reject()`.
|
|
698
|
+
*/
|
|
699
|
+
async send(message) {
|
|
700
|
+
this.assertOpen();
|
|
701
|
+
const response = await this.client.sendMessage(this.sessionId, message, {
|
|
702
|
+
namespace: this.namespace,
|
|
703
|
+
publicKey: this.publicKey,
|
|
704
|
+
apiKey: this.apiKey,
|
|
705
|
+
userState: this.userState
|
|
706
|
+
});
|
|
707
|
+
this.applyState(response);
|
|
708
|
+
if (!response.is_processing && this.walletRequests.length === 0) {
|
|
709
|
+
return { messages: this._messages, title: this._title };
|
|
710
|
+
}
|
|
711
|
+
this._isProcessing = true;
|
|
712
|
+
this.emit("processing_start", void 0);
|
|
713
|
+
return new Promise((resolve) => {
|
|
714
|
+
this.pendingResolve = resolve;
|
|
715
|
+
this.startPolling();
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* Send a message without waiting for completion.
|
|
720
|
+
* Polling starts in the background; listen to events for updates.
|
|
721
|
+
*/
|
|
722
|
+
async sendAsync(message) {
|
|
723
|
+
this.assertOpen();
|
|
724
|
+
const response = await this.client.sendMessage(this.sessionId, message, {
|
|
725
|
+
namespace: this.namespace,
|
|
726
|
+
publicKey: this.publicKey,
|
|
727
|
+
apiKey: this.apiKey,
|
|
728
|
+
userState: this.userState
|
|
729
|
+
});
|
|
730
|
+
this.applyState(response);
|
|
731
|
+
if (response.is_processing) {
|
|
732
|
+
this._isProcessing = true;
|
|
733
|
+
this.emit("processing_start", void 0);
|
|
734
|
+
this.startPolling();
|
|
735
|
+
}
|
|
736
|
+
return response;
|
|
737
|
+
}
|
|
738
|
+
// ===========================================================================
|
|
739
|
+
// Public API — Wallet Request Resolution
|
|
740
|
+
// ===========================================================================
|
|
741
|
+
/**
|
|
742
|
+
* Resolve a pending wallet request (transaction or EIP-712 signing).
|
|
743
|
+
* Sends the result to the backend and resumes polling.
|
|
744
|
+
*/
|
|
745
|
+
async resolve(requestId, result) {
|
|
746
|
+
var _a2;
|
|
747
|
+
const req = this.removeWalletRequest(requestId);
|
|
748
|
+
if (!req) {
|
|
749
|
+
throw new Error(`No pending wallet request with id "${requestId}"`);
|
|
750
|
+
}
|
|
751
|
+
if (req.kind === "transaction") {
|
|
752
|
+
await this.sendSystemEvent("wallet:tx_complete", {
|
|
753
|
+
txHash: (_a2 = result.txHash) != null ? _a2 : "",
|
|
754
|
+
status: "success",
|
|
755
|
+
amount: result.amount
|
|
756
|
+
});
|
|
757
|
+
} else {
|
|
758
|
+
const eip712Payload = req.payload;
|
|
759
|
+
await this.sendSystemEvent("wallet_eip712_response", {
|
|
760
|
+
status: "success",
|
|
761
|
+
signature: result.signature,
|
|
762
|
+
description: eip712Payload.description
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
if (this._isProcessing) {
|
|
766
|
+
this.startPolling();
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Reject a pending wallet request.
|
|
771
|
+
* Sends an error to the backend and resumes polling.
|
|
772
|
+
*/
|
|
773
|
+
async reject(requestId, reason) {
|
|
774
|
+
const req = this.removeWalletRequest(requestId);
|
|
775
|
+
if (!req) {
|
|
776
|
+
throw new Error(`No pending wallet request with id "${requestId}"`);
|
|
777
|
+
}
|
|
778
|
+
if (req.kind === "transaction") {
|
|
779
|
+
await this.sendSystemEvent("wallet:tx_complete", {
|
|
780
|
+
txHash: "",
|
|
781
|
+
status: "failed"
|
|
782
|
+
});
|
|
783
|
+
} else {
|
|
784
|
+
const eip712Payload = req.payload;
|
|
785
|
+
await this.sendSystemEvent("wallet_eip712_response", {
|
|
786
|
+
status: "failed",
|
|
787
|
+
error: reason != null ? reason : "Request rejected",
|
|
788
|
+
description: eip712Payload.description
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
if (this._isProcessing) {
|
|
792
|
+
this.startPolling();
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
// ===========================================================================
|
|
796
|
+
// Public API — Control
|
|
797
|
+
// ===========================================================================
|
|
798
|
+
/**
|
|
799
|
+
* Cancel the AI's current response.
|
|
800
|
+
*/
|
|
801
|
+
async interrupt() {
|
|
802
|
+
this.stopPolling();
|
|
803
|
+
const response = await this.client.interrupt(this.sessionId);
|
|
804
|
+
this.applyState(response);
|
|
805
|
+
this._isProcessing = false;
|
|
806
|
+
this.emit("processing_end", void 0);
|
|
807
|
+
this.resolvePending();
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Close the session. Stops polling, unsubscribes SSE, removes all listeners.
|
|
811
|
+
* The session cannot be used after closing.
|
|
812
|
+
*/
|
|
813
|
+
close() {
|
|
814
|
+
var _a2;
|
|
815
|
+
if (this.closed) return;
|
|
816
|
+
this.closed = true;
|
|
817
|
+
this.stopPolling();
|
|
818
|
+
(_a2 = this.unsubscribeSSE) == null ? void 0 : _a2.call(this);
|
|
819
|
+
this.unsubscribeSSE = null;
|
|
820
|
+
this.resolvePending();
|
|
821
|
+
this.removeAllListeners();
|
|
822
|
+
}
|
|
823
|
+
// ===========================================================================
|
|
824
|
+
// Public API — Accessors
|
|
825
|
+
// ===========================================================================
|
|
826
|
+
/** Current messages in the session. */
|
|
827
|
+
getMessages() {
|
|
828
|
+
return this._messages;
|
|
829
|
+
}
|
|
830
|
+
/** Current session title. */
|
|
831
|
+
getTitle() {
|
|
832
|
+
return this._title;
|
|
833
|
+
}
|
|
834
|
+
/** Pending wallet requests waiting for resolve/reject. */
|
|
835
|
+
getPendingRequests() {
|
|
836
|
+
return [...this.walletRequests];
|
|
837
|
+
}
|
|
838
|
+
/** Whether the AI is currently processing. */
|
|
839
|
+
getIsProcessing() {
|
|
840
|
+
return this._isProcessing;
|
|
841
|
+
}
|
|
842
|
+
// ===========================================================================
|
|
843
|
+
// Internal — Polling (ported from PollingController)
|
|
844
|
+
// ===========================================================================
|
|
845
|
+
startPolling() {
|
|
846
|
+
var _a2;
|
|
847
|
+
if (this.pollTimer || this.closed) return;
|
|
848
|
+
(_a2 = this.logger) == null ? void 0 : _a2.debug("[session] polling started", this.sessionId);
|
|
849
|
+
this.pollTimer = setInterval(() => {
|
|
850
|
+
void this.pollTick();
|
|
851
|
+
}, this.pollIntervalMs);
|
|
852
|
+
}
|
|
853
|
+
stopPolling() {
|
|
854
|
+
var _a2;
|
|
855
|
+
if (this.pollTimer) {
|
|
856
|
+
clearInterval(this.pollTimer);
|
|
857
|
+
this.pollTimer = null;
|
|
858
|
+
(_a2 = this.logger) == null ? void 0 : _a2.debug("[session] polling stopped", this.sessionId);
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async pollTick() {
|
|
862
|
+
var _a2;
|
|
863
|
+
if (!this.pollTimer) return;
|
|
864
|
+
try {
|
|
865
|
+
const state = await this.client.fetchState(
|
|
866
|
+
this.sessionId,
|
|
867
|
+
this.userState
|
|
868
|
+
);
|
|
869
|
+
if (!this.pollTimer) return;
|
|
870
|
+
this.applyState(state);
|
|
871
|
+
if (!state.is_processing && this.walletRequests.length === 0) {
|
|
872
|
+
this.stopPolling();
|
|
873
|
+
this._isProcessing = false;
|
|
874
|
+
this.emit("processing_end", void 0);
|
|
875
|
+
this.resolvePending();
|
|
876
|
+
}
|
|
877
|
+
} catch (error) {
|
|
878
|
+
(_a2 = this.logger) == null ? void 0 : _a2.debug("[session] poll error", error);
|
|
879
|
+
this.emit("error", { error });
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
// ===========================================================================
|
|
883
|
+
// Internal — State Application
|
|
884
|
+
// ===========================================================================
|
|
885
|
+
applyState(state) {
|
|
886
|
+
var _a2;
|
|
887
|
+
if (state.messages) {
|
|
888
|
+
this._messages = state.messages;
|
|
889
|
+
this.emit("messages", this._messages);
|
|
890
|
+
}
|
|
891
|
+
if (state.title) {
|
|
892
|
+
this._title = state.title;
|
|
893
|
+
}
|
|
894
|
+
if ((_a2 = state.system_events) == null ? void 0 : _a2.length) {
|
|
895
|
+
this.dispatchSystemEvents(state.system_events);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
dispatchSystemEvents(events) {
|
|
899
|
+
var _a2;
|
|
900
|
+
for (const event of events) {
|
|
901
|
+
const unwrapped = unwrapSystemEvent(event);
|
|
902
|
+
if (!unwrapped) continue;
|
|
903
|
+
if (unwrapped.type === "wallet_tx_request") {
|
|
904
|
+
const payload = normalizeTxPayload(unwrapped.payload);
|
|
905
|
+
if (payload) {
|
|
906
|
+
const req = this.enqueueWalletRequest("transaction", payload);
|
|
907
|
+
this.emit("wallet_tx_request", req);
|
|
908
|
+
}
|
|
909
|
+
} else if (unwrapped.type === "wallet_eip712_request") {
|
|
910
|
+
const payload = normalizeEip712Payload((_a2 = unwrapped.payload) != null ? _a2 : {});
|
|
911
|
+
const req = this.enqueueWalletRequest("eip712_sign", payload);
|
|
912
|
+
this.emit("wallet_eip712_request", req);
|
|
913
|
+
} else if (unwrapped.type === "system_notice" || unwrapped.type === "system_error" || unwrapped.type === "async_callback") {
|
|
914
|
+
this.emit(
|
|
915
|
+
unwrapped.type,
|
|
916
|
+
unwrapped.payload
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
// ===========================================================================
|
|
922
|
+
// Internal — SSE Handling
|
|
923
|
+
// ===========================================================================
|
|
924
|
+
handleSSEEvent(event) {
|
|
925
|
+
if (event.type === "title_changed" && event.new_title) {
|
|
926
|
+
this._title = event.new_title;
|
|
927
|
+
this.emit("title_changed", { title: event.new_title });
|
|
928
|
+
} else if (event.type === "tool_update") {
|
|
929
|
+
this.emit("tool_update", event);
|
|
930
|
+
} else if (event.type === "tool_complete") {
|
|
931
|
+
this.emit("tool_complete", event);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// ===========================================================================
|
|
935
|
+
// Internal — Wallet Request Queue
|
|
936
|
+
// ===========================================================================
|
|
937
|
+
enqueueWalletRequest(kind, payload) {
|
|
938
|
+
const req = {
|
|
939
|
+
id: `wreq-${this.walletRequestNextId++}`,
|
|
940
|
+
kind,
|
|
941
|
+
payload,
|
|
942
|
+
timestamp: Date.now()
|
|
943
|
+
};
|
|
944
|
+
this.walletRequests.push(req);
|
|
945
|
+
return req;
|
|
946
|
+
}
|
|
947
|
+
removeWalletRequest(id) {
|
|
948
|
+
const idx = this.walletRequests.findIndex((r) => r.id === id);
|
|
949
|
+
if (idx === -1) return null;
|
|
950
|
+
return this.walletRequests.splice(idx, 1)[0];
|
|
951
|
+
}
|
|
952
|
+
// ===========================================================================
|
|
953
|
+
// Internal — Helpers
|
|
954
|
+
// ===========================================================================
|
|
955
|
+
async sendSystemEvent(type, payload) {
|
|
956
|
+
const message = JSON.stringify({ type, payload });
|
|
957
|
+
await this.client.sendSystemMessage(this.sessionId, message);
|
|
958
|
+
}
|
|
959
|
+
resolvePending() {
|
|
960
|
+
if (this.pendingResolve) {
|
|
961
|
+
const resolve = this.pendingResolve;
|
|
962
|
+
this.pendingResolve = null;
|
|
963
|
+
resolve({ messages: this._messages, title: this._title });
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
assertOpen() {
|
|
967
|
+
if (this.closed) {
|
|
968
|
+
throw new Error("Session is closed");
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
};
|
|
972
|
+
|
|
973
|
+
// src/cli-state.ts
|
|
974
|
+
import { readFileSync, writeFileSync, unlinkSync, existsSync } from "fs";
|
|
975
|
+
import { join } from "path";
|
|
976
|
+
import { tmpdir } from "os";
|
|
977
|
+
var _a;
|
|
978
|
+
var STATE_FILE = join(
|
|
979
|
+
(_a = process.env.XDG_RUNTIME_DIR) != null ? _a : tmpdir(),
|
|
980
|
+
"aomi-session.json"
|
|
981
|
+
);
|
|
982
|
+
function readState() {
|
|
983
|
+
try {
|
|
984
|
+
if (!existsSync(STATE_FILE)) return null;
|
|
985
|
+
const raw = readFileSync(STATE_FILE, "utf-8");
|
|
986
|
+
return JSON.parse(raw);
|
|
987
|
+
} catch (e) {
|
|
988
|
+
return null;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
function writeState(state) {
|
|
992
|
+
writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
993
|
+
}
|
|
994
|
+
function clearState() {
|
|
995
|
+
try {
|
|
996
|
+
if (existsSync(STATE_FILE)) unlinkSync(STATE_FILE);
|
|
997
|
+
} catch (e) {
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function getNextTxId(state) {
|
|
1001
|
+
var _a2, _b;
|
|
1002
|
+
const allIds = [
|
|
1003
|
+
...(_a2 = state.pendingTxs) != null ? _a2 : [],
|
|
1004
|
+
...(_b = state.signedTxs) != null ? _b : []
|
|
1005
|
+
].map((t) => {
|
|
1006
|
+
const m = t.id.match(/^tx-(\d+)$/);
|
|
1007
|
+
return m ? parseInt(m[1], 10) : 0;
|
|
1008
|
+
});
|
|
1009
|
+
const max = allIds.length > 0 ? Math.max(...allIds) : 0;
|
|
1010
|
+
return `tx-${max + 1}`;
|
|
1011
|
+
}
|
|
1012
|
+
function addPendingTx(state, tx) {
|
|
1013
|
+
if (!state.pendingTxs) state.pendingTxs = [];
|
|
1014
|
+
const pending = __spreadProps(__spreadValues({}, tx), {
|
|
1015
|
+
id: getNextTxId(state)
|
|
1016
|
+
});
|
|
1017
|
+
state.pendingTxs.push(pending);
|
|
1018
|
+
writeState(state);
|
|
1019
|
+
return pending;
|
|
1020
|
+
}
|
|
1021
|
+
function removePendingTx(state, id) {
|
|
1022
|
+
if (!state.pendingTxs) return null;
|
|
1023
|
+
const idx = state.pendingTxs.findIndex((t) => t.id === id);
|
|
1024
|
+
if (idx === -1) return null;
|
|
1025
|
+
const [removed] = state.pendingTxs.splice(idx, 1);
|
|
1026
|
+
writeState(state);
|
|
1027
|
+
return removed;
|
|
1028
|
+
}
|
|
1029
|
+
function addSignedTx(state, tx) {
|
|
1030
|
+
if (!state.signedTxs) state.signedTxs = [];
|
|
1031
|
+
state.signedTxs.push(tx);
|
|
1032
|
+
writeState(state);
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
// src/cli.ts
|
|
1036
|
+
var CliExit = class extends Error {
|
|
1037
|
+
constructor(code) {
|
|
1038
|
+
super();
|
|
1039
|
+
this.code = code;
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
function fatal(message) {
|
|
1043
|
+
console.error(message);
|
|
1044
|
+
throw new CliExit(1);
|
|
1045
|
+
}
|
|
1046
|
+
function printDataFileLocation() {
|
|
1047
|
+
console.log(`Data stored at ${STATE_FILE} \u{1F4DD}`);
|
|
1048
|
+
}
|
|
1049
|
+
function parseArgs(argv) {
|
|
1050
|
+
const raw = argv.slice(2);
|
|
1051
|
+
const command = raw[0] && !raw[0].startsWith("--") ? raw[0] : void 0;
|
|
1052
|
+
const rest = command ? raw.slice(1) : raw;
|
|
1053
|
+
const positional = [];
|
|
1054
|
+
const flags = {};
|
|
1055
|
+
for (let i = 0; i < rest.length; i++) {
|
|
1056
|
+
const arg = rest[i];
|
|
1057
|
+
if (arg.startsWith("--") && arg.includes("=")) {
|
|
1058
|
+
const [key, ...val] = arg.slice(2).split("=");
|
|
1059
|
+
flags[key] = val.join("=");
|
|
1060
|
+
} else if (arg.startsWith("--")) {
|
|
1061
|
+
const key = arg.slice(2);
|
|
1062
|
+
const next = rest[i + 1];
|
|
1063
|
+
if (next && !next.startsWith("-")) {
|
|
1064
|
+
flags[key] = next;
|
|
1065
|
+
i++;
|
|
1066
|
+
} else {
|
|
1067
|
+
flags[key] = "true";
|
|
1068
|
+
}
|
|
1069
|
+
} else if (arg.startsWith("-") && arg.length === 2) {
|
|
1070
|
+
flags[arg.slice(1)] = "true";
|
|
1071
|
+
} else {
|
|
1072
|
+
positional.push(arg);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
return { command, positional, flags };
|
|
1076
|
+
}
|
|
1077
|
+
var parsed = parseArgs(process.argv);
|
|
1078
|
+
function getConfig() {
|
|
1079
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h;
|
|
1080
|
+
return {
|
|
1081
|
+
baseUrl: (_b = (_a2 = parsed.flags["backend-url"]) != null ? _a2 : process.env.AOMI_BASE_URL) != null ? _b : "https://api.aomi.dev",
|
|
1082
|
+
apiKey: (_c = parsed.flags["api-key"]) != null ? _c : process.env.AOMI_API_KEY,
|
|
1083
|
+
namespace: (_e = (_d = parsed.flags["namespace"]) != null ? _d : process.env.AOMI_NAMESPACE) != null ? _e : "default",
|
|
1084
|
+
publicKey: (_f = parsed.flags["public-key"]) != null ? _f : process.env.AOMI_PUBLIC_KEY,
|
|
1085
|
+
privateKey: (_g = parsed.flags["private-key"]) != null ? _g : process.env.PRIVATE_KEY,
|
|
1086
|
+
chainRpcUrl: (_h = parsed.flags["rpc-url"]) != null ? _h : process.env.CHAIN_RPC_URL
|
|
1087
|
+
};
|
|
1088
|
+
}
|
|
1089
|
+
function getOrCreateSession() {
|
|
1090
|
+
const config = getConfig();
|
|
1091
|
+
let state = readState();
|
|
1092
|
+
if (!state) {
|
|
1093
|
+
state = {
|
|
1094
|
+
sessionId: crypto.randomUUID(),
|
|
1095
|
+
baseUrl: config.baseUrl,
|
|
1096
|
+
namespace: config.namespace,
|
|
1097
|
+
apiKey: config.apiKey,
|
|
1098
|
+
publicKey: config.publicKey
|
|
1099
|
+
};
|
|
1100
|
+
writeState(state);
|
|
1101
|
+
} else {
|
|
1102
|
+
let changed = false;
|
|
1103
|
+
if (config.baseUrl && config.baseUrl !== state.baseUrl) {
|
|
1104
|
+
state.baseUrl = config.baseUrl;
|
|
1105
|
+
changed = true;
|
|
1106
|
+
}
|
|
1107
|
+
if (config.namespace && config.namespace !== state.namespace) {
|
|
1108
|
+
state.namespace = config.namespace;
|
|
1109
|
+
changed = true;
|
|
1110
|
+
}
|
|
1111
|
+
if (config.apiKey !== void 0 && config.apiKey !== state.apiKey) {
|
|
1112
|
+
state.apiKey = config.apiKey;
|
|
1113
|
+
changed = true;
|
|
1114
|
+
}
|
|
1115
|
+
if (config.publicKey !== void 0 && config.publicKey !== state.publicKey) {
|
|
1116
|
+
state.publicKey = config.publicKey;
|
|
1117
|
+
changed = true;
|
|
1118
|
+
}
|
|
1119
|
+
if (changed) writeState(state);
|
|
1120
|
+
}
|
|
1121
|
+
const session = new Session(
|
|
1122
|
+
{ baseUrl: state.baseUrl, apiKey: state.apiKey },
|
|
1123
|
+
{
|
|
1124
|
+
sessionId: state.sessionId,
|
|
1125
|
+
namespace: state.namespace,
|
|
1126
|
+
apiKey: state.apiKey,
|
|
1127
|
+
publicKey: state.publicKey,
|
|
1128
|
+
userState: state.publicKey ? {
|
|
1129
|
+
address: state.publicKey,
|
|
1130
|
+
chainId: 1,
|
|
1131
|
+
isConnected: true
|
|
1132
|
+
} : void 0
|
|
1133
|
+
}
|
|
1134
|
+
);
|
|
1135
|
+
return { session, state };
|
|
1136
|
+
}
|
|
1137
|
+
function walletRequestToPendingTx(req) {
|
|
1138
|
+
if (req.kind === "transaction") {
|
|
1139
|
+
const p2 = req.payload;
|
|
1140
|
+
return {
|
|
1141
|
+
kind: "transaction",
|
|
1142
|
+
to: p2.to,
|
|
1143
|
+
value: p2.value,
|
|
1144
|
+
data: p2.data,
|
|
1145
|
+
chainId: p2.chainId,
|
|
1146
|
+
timestamp: req.timestamp,
|
|
1147
|
+
payload: req.payload
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
const p = req.payload;
|
|
1151
|
+
return {
|
|
1152
|
+
kind: "eip712_sign",
|
|
1153
|
+
description: p.description,
|
|
1154
|
+
timestamp: req.timestamp,
|
|
1155
|
+
payload: req.payload
|
|
1156
|
+
};
|
|
1157
|
+
}
|
|
1158
|
+
function formatTxLine(tx, prefix) {
|
|
1159
|
+
var _a2;
|
|
1160
|
+
const parts = [`${prefix} ${tx.id}`];
|
|
1161
|
+
if (tx.kind === "transaction") {
|
|
1162
|
+
parts.push(`to: ${(_a2 = tx.to) != null ? _a2 : "?"}`);
|
|
1163
|
+
if (tx.value) parts.push(`value: ${tx.value}`);
|
|
1164
|
+
if (tx.chainId) parts.push(`chain: ${tx.chainId}`);
|
|
1165
|
+
if (tx.data) parts.push(`data: ${tx.data.slice(0, 20)}...`);
|
|
1166
|
+
} else {
|
|
1167
|
+
parts.push(`eip712`);
|
|
1168
|
+
if (tx.description) parts.push(tx.description);
|
|
1169
|
+
}
|
|
1170
|
+
parts.push(`(${new Date(tx.timestamp).toLocaleTimeString()})`);
|
|
1171
|
+
return parts.join(" ");
|
|
1172
|
+
}
|
|
1173
|
+
var DIM = "\x1B[2m";
|
|
1174
|
+
var CYAN = "\x1B[36m";
|
|
1175
|
+
var YELLOW = "\x1B[33m";
|
|
1176
|
+
var GREEN = "\x1B[32m";
|
|
1177
|
+
var RESET = "\x1B[0m";
|
|
1178
|
+
function printToolUpdate(event) {
|
|
1179
|
+
var _a2, _b, _c;
|
|
1180
|
+
const name = (_b = (_a2 = event.tool_name) != null ? _a2 : event.name) != null ? _b : "unknown";
|
|
1181
|
+
const status = (_c = event.status) != null ? _c : "running";
|
|
1182
|
+
console.log(`${DIM}\u{1F527} [tool] ${name}: ${status}${RESET}`);
|
|
1183
|
+
}
|
|
1184
|
+
function printToolComplete(event) {
|
|
1185
|
+
var _a2, _b, _c;
|
|
1186
|
+
const name = (_b = (_a2 = event.tool_name) != null ? _a2 : event.name) != null ? _b : "unknown";
|
|
1187
|
+
const result = (_c = event.result) != null ? _c : event.output;
|
|
1188
|
+
const line = result ? `${GREEN}\u2714 [tool] ${name} \u2192 ${result.slice(0, 120)}${result.length > 120 ? "\u2026" : ""}${RESET}` : `${GREEN}\u2714 [tool] ${name} done${RESET}`;
|
|
1189
|
+
console.log(line);
|
|
1190
|
+
}
|
|
1191
|
+
function printNewAgentMessages(messages, lastPrintedCount) {
|
|
1192
|
+
const agentMessages = messages.filter(
|
|
1193
|
+
(m) => m.sender === "agent" || m.sender === "assistant"
|
|
1194
|
+
);
|
|
1195
|
+
let handled = lastPrintedCount;
|
|
1196
|
+
for (let i = lastPrintedCount; i < agentMessages.length; i++) {
|
|
1197
|
+
const msg = agentMessages[i];
|
|
1198
|
+
if (msg.is_streaming) {
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
if (msg.content) {
|
|
1202
|
+
console.log(`${CYAN}\u{1F916} ${msg.content}${RESET}`);
|
|
1203
|
+
}
|
|
1204
|
+
handled = i + 1;
|
|
1205
|
+
}
|
|
1206
|
+
return handled;
|
|
1207
|
+
}
|
|
1208
|
+
async function chatCommand() {
|
|
1209
|
+
const message = parsed.positional.join(" ");
|
|
1210
|
+
if (!message) {
|
|
1211
|
+
fatal("Usage: aomi chat <message>");
|
|
1212
|
+
}
|
|
1213
|
+
const verbose = parsed.flags["verbose"] === "true" || parsed.flags["v"] === "true";
|
|
1214
|
+
const { session, state } = getOrCreateSession();
|
|
1215
|
+
if (state.publicKey) {
|
|
1216
|
+
await session.client.sendSystemMessage(
|
|
1217
|
+
session.sessionId,
|
|
1218
|
+
JSON.stringify({
|
|
1219
|
+
type: "wallet:state_changed",
|
|
1220
|
+
payload: {
|
|
1221
|
+
address: state.publicKey,
|
|
1222
|
+
chainId: 1,
|
|
1223
|
+
isConnected: true
|
|
1224
|
+
}
|
|
1225
|
+
})
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const capturedRequests = [];
|
|
1229
|
+
let printedAgentCount = 0;
|
|
1230
|
+
session.on("wallet_tx_request", (req) => capturedRequests.push(req));
|
|
1231
|
+
session.on("wallet_eip712_request", (req) => capturedRequests.push(req));
|
|
1232
|
+
try {
|
|
1233
|
+
await session.sendAsync(message);
|
|
1234
|
+
const allMsgs = session.getMessages();
|
|
1235
|
+
let seedIdx = allMsgs.length;
|
|
1236
|
+
for (let i = allMsgs.length - 1; i >= 0; i--) {
|
|
1237
|
+
if (allMsgs[i].sender === "user") {
|
|
1238
|
+
seedIdx = i;
|
|
1239
|
+
break;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
printedAgentCount = allMsgs.slice(0, seedIdx).filter(
|
|
1243
|
+
(m) => m.sender === "agent" || m.sender === "assistant"
|
|
1244
|
+
).length;
|
|
1245
|
+
if (verbose) {
|
|
1246
|
+
if (session.getIsProcessing()) {
|
|
1247
|
+
console.log(`${DIM}\u23F3 Processing\u2026${RESET}`);
|
|
1248
|
+
}
|
|
1249
|
+
printedAgentCount = printNewAgentMessages(allMsgs, printedAgentCount);
|
|
1250
|
+
session.on("tool_update", (event) => printToolUpdate(event));
|
|
1251
|
+
session.on("tool_complete", (event) => printToolComplete(event));
|
|
1252
|
+
session.on("messages", (msgs) => {
|
|
1253
|
+
printedAgentCount = printNewAgentMessages(msgs, printedAgentCount);
|
|
1254
|
+
});
|
|
1255
|
+
session.on("system_notice", ({ message: msg }) => {
|
|
1256
|
+
console.log(`${YELLOW}\u{1F4E2} ${msg}${RESET}`);
|
|
1257
|
+
});
|
|
1258
|
+
session.on("system_error", ({ message: msg }) => {
|
|
1259
|
+
console.log(`\x1B[31m\u274C ${msg}${RESET}`);
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
if (session.getIsProcessing()) {
|
|
1263
|
+
await new Promise((resolve) => {
|
|
1264
|
+
const checkWallet = () => {
|
|
1265
|
+
if (capturedRequests.length > 0) resolve();
|
|
1266
|
+
};
|
|
1267
|
+
session.on("wallet_tx_request", checkWallet);
|
|
1268
|
+
session.on("wallet_eip712_request", checkWallet);
|
|
1269
|
+
session.on("processing_end", () => resolve());
|
|
1270
|
+
});
|
|
1271
|
+
}
|
|
1272
|
+
if (verbose) {
|
|
1273
|
+
printNewAgentMessages(session.getMessages(), printedAgentCount);
|
|
1274
|
+
console.log(`${DIM}\u2705 Done${RESET}`);
|
|
1275
|
+
}
|
|
1276
|
+
for (const req of capturedRequests) {
|
|
1277
|
+
const pending = addPendingTx(state, walletRequestToPendingTx(req));
|
|
1278
|
+
console.log(`\u26A1 Wallet request queued: ${pending.id}`);
|
|
1279
|
+
if (req.kind === "transaction") {
|
|
1280
|
+
const p = req.payload;
|
|
1281
|
+
console.log(` to: ${p.to}`);
|
|
1282
|
+
if (p.value) console.log(` value: ${p.value}`);
|
|
1283
|
+
if (p.chainId) console.log(` chain: ${p.chainId}`);
|
|
1284
|
+
} else {
|
|
1285
|
+
const p = req.payload;
|
|
1286
|
+
if (p.description) console.log(` desc: ${p.description}`);
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
if (!verbose) {
|
|
1290
|
+
const messages = session.getMessages();
|
|
1291
|
+
const agentMessages = messages.filter(
|
|
1292
|
+
(m) => m.sender === "agent" || m.sender === "assistant"
|
|
1293
|
+
);
|
|
1294
|
+
const last = agentMessages[agentMessages.length - 1];
|
|
1295
|
+
if (last == null ? void 0 : last.content) {
|
|
1296
|
+
console.log(last.content);
|
|
1297
|
+
} else if (capturedRequests.length === 0) {
|
|
1298
|
+
console.log("(no response)");
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
1301
|
+
if (capturedRequests.length > 0) {
|
|
1302
|
+
console.log(`
|
|
1303
|
+
Run \`aomi tx\` to see pending transactions, \`aomi sign <id>\` to sign.`);
|
|
1304
|
+
}
|
|
1305
|
+
} finally {
|
|
1306
|
+
session.close();
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
async function statusCommand() {
|
|
1310
|
+
var _a2, _b, _c, _d, _e, _f, _g, _h;
|
|
1311
|
+
const state = readState();
|
|
1312
|
+
if (!state) {
|
|
1313
|
+
console.log("No active session");
|
|
1314
|
+
printDataFileLocation();
|
|
1315
|
+
return;
|
|
1316
|
+
}
|
|
1317
|
+
const { session } = getOrCreateSession();
|
|
1318
|
+
try {
|
|
1319
|
+
const apiState = await session.client.fetchState(state.sessionId);
|
|
1320
|
+
console.log(
|
|
1321
|
+
JSON.stringify(
|
|
1322
|
+
{
|
|
1323
|
+
sessionId: state.sessionId,
|
|
1324
|
+
baseUrl: state.baseUrl,
|
|
1325
|
+
namespace: state.namespace,
|
|
1326
|
+
isProcessing: (_a2 = apiState.is_processing) != null ? _a2 : false,
|
|
1327
|
+
messageCount: (_c = (_b = apiState.messages) == null ? void 0 : _b.length) != null ? _c : 0,
|
|
1328
|
+
title: (_d = apiState.title) != null ? _d : null,
|
|
1329
|
+
pendingTxs: (_f = (_e = state.pendingTxs) == null ? void 0 : _e.length) != null ? _f : 0,
|
|
1330
|
+
signedTxs: (_h = (_g = state.signedTxs) == null ? void 0 : _g.length) != null ? _h : 0
|
|
1331
|
+
},
|
|
1332
|
+
null,
|
|
1333
|
+
2
|
|
1334
|
+
)
|
|
1335
|
+
);
|
|
1336
|
+
printDataFileLocation();
|
|
1337
|
+
} finally {
|
|
1338
|
+
session.close();
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
async function eventsCommand() {
|
|
1342
|
+
const state = readState();
|
|
1343
|
+
if (!state) {
|
|
1344
|
+
console.log("No active session");
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
const { session } = getOrCreateSession();
|
|
1348
|
+
try {
|
|
1349
|
+
const events = await session.client.getSystemEvents(state.sessionId);
|
|
1350
|
+
console.log(JSON.stringify(events, null, 2));
|
|
1351
|
+
} finally {
|
|
1352
|
+
session.close();
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
function txCommand() {
|
|
1356
|
+
var _a2, _b, _c;
|
|
1357
|
+
const state = readState();
|
|
1358
|
+
if (!state) {
|
|
1359
|
+
console.log("No active session");
|
|
1360
|
+
printDataFileLocation();
|
|
1361
|
+
return;
|
|
1362
|
+
}
|
|
1363
|
+
const pending = (_a2 = state.pendingTxs) != null ? _a2 : [];
|
|
1364
|
+
const signed = (_b = state.signedTxs) != null ? _b : [];
|
|
1365
|
+
if (pending.length === 0 && signed.length === 0) {
|
|
1366
|
+
console.log("No transactions.");
|
|
1367
|
+
printDataFileLocation();
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
if (pending.length > 0) {
|
|
1371
|
+
console.log(`Pending (${pending.length}):`);
|
|
1372
|
+
for (const tx of pending) {
|
|
1373
|
+
console.log(formatTxLine(tx, " \u23F3"));
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
if (signed.length > 0) {
|
|
1377
|
+
if (pending.length > 0) console.log();
|
|
1378
|
+
console.log(`Signed (${signed.length}):`);
|
|
1379
|
+
for (const tx of signed) {
|
|
1380
|
+
const parts = [` \u2705 ${tx.id}`];
|
|
1381
|
+
if (tx.kind === "eip712_sign") {
|
|
1382
|
+
parts.push(`sig: ${(_c = tx.signature) == null ? void 0 : _c.slice(0, 20)}...`);
|
|
1383
|
+
if (tx.description) parts.push(tx.description);
|
|
1384
|
+
} else {
|
|
1385
|
+
parts.push(`hash: ${tx.txHash}`);
|
|
1386
|
+
if (tx.to) parts.push(`to: ${tx.to}`);
|
|
1387
|
+
if (tx.value) parts.push(`value: ${tx.value}`);
|
|
1388
|
+
}
|
|
1389
|
+
parts.push(`(${new Date(tx.timestamp).toLocaleTimeString()})`);
|
|
1390
|
+
console.log(parts.join(" "));
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
printDataFileLocation();
|
|
1394
|
+
}
|
|
1395
|
+
async function signCommand() {
|
|
1396
|
+
var _a2, _b, _c, _d;
|
|
1397
|
+
const txId = parsed.positional[0];
|
|
1398
|
+
if (!txId) {
|
|
1399
|
+
fatal("Usage: aomi sign <tx-id>\nRun `aomi tx` to see pending transaction IDs.");
|
|
1400
|
+
}
|
|
1401
|
+
const config = getConfig();
|
|
1402
|
+
const privateKey = config.privateKey;
|
|
1403
|
+
if (!privateKey) {
|
|
1404
|
+
fatal("Private key required. Pass --private-key or set PRIVATE_KEY env var.");
|
|
1405
|
+
}
|
|
1406
|
+
const state = readState();
|
|
1407
|
+
if (!state) {
|
|
1408
|
+
fatal("No active session. Run `aomi chat` first.");
|
|
1409
|
+
}
|
|
1410
|
+
const pendingTx = ((_a2 = state.pendingTxs) != null ? _a2 : []).find((t) => t.id === txId);
|
|
1411
|
+
if (!pendingTx) {
|
|
1412
|
+
fatal(`No pending transaction with id "${txId}".
|
|
1413
|
+
Run \`aomi tx\` to see available IDs.`);
|
|
1414
|
+
}
|
|
1415
|
+
const { session } = getOrCreateSession();
|
|
1416
|
+
try {
|
|
1417
|
+
let viem;
|
|
1418
|
+
let viemAccounts;
|
|
1419
|
+
let viemChains;
|
|
1420
|
+
try {
|
|
1421
|
+
viem = await import("viem");
|
|
1422
|
+
viemAccounts = await import("viem/accounts");
|
|
1423
|
+
viemChains = await import("viem/chains");
|
|
1424
|
+
} catch (e) {
|
|
1425
|
+
fatal(
|
|
1426
|
+
"viem is required for `aomi sign`. Install it:\n npm install viem\n # or: pnpm add viem"
|
|
1427
|
+
);
|
|
1428
|
+
}
|
|
1429
|
+
const { createWalletClient, http } = viem;
|
|
1430
|
+
const { privateKeyToAccount } = viemAccounts;
|
|
1431
|
+
const account = privateKeyToAccount(privateKey);
|
|
1432
|
+
const rpcUrl = config.chainRpcUrl;
|
|
1433
|
+
const targetChainId = (_b = pendingTx.chainId) != null ? _b : 1;
|
|
1434
|
+
const chain = (_c = Object.values(viemChains).find(
|
|
1435
|
+
(c) => typeof c === "object" && c !== null && "id" in c && c.id === targetChainId
|
|
1436
|
+
)) != null ? _c : { id: targetChainId, name: `Chain ${targetChainId}`, nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, rpcUrls: { default: { http: [rpcUrl != null ? rpcUrl : ""] } } };
|
|
1437
|
+
const walletClient = createWalletClient({
|
|
1438
|
+
account,
|
|
1439
|
+
chain,
|
|
1440
|
+
transport: http(rpcUrl)
|
|
1441
|
+
});
|
|
1442
|
+
console.log(`Signer: ${account.address}`);
|
|
1443
|
+
console.log(`ID: ${pendingTx.id}`);
|
|
1444
|
+
console.log(`Kind: ${pendingTx.kind}`);
|
|
1445
|
+
if (pendingTx.kind === "transaction") {
|
|
1446
|
+
console.log(`To: ${pendingTx.to}`);
|
|
1447
|
+
if (pendingTx.value) console.log(`Value: ${pendingTx.value}`);
|
|
1448
|
+
if (pendingTx.chainId) console.log(`Chain: ${pendingTx.chainId}`);
|
|
1449
|
+
if (pendingTx.data) console.log(`Data: ${pendingTx.data.slice(0, 40)}...`);
|
|
1450
|
+
console.log();
|
|
1451
|
+
const hash = await walletClient.sendTransaction({
|
|
1452
|
+
to: pendingTx.to,
|
|
1453
|
+
value: pendingTx.value ? BigInt(pendingTx.value) : /* @__PURE__ */ BigInt("0"),
|
|
1454
|
+
data: (_d = pendingTx.data) != null ? _d : void 0
|
|
1455
|
+
});
|
|
1456
|
+
console.log(`\u2705 Sent! Hash: ${hash}`);
|
|
1457
|
+
removePendingTx(state, txId);
|
|
1458
|
+
const freshState = readState();
|
|
1459
|
+
addSignedTx(freshState, {
|
|
1460
|
+
id: txId,
|
|
1461
|
+
kind: "transaction",
|
|
1462
|
+
txHash: hash,
|
|
1463
|
+
from: account.address,
|
|
1464
|
+
to: pendingTx.to,
|
|
1465
|
+
value: pendingTx.value,
|
|
1466
|
+
chainId: pendingTx.chainId,
|
|
1467
|
+
timestamp: Date.now()
|
|
1468
|
+
});
|
|
1469
|
+
await session.client.sendSystemMessage(
|
|
1470
|
+
state.sessionId,
|
|
1471
|
+
JSON.stringify({
|
|
1472
|
+
type: "wallet:tx_complete",
|
|
1473
|
+
payload: { txHash: hash, status: "success" }
|
|
1474
|
+
})
|
|
1475
|
+
);
|
|
1476
|
+
} else {
|
|
1477
|
+
const typedData = pendingTx.payload.typed_data;
|
|
1478
|
+
if (!typedData) {
|
|
1479
|
+
fatal("EIP-712 request is missing typed_data payload.");
|
|
1480
|
+
}
|
|
1481
|
+
if (pendingTx.description) console.log(`Desc: ${pendingTx.description}`);
|
|
1482
|
+
if (typedData.primaryType) console.log(`Type: ${typedData.primaryType}`);
|
|
1483
|
+
console.log();
|
|
1484
|
+
const { domain, types, primaryType, message } = typedData;
|
|
1485
|
+
const sigTypes = __spreadValues({}, types);
|
|
1486
|
+
delete sigTypes["EIP712Domain"];
|
|
1487
|
+
const signature = await walletClient.signTypedData({
|
|
1488
|
+
domain,
|
|
1489
|
+
types: sigTypes,
|
|
1490
|
+
primaryType,
|
|
1491
|
+
message
|
|
1492
|
+
});
|
|
1493
|
+
console.log(`\u2705 Signed! Signature: ${signature.slice(0, 20)}...`);
|
|
1494
|
+
removePendingTx(state, txId);
|
|
1495
|
+
const freshState = readState();
|
|
1496
|
+
addSignedTx(freshState, {
|
|
1497
|
+
id: txId,
|
|
1498
|
+
kind: "eip712_sign",
|
|
1499
|
+
signature,
|
|
1500
|
+
from: account.address,
|
|
1501
|
+
description: pendingTx.description,
|
|
1502
|
+
timestamp: Date.now()
|
|
1503
|
+
});
|
|
1504
|
+
await session.client.sendSystemMessage(
|
|
1505
|
+
state.sessionId,
|
|
1506
|
+
JSON.stringify({
|
|
1507
|
+
type: "wallet_eip712_response",
|
|
1508
|
+
payload: {
|
|
1509
|
+
status: "success",
|
|
1510
|
+
signature,
|
|
1511
|
+
description: pendingTx.description
|
|
1512
|
+
}
|
|
1513
|
+
})
|
|
1514
|
+
);
|
|
1515
|
+
}
|
|
1516
|
+
console.log("Backend notified.");
|
|
1517
|
+
} catch (err) {
|
|
1518
|
+
if (err instanceof CliExit) throw err;
|
|
1519
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1520
|
+
fatal(`\u274C Signing failed: ${errMsg}`);
|
|
1521
|
+
} finally {
|
|
1522
|
+
session.close();
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
async function logCommand() {
|
|
1526
|
+
var _a2, _b, _c, _d, _e;
|
|
1527
|
+
const state = readState();
|
|
1528
|
+
if (!state) {
|
|
1529
|
+
console.log("No active session");
|
|
1530
|
+
printDataFileLocation();
|
|
1531
|
+
return;
|
|
1532
|
+
}
|
|
1533
|
+
const { session } = getOrCreateSession();
|
|
1534
|
+
try {
|
|
1535
|
+
const apiState = await session.client.fetchState(state.sessionId);
|
|
1536
|
+
const messages = (_a2 = apiState.messages) != null ? _a2 : [];
|
|
1537
|
+
if (messages.length === 0) {
|
|
1538
|
+
console.log("No messages in this session.");
|
|
1539
|
+
printDataFileLocation();
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
for (const msg of messages) {
|
|
1543
|
+
let time = "";
|
|
1544
|
+
if (msg.timestamp) {
|
|
1545
|
+
const raw = msg.timestamp;
|
|
1546
|
+
const n = /^\d+$/.test(raw) ? parseInt(raw, 10) : NaN;
|
|
1547
|
+
const date = !isNaN(n) ? new Date(n < 1e12 ? n * 1e3 : n) : new Date(raw);
|
|
1548
|
+
time = isNaN(date.getTime()) ? "" : `${DIM}${date.toLocaleTimeString()}${RESET} `;
|
|
1549
|
+
}
|
|
1550
|
+
const sender = (_b = msg.sender) != null ? _b : "unknown";
|
|
1551
|
+
if (sender === "user") {
|
|
1552
|
+
console.log(`${time}${CYAN}\u{1F464} You:${RESET} ${(_c = msg.content) != null ? _c : ""}`);
|
|
1553
|
+
} else if (sender === "agent" || sender === "assistant") {
|
|
1554
|
+
if (msg.tool_result) {
|
|
1555
|
+
const [toolName, result] = msg.tool_result;
|
|
1556
|
+
console.log(
|
|
1557
|
+
`${time}${GREEN}\u{1F527} [${toolName}]${RESET} ${result.slice(0, 200)}${result.length > 200 ? "\u2026" : ""}`
|
|
1558
|
+
);
|
|
1559
|
+
}
|
|
1560
|
+
if (msg.content) {
|
|
1561
|
+
console.log(`${time}${CYAN}\u{1F916} Agent:${RESET} ${msg.content}`);
|
|
1562
|
+
}
|
|
1563
|
+
} else if (sender === "system") {
|
|
1564
|
+
console.log(`${time}${YELLOW}\u2699\uFE0F System:${RESET} ${(_d = msg.content) != null ? _d : ""}`);
|
|
1565
|
+
} else {
|
|
1566
|
+
console.log(`${time}${DIM}[${sender}]${RESET} ${(_e = msg.content) != null ? _e : ""}`);
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
console.log(`
|
|
1570
|
+
${DIM}\u2014 ${messages.length} messages \u2014${RESET}`);
|
|
1571
|
+
printDataFileLocation();
|
|
1572
|
+
} finally {
|
|
1573
|
+
session.close();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
function closeCommand() {
|
|
1577
|
+
const state = readState();
|
|
1578
|
+
if (state) {
|
|
1579
|
+
const { session } = getOrCreateSession();
|
|
1580
|
+
session.close();
|
|
1581
|
+
}
|
|
1582
|
+
clearState();
|
|
1583
|
+
console.log("Session closed");
|
|
1584
|
+
}
|
|
1585
|
+
function printUsage() {
|
|
1586
|
+
console.log(`
|
|
1587
|
+
aomi \u2014 CLI client for Aomi on-chain agent
|
|
1588
|
+
|
|
1589
|
+
Usage:
|
|
1590
|
+
aomi chat <message> Send a message and print the response
|
|
1591
|
+
aomi chat --verbose Stream agent responses, tool calls, and events live
|
|
1592
|
+
aomi log Show full conversation history with tool results
|
|
1593
|
+
aomi tx List pending and signed transactions
|
|
1594
|
+
aomi sign <tx-id> Sign and submit a pending transaction
|
|
1595
|
+
aomi status Show current session state
|
|
1596
|
+
aomi events List system events
|
|
1597
|
+
aomi close Close the current session
|
|
1598
|
+
|
|
1599
|
+
Options:
|
|
1600
|
+
--backend-url <url> Backend URL (default: https://api.aomi.dev)
|
|
1601
|
+
--api-key <key> API key for non-default namespaces
|
|
1602
|
+
--namespace <ns> Namespace (default: "default")
|
|
1603
|
+
--public-key <addr> Wallet address (so the agent knows your wallet)
|
|
1604
|
+
--private-key <key> Hex private key for signing
|
|
1605
|
+
--rpc-url <url> RPC URL for transaction submission
|
|
1606
|
+
--verbose, -v Show tool calls and streaming output (for chat)
|
|
1607
|
+
|
|
1608
|
+
Environment (overridden by flags):
|
|
1609
|
+
AOMI_BASE_URL Backend URL
|
|
1610
|
+
AOMI_API_KEY API key
|
|
1611
|
+
AOMI_NAMESPACE Namespace
|
|
1612
|
+
AOMI_PUBLIC_KEY Wallet address
|
|
1613
|
+
PRIVATE_KEY Hex private key for signing
|
|
1614
|
+
CHAIN_RPC_URL RPC URL for transaction submission
|
|
1615
|
+
`.trim());
|
|
1616
|
+
}
|
|
1617
|
+
async function main() {
|
|
1618
|
+
var _a2;
|
|
1619
|
+
const cmd = (_a2 = parsed.command) != null ? _a2 : parsed.flags["help"] || parsed.flags["h"] ? "help" : void 0;
|
|
1620
|
+
switch (cmd) {
|
|
1621
|
+
case "chat":
|
|
1622
|
+
await chatCommand();
|
|
1623
|
+
break;
|
|
1624
|
+
case "log":
|
|
1625
|
+
await logCommand();
|
|
1626
|
+
break;
|
|
1627
|
+
case "tx":
|
|
1628
|
+
txCommand();
|
|
1629
|
+
break;
|
|
1630
|
+
case "sign":
|
|
1631
|
+
await signCommand();
|
|
1632
|
+
break;
|
|
1633
|
+
case "status":
|
|
1634
|
+
await statusCommand();
|
|
1635
|
+
break;
|
|
1636
|
+
case "events":
|
|
1637
|
+
await eventsCommand();
|
|
1638
|
+
break;
|
|
1639
|
+
case "close":
|
|
1640
|
+
closeCommand();
|
|
1641
|
+
break;
|
|
1642
|
+
case "help":
|
|
1643
|
+
printUsage();
|
|
1644
|
+
break;
|
|
1645
|
+
default:
|
|
1646
|
+
printUsage();
|
|
1647
|
+
if (cmd) throw new CliExit(1);
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
main().catch((err) => {
|
|
1651
|
+
if (err instanceof CliExit) {
|
|
1652
|
+
process.exit(err.code);
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
console.error(err instanceof Error ? err.message : err);
|
|
1656
|
+
process.exit(1);
|
|
1657
|
+
});
|