@agi-cli/server 0.1.138 → 0.1.139
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/package.json +3 -3
- package/src/routes/sessions.ts +366 -1
- package/sst-env.d.ts +10 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agi-cli/server",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.139",
|
|
4
4
|
"description": "HTTP API server for AGI CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"typecheck": "tsc --noEmit"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@agi-cli/sdk": "0.1.
|
|
33
|
-
"@agi-cli/database": "0.1.
|
|
32
|
+
"@agi-cli/sdk": "0.1.139",
|
|
33
|
+
"@agi-cli/database": "0.1.139",
|
|
34
34
|
"drizzle-orm": "^0.44.5",
|
|
35
35
|
"hono": "^4.9.9",
|
|
36
36
|
"zod": "^4.1.8"
|
package/src/routes/sessions.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import type { Hono } from 'hono';
|
|
2
2
|
import { loadConfig } from '@agi-cli/sdk';
|
|
3
|
+
import { userInfo } from 'node:os';
|
|
3
4
|
import { getDb } from '@agi-cli/database';
|
|
4
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
sessions,
|
|
7
|
+
messages,
|
|
8
|
+
messageParts,
|
|
9
|
+
shares,
|
|
10
|
+
} from '@agi-cli/database/schema';
|
|
5
11
|
import { desc, eq, and, ne, inArray } from 'drizzle-orm';
|
|
6
12
|
import type { ProviderId } from '@agi-cli/sdk';
|
|
7
13
|
import { isProviderId, catalog } from '@agi-cli/sdk';
|
|
@@ -196,6 +202,52 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
196
202
|
}
|
|
197
203
|
});
|
|
198
204
|
|
|
205
|
+
// Delete session
|
|
206
|
+
app.delete('/v1/sessions/:sessionId', async (c) => {
|
|
207
|
+
try {
|
|
208
|
+
const sessionId = c.req.param('sessionId');
|
|
209
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
210
|
+
const cfg = await loadConfig(projectRoot);
|
|
211
|
+
const db = await getDb(cfg.projectRoot);
|
|
212
|
+
|
|
213
|
+
const existingRows = await db
|
|
214
|
+
.select()
|
|
215
|
+
.from(sessions)
|
|
216
|
+
.where(eq(sessions.id, sessionId))
|
|
217
|
+
.limit(1);
|
|
218
|
+
|
|
219
|
+
if (!existingRows.length) {
|
|
220
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const existingSession = existingRows[0];
|
|
224
|
+
|
|
225
|
+
if (existingSession.projectPath !== cfg.projectRoot) {
|
|
226
|
+
return c.json({ error: 'Session not found in this project' }, 404);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
await db
|
|
230
|
+
.delete(messageParts)
|
|
231
|
+
.where(
|
|
232
|
+
inArray(
|
|
233
|
+
messageParts.messageId,
|
|
234
|
+
db
|
|
235
|
+
.select({ id: messages.id })
|
|
236
|
+
.from(messages)
|
|
237
|
+
.where(eq(messages.sessionId, sessionId)),
|
|
238
|
+
),
|
|
239
|
+
);
|
|
240
|
+
await db.delete(messages).where(eq(messages.sessionId, sessionId));
|
|
241
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
242
|
+
|
|
243
|
+
return c.json({ success: true });
|
|
244
|
+
} catch (err) {
|
|
245
|
+
logger.error('Failed to delete session', err);
|
|
246
|
+
const errorResponse = serializeError(err);
|
|
247
|
+
return c.json(errorResponse, errorResponse.error.status || 500);
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
199
251
|
// Abort session stream
|
|
200
252
|
app.delete('/v1/sessions/:sessionId/abort', async (c) => {
|
|
201
253
|
const sessionId = c.req.param('sessionId');
|
|
@@ -331,4 +383,317 @@ export function registerSessionsRoutes(app: Hono) {
|
|
|
331
383
|
|
|
332
384
|
return c.json({ success: false, removed: false }, 404);
|
|
333
385
|
});
|
|
386
|
+
|
|
387
|
+
app.get('/v1/sessions/:sessionId/share', async (c) => {
|
|
388
|
+
const sessionId = c.req.param('sessionId');
|
|
389
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
390
|
+
const cfg = await loadConfig(projectRoot);
|
|
391
|
+
const db = await getDb(cfg.projectRoot);
|
|
392
|
+
|
|
393
|
+
const share = await db
|
|
394
|
+
.select()
|
|
395
|
+
.from(shares)
|
|
396
|
+
.where(eq(shares.sessionId, sessionId))
|
|
397
|
+
.limit(1);
|
|
398
|
+
|
|
399
|
+
if (!share.length) {
|
|
400
|
+
return c.json({ shared: false });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
const allMessages = await db
|
|
404
|
+
.select({ id: messages.id })
|
|
405
|
+
.from(messages)
|
|
406
|
+
.where(eq(messages.sessionId, sessionId))
|
|
407
|
+
.orderBy(messages.createdAt);
|
|
408
|
+
|
|
409
|
+
const totalMessages = allMessages.length;
|
|
410
|
+
const syncedIdx = allMessages.findIndex(
|
|
411
|
+
(m) => m.id === share[0].lastSyncedMessageId,
|
|
412
|
+
);
|
|
413
|
+
const syncedMessages = syncedIdx === -1 ? 0 : syncedIdx + 1;
|
|
414
|
+
const pendingMessages = totalMessages - syncedMessages;
|
|
415
|
+
|
|
416
|
+
return c.json({
|
|
417
|
+
shared: true,
|
|
418
|
+
shareId: share[0].shareId,
|
|
419
|
+
url: share[0].url,
|
|
420
|
+
title: share[0].title,
|
|
421
|
+
createdAt: share[0].createdAt,
|
|
422
|
+
lastSyncedAt: share[0].lastSyncedAt,
|
|
423
|
+
lastSyncedMessageId: share[0].lastSyncedMessageId,
|
|
424
|
+
syncedMessages,
|
|
425
|
+
totalMessages,
|
|
426
|
+
pendingMessages,
|
|
427
|
+
isSynced: pendingMessages === 0,
|
|
428
|
+
});
|
|
429
|
+
});
|
|
430
|
+
|
|
431
|
+
const SHARE_API_URL =
|
|
432
|
+
process.env.AGI_SHARE_API_URL || 'https://api.share.agi.nitish.sh';
|
|
433
|
+
|
|
434
|
+
function getUsername(): string {
|
|
435
|
+
try {
|
|
436
|
+
return userInfo().username;
|
|
437
|
+
} catch {
|
|
438
|
+
return 'anonymous';
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
app.post('/v1/sessions/:sessionId/share', async (c) => {
|
|
443
|
+
const sessionId = c.req.param('sessionId');
|
|
444
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
445
|
+
const cfg = await loadConfig(projectRoot);
|
|
446
|
+
const db = await getDb(cfg.projectRoot);
|
|
447
|
+
|
|
448
|
+
const session = await db
|
|
449
|
+
.select()
|
|
450
|
+
.from(sessions)
|
|
451
|
+
.where(eq(sessions.id, sessionId))
|
|
452
|
+
.limit(1);
|
|
453
|
+
if (!session.length) {
|
|
454
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const existingShare = await db
|
|
458
|
+
.select()
|
|
459
|
+
.from(shares)
|
|
460
|
+
.where(eq(shares.sessionId, sessionId))
|
|
461
|
+
.limit(1);
|
|
462
|
+
if (existingShare.length) {
|
|
463
|
+
return c.json({
|
|
464
|
+
shared: true,
|
|
465
|
+
shareId: existingShare[0].shareId,
|
|
466
|
+
url: existingShare[0].url,
|
|
467
|
+
message: 'Already shared',
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const allMessages = await db
|
|
472
|
+
.select()
|
|
473
|
+
.from(messages)
|
|
474
|
+
.where(eq(messages.sessionId, sessionId))
|
|
475
|
+
.orderBy(messages.createdAt);
|
|
476
|
+
|
|
477
|
+
if (!allMessages.length) {
|
|
478
|
+
return c.json({ error: 'Session has no messages' }, 400);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const msgParts = await db
|
|
482
|
+
.select()
|
|
483
|
+
.from(messageParts)
|
|
484
|
+
.where(
|
|
485
|
+
inArray(
|
|
486
|
+
messageParts.messageId,
|
|
487
|
+
allMessages.map((m) => m.id),
|
|
488
|
+
),
|
|
489
|
+
)
|
|
490
|
+
.orderBy(messageParts.index);
|
|
491
|
+
|
|
492
|
+
const partsByMessage = new Map<string, typeof msgParts>();
|
|
493
|
+
for (const part of msgParts) {
|
|
494
|
+
const list = partsByMessage.get(part.messageId) || [];
|
|
495
|
+
list.push(part);
|
|
496
|
+
partsByMessage.set(part.messageId, list);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const lastMessageId = allMessages[allMessages.length - 1].id;
|
|
500
|
+
const sess = session[0];
|
|
501
|
+
|
|
502
|
+
const sessionData = {
|
|
503
|
+
title: sess.title,
|
|
504
|
+
username: getUsername(),
|
|
505
|
+
agent: sess.agent,
|
|
506
|
+
provider: sess.provider,
|
|
507
|
+
model: sess.model,
|
|
508
|
+
createdAt: sess.createdAt,
|
|
509
|
+
stats: {
|
|
510
|
+
inputTokens: sess.totalInputTokens ?? 0,
|
|
511
|
+
outputTokens: sess.totalOutputTokens ?? 0,
|
|
512
|
+
cachedTokens: sess.totalCachedTokens ?? 0,
|
|
513
|
+
cacheCreationTokens: sess.totalCacheCreationTokens ?? 0,
|
|
514
|
+
reasoningTokens: sess.totalReasoningTokens ?? 0,
|
|
515
|
+
toolTimeMs: sess.totalToolTimeMs ?? 0,
|
|
516
|
+
toolCounts: sess.toolCountsJson ? JSON.parse(sess.toolCountsJson) : {},
|
|
517
|
+
},
|
|
518
|
+
messages: allMessages.map((m) => ({
|
|
519
|
+
id: m.id,
|
|
520
|
+
role: m.role,
|
|
521
|
+
createdAt: m.createdAt,
|
|
522
|
+
parts: (partsByMessage.get(m.id) || []).map((p) => ({
|
|
523
|
+
type: p.type,
|
|
524
|
+
content: p.content,
|
|
525
|
+
toolName: p.toolName,
|
|
526
|
+
toolCallId: p.toolCallId,
|
|
527
|
+
})),
|
|
528
|
+
})),
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const res = await fetch(`${SHARE_API_URL}/share`, {
|
|
532
|
+
method: 'POST',
|
|
533
|
+
headers: { 'Content-Type': 'application/json' },
|
|
534
|
+
body: JSON.stringify({
|
|
535
|
+
sessionData,
|
|
536
|
+
title: sess.title,
|
|
537
|
+
lastMessageId,
|
|
538
|
+
}),
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
if (!res.ok) {
|
|
542
|
+
const err = await res.text();
|
|
543
|
+
return c.json({ error: `Failed to create share: ${err}` }, 500);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const data = (await res.json()) as {
|
|
547
|
+
shareId: string;
|
|
548
|
+
secret: string;
|
|
549
|
+
url: string;
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
await db.insert(shares).values({
|
|
553
|
+
sessionId,
|
|
554
|
+
shareId: data.shareId,
|
|
555
|
+
secret: data.secret,
|
|
556
|
+
url: data.url,
|
|
557
|
+
title: sess.title,
|
|
558
|
+
description: null,
|
|
559
|
+
createdAt: Date.now(),
|
|
560
|
+
lastSyncedAt: Date.now(),
|
|
561
|
+
lastSyncedMessageId: lastMessageId,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
return c.json({
|
|
565
|
+
shared: true,
|
|
566
|
+
shareId: data.shareId,
|
|
567
|
+
url: data.url,
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
app.put('/v1/sessions/:sessionId/share', async (c) => {
|
|
572
|
+
const sessionId = c.req.param('sessionId');
|
|
573
|
+
const projectRoot = c.req.query('project') || process.cwd();
|
|
574
|
+
const cfg = await loadConfig(projectRoot);
|
|
575
|
+
const db = await getDb(cfg.projectRoot);
|
|
576
|
+
|
|
577
|
+
const share = await db
|
|
578
|
+
.select()
|
|
579
|
+
.from(shares)
|
|
580
|
+
.where(eq(shares.sessionId, sessionId))
|
|
581
|
+
.limit(1);
|
|
582
|
+
if (!share.length) {
|
|
583
|
+
return c.json({ error: 'Session not shared. Use share first.' }, 400);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const session = await db
|
|
587
|
+
.select()
|
|
588
|
+
.from(sessions)
|
|
589
|
+
.where(eq(sessions.id, sessionId))
|
|
590
|
+
.limit(1);
|
|
591
|
+
if (!session.length) {
|
|
592
|
+
return c.json({ error: 'Session not found' }, 404);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const allMessages = await db
|
|
596
|
+
.select()
|
|
597
|
+
.from(messages)
|
|
598
|
+
.where(eq(messages.sessionId, sessionId))
|
|
599
|
+
.orderBy(messages.createdAt);
|
|
600
|
+
|
|
601
|
+
const msgParts = await db
|
|
602
|
+
.select()
|
|
603
|
+
.from(messageParts)
|
|
604
|
+
.where(
|
|
605
|
+
inArray(
|
|
606
|
+
messageParts.messageId,
|
|
607
|
+
allMessages.map((m) => m.id),
|
|
608
|
+
),
|
|
609
|
+
)
|
|
610
|
+
.orderBy(messageParts.index);
|
|
611
|
+
|
|
612
|
+
const partsByMessage = new Map<string, typeof msgParts>();
|
|
613
|
+
for (const part of msgParts) {
|
|
614
|
+
const list = partsByMessage.get(part.messageId) || [];
|
|
615
|
+
list.push(part);
|
|
616
|
+
partsByMessage.set(part.messageId, list);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const lastSyncedIdx = allMessages.findIndex(
|
|
620
|
+
(m) => m.id === share[0].lastSyncedMessageId,
|
|
621
|
+
);
|
|
622
|
+
const newMessages =
|
|
623
|
+
lastSyncedIdx === -1 ? allMessages : allMessages.slice(lastSyncedIdx + 1);
|
|
624
|
+
const lastMessageId =
|
|
625
|
+
allMessages[allMessages.length - 1]?.id ?? share[0].lastSyncedMessageId;
|
|
626
|
+
|
|
627
|
+
if (newMessages.length === 0) {
|
|
628
|
+
return c.json({
|
|
629
|
+
synced: true,
|
|
630
|
+
url: share[0].url,
|
|
631
|
+
newMessages: 0,
|
|
632
|
+
message: 'Already synced',
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
const sess = session[0];
|
|
637
|
+
const sessionData = {
|
|
638
|
+
title: sess.title,
|
|
639
|
+
username: getUsername(),
|
|
640
|
+
agent: sess.agent,
|
|
641
|
+
provider: sess.provider,
|
|
642
|
+
model: sess.model,
|
|
643
|
+
createdAt: sess.createdAt,
|
|
644
|
+
stats: {
|
|
645
|
+
inputTokens: sess.totalInputTokens ?? 0,
|
|
646
|
+
outputTokens: sess.totalOutputTokens ?? 0,
|
|
647
|
+
cachedTokens: sess.totalCachedTokens ?? 0,
|
|
648
|
+
cacheCreationTokens: sess.totalCacheCreationTokens ?? 0,
|
|
649
|
+
reasoningTokens: sess.totalReasoningTokens ?? 0,
|
|
650
|
+
toolTimeMs: sess.totalToolTimeMs ?? 0,
|
|
651
|
+
toolCounts: sess.toolCountsJson ? JSON.parse(sess.toolCountsJson) : {},
|
|
652
|
+
},
|
|
653
|
+
messages: allMessages.map((m) => ({
|
|
654
|
+
id: m.id,
|
|
655
|
+
role: m.role,
|
|
656
|
+
createdAt: m.createdAt,
|
|
657
|
+
parts: (partsByMessage.get(m.id) || []).map((p) => ({
|
|
658
|
+
type: p.type,
|
|
659
|
+
content: p.content,
|
|
660
|
+
toolName: p.toolName,
|
|
661
|
+
toolCallId: p.toolCallId,
|
|
662
|
+
})),
|
|
663
|
+
})),
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const res = await fetch(`${SHARE_API_URL}/share/${share[0].shareId}`, {
|
|
667
|
+
method: 'PUT',
|
|
668
|
+
headers: {
|
|
669
|
+
'Content-Type': 'application/json',
|
|
670
|
+
'X-Share-Secret': share[0].secret,
|
|
671
|
+
},
|
|
672
|
+
body: JSON.stringify({
|
|
673
|
+
sessionData,
|
|
674
|
+
title: sess.title,
|
|
675
|
+
lastMessageId,
|
|
676
|
+
}),
|
|
677
|
+
});
|
|
678
|
+
|
|
679
|
+
if (!res.ok) {
|
|
680
|
+
const err = await res.text();
|
|
681
|
+
return c.json({ error: `Failed to sync share: ${err}` }, 500);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
await db
|
|
685
|
+
.update(shares)
|
|
686
|
+
.set({
|
|
687
|
+
title: sess.title,
|
|
688
|
+
lastSyncedAt: Date.now(),
|
|
689
|
+
lastSyncedMessageId: lastMessageId,
|
|
690
|
+
})
|
|
691
|
+
.where(eq(shares.sessionId, sessionId));
|
|
692
|
+
|
|
693
|
+
return c.json({
|
|
694
|
+
synced: true,
|
|
695
|
+
url: share[0].url,
|
|
696
|
+
newMessages: newMessages.length,
|
|
697
|
+
});
|
|
698
|
+
});
|
|
334
699
|
}
|
package/sst-env.d.ts
ADDED