@agi-cli/server 0.1.138 → 0.1.140

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agi-cli/server",
3
- "version": "0.1.138",
3
+ "version": "0.1.140",
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.138",
33
- "@agi-cli/database": "0.1.138",
32
+ "@agi-cli/sdk": "0.1.140",
33
+ "@agi-cli/database": "0.1.140",
34
34
  "drizzle-orm": "^0.44.5",
35
35
  "hono": "^4.9.9",
36
36
  "zod": "^4.1.8"
@@ -91,8 +91,8 @@ export const schemas = {
91
91
  createdAt: { type: 'integer', format: 'int64' },
92
92
  completedAt: { type: 'integer', format: 'int64', nullable: true },
93
93
  latencyMs: { type: 'integer', nullable: true },
94
- promptTokens: { type: 'integer', nullable: true },
95
- completionTokens: { type: 'integer', nullable: true },
94
+ inputTokens: { type: 'integer', nullable: true },
95
+ outputTokens: { type: 'integer', nullable: true },
96
96
  totalTokens: { type: 'integer', nullable: true },
97
97
  error: { type: 'string', nullable: true },
98
98
  },
@@ -206,7 +206,7 @@ export const schemas = {
206
206
  id: { type: 'string' },
207
207
  label: { type: 'string' },
208
208
  toolCall: { type: 'boolean' },
209
- reasoning: { type: 'boolean' },
209
+ reasoningText: { type: 'boolean' },
210
210
  },
211
211
  required: ['id', 'label'],
212
212
  },
@@ -59,7 +59,7 @@ export function registerModelsRoutes(app: Hono) {
59
59
  id: m.id,
60
60
  label: m.label || m.id,
61
61
  toolCall: m.toolCall,
62
- reasoning: m.reasoning,
62
+ reasoningText: m.reasoningText,
63
63
  vision: m.modalities?.input?.includes('image') ?? false,
64
64
  })),
65
65
  default: getDefault(
@@ -97,7 +97,7 @@ export function registerModelsRoutes(app: Hono) {
97
97
  id: string;
98
98
  label: string;
99
99
  toolCall?: boolean;
100
- reasoning?: boolean;
100
+ reasoningText?: boolean;
101
101
  }>;
102
102
  }
103
103
  > = {};
@@ -122,7 +122,7 @@ export function registerModelsRoutes(app: Hono) {
122
122
  id: m.id,
123
123
  label: m.label || m.id,
124
124
  toolCall: m.toolCall,
125
- reasoning: m.reasoning,
125
+ reasoningText: m.reasoningText,
126
126
  vision: m.modalities?.input?.includes('image') ?? false,
127
127
  })),
128
128
  };
@@ -159,7 +159,7 @@ Commit message:`;
159
159
  model,
160
160
  system: systemPrompt,
161
161
  prompt: userPrompt,
162
- maxTokens: 500,
162
+ maxOutputTokens: 500,
163
163
  });
164
164
 
165
165
  const message = text.trim();
@@ -1,4 +1,4 @@
1
- import { z } from 'zod';
1
+ import { z } from 'zod/v3';
2
2
 
3
3
  export const gitStatusSchema = z.object({
4
4
  project: z.string().optional(),
@@ -122,7 +122,7 @@ export function registerSessionMessagesRoutes(app: Hono) {
122
122
  typeOf: typeof userContext,
123
123
  });
124
124
 
125
- const reasoning = body?.reasoning === true;
125
+ const reasoning = body?.reasoningText === true;
126
126
 
127
127
  // Validate model capabilities if tools are allowed for this agent
128
128
  const wantsToolCalls = true; // agent toolset may be non-empty
@@ -156,7 +156,7 @@ export function registerSessionMessagesRoutes(app: Hono) {
156
156
  content,
157
157
  oneShot: Boolean(body?.oneShot),
158
158
  userContext,
159
- reasoning,
159
+ reasoningText: reasoning,
160
160
  images,
161
161
  files,
162
162
  });
@@ -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 { sessions, messages, messageParts } from '@agi-cli/database/schema';
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
  }
@@ -1,4 +1,4 @@
1
- import { loadConfig, catalog } from '@agi-cli/sdk';
1
+ import { loadConfig, getUnderlyingProviderKey } from '@agi-cli/sdk';
2
2
  import { getDb } from '@agi-cli/database';
3
3
  import { sessions } from '@agi-cli/database/schema';
4
4
  import { eq } from 'drizzle-orm';
@@ -40,16 +40,6 @@ export interface SetupResult {
40
40
 
41
41
  const THINKING_BUDGET = 16000;
42
42
 
43
- function getSolforgeUnderlyingProvider(
44
- model: string,
45
- ): 'anthropic' | 'openai' | null {
46
- const entry = catalog.solforge?.models?.find((m) => m.id === model);
47
- const npm = entry?.provider?.npm;
48
- if (npm === '@ai-sdk/anthropic') return 'anthropic';
49
- if (npm === '@ai-sdk/openai') return 'openai';
50
- return null;
51
- }
52
-
53
43
  export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
54
44
  const cfgTimer = time('runner:loadConfig+db');
55
45
  const cfg = await loadConfig(opts.projectRoot);
@@ -231,36 +221,31 @@ export async function setupRunner(opts: RunOpts): Promise<SetupResult> {
231
221
  const providerOptions: Record<string, unknown> = {};
232
222
  let effectiveMaxOutputTokens = maxOutputTokens;
233
223
 
234
- if (opts.reasoning) {
235
- if (opts.provider === 'anthropic') {
224
+ if (opts.reasoningText) {
225
+ const underlyingProvider = getUnderlyingProviderKey(
226
+ opts.provider,
227
+ opts.model,
228
+ );
229
+
230
+ if (underlyingProvider === 'anthropic') {
236
231
  providerOptions.anthropic = {
237
232
  thinking: { type: 'enabled', budgetTokens: THINKING_BUDGET },
238
233
  };
239
234
  if (maxOutputTokens && maxOutputTokens > THINKING_BUDGET) {
240
235
  effectiveMaxOutputTokens = maxOutputTokens - THINKING_BUDGET;
241
236
  }
242
- } else if (opts.provider === 'openai') {
237
+ } else if (underlyingProvider === 'openai') {
243
238
  providerOptions.openai = {
244
239
  reasoningSummary: 'auto',
245
240
  };
246
- } else if (opts.provider === 'google') {
241
+ } else if (underlyingProvider === 'google') {
247
242
  providerOptions.google = {
248
243
  thinkingConfig: { thinkingBudget: THINKING_BUDGET },
249
244
  };
250
- } else if (opts.provider === 'solforge') {
251
- const underlying = getSolforgeUnderlyingProvider(opts.model);
252
- if (underlying === 'anthropic') {
253
- providerOptions.anthropic = {
254
- thinking: { type: 'enabled', budgetTokens: THINKING_BUDGET },
255
- };
256
- if (maxOutputTokens && maxOutputTokens > THINKING_BUDGET) {
257
- effectiveMaxOutputTokens = maxOutputTokens - THINKING_BUDGET;
258
- }
259
- } else if (underlying === 'openai') {
260
- providerOptions.openai = {
261
- reasoningSummary: 'auto',
262
- };
263
- }
245
+ } else if (underlyingProvider === 'openai-compatible') {
246
+ providerOptions['openai-compatible'] = {
247
+ reasoningEffort: 'high',
248
+ };
264
249
  }
265
250
  }
266
251
 
@@ -9,7 +9,7 @@ export interface TokenUsage {
9
9
  output: number;
10
10
  cacheRead?: number;
11
11
  cacheWrite?: number;
12
- reasoning?: number;
12
+ reasoningText?: number;
13
13
  }
14
14
 
15
15
  export interface ModelLimits {
@@ -17,7 +17,10 @@ export interface ModelLimits {
17
17
  output: number;
18
18
  }
19
19
 
20
- export function isOverflow(tokens: TokenUsage, limits: ModelLimits): boolean {
20
+ export function isOverflow(
21
+ tokens: LanguageModelUsage,
22
+ limits: ModelLimits,
23
+ ): boolean {
21
24
  if (limits.context === 0) return false;
22
25
 
23
26
  const count =
@@ -211,7 +211,7 @@ export async function buildHistoryMessages(
211
211
  }
212
212
  }
213
213
 
214
- return convertToModelMessages(ui);
214
+ return await convertToModelMessages(ui);
215
215
  }
216
216
 
217
217
  async function logPendingToolParts(
@@ -23,7 +23,7 @@ type DispatchOptions = {
23
23
  content: string;
24
24
  oneShot?: boolean;
25
25
  userContext?: string;
26
- reasoning?: boolean;
26
+ reasoningText?: boolean;
27
27
  images?: Array<{ data: string; mediaType: string }>;
28
28
  files?: Array<{
29
29
  type: 'image' | 'pdf' | 'text';
@@ -47,7 +47,7 @@ export async function dispatchAssistantMessage(
47
47
  content,
48
48
  oneShot,
49
49
  userContext,
50
- reasoning,
50
+ reasoningText,
51
51
  images,
52
52
  files,
53
53
  } = options;
@@ -181,7 +181,7 @@ export async function dispatchAssistantMessage(
181
181
  projectRoot: cfg.projectRoot,
182
182
  oneShot: Boolean(oneShot),
183
183
  userContext,
184
- reasoning,
184
+ reasoningText,
185
185
  isCompactCommand: isCompact,
186
186
  compactionContext,
187
187
  },
@@ -115,8 +115,8 @@ export async function createBranch({
115
115
  createdAt: msg.createdAt,
116
116
  completedAt: msg.completedAt,
117
117
  latencyMs: msg.latencyMs,
118
- promptTokens: msg.promptTokens,
119
- completionTokens: msg.completionTokens,
118
+ inputTokens: msg.inputTokens,
119
+ outputTokens: msg.outputTokens,
120
120
  totalTokens: msg.totalTokens,
121
121
  cachedInputTokens: msg.cachedInputTokens,
122
122
  cacheCreationInputTokens: msg.cacheCreationInputTokens,
@@ -27,7 +27,7 @@ export type ProviderMetadata = Record<string, unknown> & {
27
27
 
28
28
  export function normalizeUsage(
29
29
  usage: UsageData,
30
- providerMetadata: ProviderMetadata | undefined,
30
+ providerOptions: ProviderMetadata | undefined,
31
31
  provider: ProviderId,
32
32
  ): UsageData {
33
33
  const rawInputTokens = Number(usage.inputTokens ?? 0);
@@ -37,17 +37,17 @@ export function normalizeUsage(
37
37
  const cachedInputTokens =
38
38
  usage.cachedInputTokens != null
39
39
  ? Number(usage.cachedInputTokens)
40
- : providerMetadata?.openai?.cachedPromptTokens != null
41
- ? Number(providerMetadata.openai.cachedPromptTokens)
42
- : providerMetadata?.anthropic?.cacheReadInputTokens != null
43
- ? Number(providerMetadata.anthropic.cacheReadInputTokens)
40
+ : providerOptions?.openai?.cachedPromptTokens != null
41
+ ? Number(providerOptions.openai.cachedPromptTokens)
42
+ : providerOptions?.anthropic?.cacheReadInputTokens != null
43
+ ? Number(providerOptions.anthropic.cacheReadInputTokens)
44
44
  : undefined;
45
45
 
46
46
  const cacheCreationInputTokens =
47
47
  usage.cacheCreationInputTokens != null
48
48
  ? Number(usage.cacheCreationInputTokens)
49
- : providerMetadata?.anthropic?.cacheCreationInputTokens != null
50
- ? Number(providerMetadata.anthropic.cacheCreationInputTokens)
49
+ : providerOptions?.anthropic?.cacheCreationInputTokens != null
50
+ ? Number(providerOptions.anthropic.cacheCreationInputTokens)
51
51
  : undefined;
52
52
 
53
53
  const cachedValue = cachedInputTokens ?? 0;
@@ -99,18 +99,14 @@ export function resolveUsageProvider(
99
99
  */
100
100
  export async function updateSessionTokensIncremental(
101
101
  usage: UsageData,
102
- providerMetadata: ProviderMetadata | undefined,
102
+ providerOptions: ProviderMetadata | undefined,
103
103
  opts: RunOpts,
104
104
  db: Awaited<ReturnType<typeof getDb>>,
105
105
  ) {
106
106
  if (!usage || !db) return;
107
107
 
108
108
  const usageProvider = resolveUsageProvider(opts.provider, opts.model);
109
- const normalizedUsage = normalizeUsage(
110
- usage,
111
- providerMetadata,
112
- usageProvider,
113
- );
109
+ const normalizedUsage = normalizeUsage(usage, providerOptions, usageProvider);
114
110
 
115
111
  // Read session totals
116
112
  const sessRows = await db
@@ -134,8 +130,8 @@ export async function updateSessionTokensIncremental(
134
130
  .where(eq(messages.id, opts.assistantMessageId));
135
131
 
136
132
  const msg = msgRows[0];
137
- const priorPromptMsg = Number(msg?.promptTokens ?? 0);
138
- const priorCompletionMsg = Number(msg?.completionTokens ?? 0);
133
+ const priorPromptMsg = Number(msg?.inputTokens ?? 0);
134
+ const priorCompletionMsg = Number(msg?.outputTokens ?? 0);
139
135
  const priorCachedMsg = Number(msg?.cachedInputTokens ?? 0);
140
136
  const priorCacheCreationMsg = Number(msg?.cacheCreationInputTokens ?? 0);
141
137
  const priorReasoningMsg = Number(msg?.reasoningTokens ?? 0);
@@ -231,18 +227,14 @@ export async function updateSessionTokens(
231
227
  */
232
228
  export async function updateMessageTokensIncremental(
233
229
  usage: UsageData,
234
- providerMetadata: ProviderMetadata | undefined,
230
+ providerOptions: ProviderMetadata | undefined,
235
231
  opts: RunOpts,
236
232
  db: Awaited<ReturnType<typeof getDb>>,
237
233
  ) {
238
234
  if (!usage || !db) return;
239
235
 
240
236
  const usageProvider = resolveUsageProvider(opts.provider, opts.model);
241
- const normalizedUsage = normalizeUsage(
242
- usage,
243
- providerMetadata,
244
- usageProvider,
245
- );
237
+ const normalizedUsage = normalizeUsage(usage, providerOptions, usageProvider);
246
238
 
247
239
  const msgRows = await db
248
240
  .select()
@@ -251,8 +243,8 @@ export async function updateMessageTokensIncremental(
251
243
 
252
244
  if (msgRows.length > 0 && msgRows[0]) {
253
245
  const msg = msgRows[0];
254
- const priorPrompt = Number(msg.promptTokens ?? 0);
255
- const priorCompletion = Number(msg.completionTokens ?? 0);
246
+ const priorPrompt = Number(msg.inputTokens ?? 0);
247
+ const priorCompletion = Number(msg.outputTokens ?? 0);
256
248
  const priorCached = Number(msg.cachedInputTokens ?? 0);
257
249
  const priorCacheCreation = Number(msg.cacheCreationInputTokens ?? 0);
258
250
  const priorReasoning = Number(msg.reasoningTokens ?? 0);
@@ -287,8 +279,8 @@ export async function updateMessageTokensIncremental(
287
279
  await db
288
280
  .update(messages)
289
281
  .set({
290
- promptTokens: cumPrompt,
291
- completionTokens: cumCompletion,
282
+ inputTokens: cumPrompt,
283
+ outputTokens: cumCompletion,
292
284
  totalTokens: cumTotal,
293
285
  cachedInputTokens: cumCached,
294
286
  cacheCreationInputTokens: cumCacheCreation,
@@ -10,7 +10,7 @@ export type RunOpts = {
10
10
  projectRoot: string;
11
11
  oneShot?: boolean;
12
12
  userContext?: string;
13
- reasoning?: boolean;
13
+ reasoningText?: boolean;
14
14
  abortSignal?: AbortSignal;
15
15
  isCompactCommand?: boolean;
16
16
  compactionContext?: string;
@@ -73,8 +73,8 @@ export function createFinishHandler(
73
73
 
74
74
  const usage = sessRows[0]
75
75
  ? {
76
- inputTokens: Number(sessRows[0].promptTokens ?? 0),
77
- outputTokens: Number(sessRows[0].completionTokens ?? 0),
76
+ inputTokens: Number(sessRows[0].inputTokens ?? 0),
77
+ outputTokens: Number(sessRows[0].outputTokens ?? 0),
78
78
  totalTokens: Number(sessRows[0].totalTokens ?? 0),
79
79
  cachedInputTokens: Number(sessRows[0].cachedInputTokens ?? 0),
80
80
  cacheCreationInputTokens: Number(
@@ -97,7 +97,7 @@ export function createFinishHandler(
97
97
  try {
98
98
  const limits = getModelLimits(opts.provider, opts.model);
99
99
  if (limits) {
100
- const tokenUsage: TokenUsage = {
100
+ const tokenUsage: LanguageModelUsage = {
101
101
  input: usage.inputTokens ?? 0,
102
102
  output: usage.outputTokens ?? 0,
103
103
  cacheRead:
@@ -18,13 +18,13 @@ export function createStepFinishHandler(
18
18
  sharedCtx: ToolAdapterContext,
19
19
  updateSessionTokensIncrementalFn: (
20
20
  usage: UsageData,
21
- providerMetadata: ProviderMetadata | undefined,
21
+ providerOptions: ProviderMetadata | undefined,
22
22
  opts: RunOpts,
23
23
  db: Awaited<ReturnType<typeof getDb>>,
24
24
  ) => Promise<void>,
25
25
  updateMessageTokensIncrementalFn: (
26
26
  usage: UsageData,
27
- providerMetadata: ProviderMetadata | undefined,
27
+ providerOptions: ProviderMetadata | undefined,
28
28
  opts: RunOpts,
29
29
  db: Awaited<ReturnType<typeof getDb>>,
30
30
  ) => Promise<void>,
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v3';
3
3
  import { getDb } from '@agi-cli/database';
4
4
  import { sessions, messages, messageParts } from '@agi-cli/database/schema';
5
5
  import { eq, asc, count } from 'drizzle-orm';
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v3';
3
3
  import { getDb } from '@agi-cli/database';
4
4
  import { sessions, messages, messageParts } from '@agi-cli/database/schema';
5
5
  import { eq, asc, count } from 'drizzle-orm';
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v3';
3
3
 
4
4
  const sessionLinkSchema = z.object({
5
5
  sessionId: z.string().describe('The session ID to link to'),
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v3';
3
3
  import { getDb } from '@agi-cli/database';
4
4
  import { sessions, messages, messageParts } from '@agi-cli/database/schema';
5
5
  import { eq, desc, asc, gte, lte, and, like, count, sql } from 'drizzle-orm';
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v3';
3
3
  import { getDb } from '@agi-cli/database';
4
4
  import { sessions, messages } from '@agi-cli/database/schema';
5
5
  import { eq, desc, asc, gte, lte, and, sql, count } from 'drizzle-orm';
@@ -1,5 +1,5 @@
1
1
  import { tool } from 'ai';
2
- import { z } from 'zod';
2
+ import { z } from 'zod/v3';
3
3
  import { getDb } from '@agi-cli/database';
4
4
  import { sessions, messages, messageParts } from '@agi-cli/database/schema';
5
5
  import { eq, desc, asc, like, and, sql } from 'drizzle-orm';
package/sst-env.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ /* This file is auto-generated by SST. Do not edit. */
2
+ /* tslint:disable */
3
+ /* eslint-disable */
4
+ /* deno-fmt-ignore-file */
5
+ /* biome-ignore-all lint: auto-generated */
6
+
7
+ /// <reference path="../../sst-env.d.ts" />
8
+
9
+ import 'sst';
10
+ export {};