@agi-cli/server 0.1.59 → 0.1.62

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.59",
3
+ "version": "0.1.62",
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.59",
33
- "@agi-cli/database": "0.1.59",
32
+ "@agi-cli/sdk": "0.1.62",
33
+ "@agi-cli/database": "0.1.62",
34
34
  "drizzle-orm": "^0.44.5",
35
35
  "hono": "^4.9.9"
36
36
  },
@@ -14,6 +14,8 @@ export function getOpenAPISpec() {
14
14
  { name: 'messages' },
15
15
  { name: 'stream' },
16
16
  { name: 'ask' },
17
+ { name: 'config' },
18
+ { name: 'git' },
17
19
  ],
18
20
  paths: {
19
21
  '/v1/ask': {
@@ -237,6 +239,498 @@ export function getOpenAPISpec() {
237
239
  },
238
240
  },
239
241
  },
242
+ '/v1/sessions/{sessionId}/abort': {
243
+ delete: {
244
+ tags: ['sessions'],
245
+ operationId: 'abortSession',
246
+ summary: 'Abort a running session',
247
+ description:
248
+ 'Aborts any currently running assistant generation for the session',
249
+ parameters: [
250
+ {
251
+ in: 'path',
252
+ name: 'sessionId',
253
+ required: true,
254
+ schema: { type: 'string' },
255
+ description: 'Session ID to abort',
256
+ },
257
+ ],
258
+ responses: {
259
+ 200: {
260
+ description: 'OK',
261
+ content: {
262
+ 'application/json': {
263
+ schema: {
264
+ type: 'object',
265
+ properties: { success: { type: 'boolean' } },
266
+ required: ['success'],
267
+ },
268
+ },
269
+ },
270
+ },
271
+ },
272
+ },
273
+ },
274
+ '/v1/config': {
275
+ get: {
276
+ tags: ['config'],
277
+ operationId: 'getConfig',
278
+ summary: 'Get full configuration',
279
+ description:
280
+ 'Returns agents, authorized providers, models, and defaults',
281
+ parameters: [projectQueryParam()],
282
+ responses: {
283
+ 200: {
284
+ description: 'OK',
285
+ content: {
286
+ 'application/json': {
287
+ schema: { $ref: '#/components/schemas/Config' },
288
+ },
289
+ },
290
+ },
291
+ },
292
+ },
293
+ },
294
+ '/v1/config/cwd': {
295
+ get: {
296
+ tags: ['config'],
297
+ operationId: 'getCwd',
298
+ summary: 'Get current working directory info',
299
+ responses: {
300
+ 200: {
301
+ description: 'OK',
302
+ content: {
303
+ 'application/json': {
304
+ schema: {
305
+ type: 'object',
306
+ properties: {
307
+ cwd: { type: 'string' },
308
+ dirName: { type: 'string' },
309
+ },
310
+ required: ['cwd', 'dirName'],
311
+ },
312
+ },
313
+ },
314
+ },
315
+ },
316
+ },
317
+ },
318
+ '/v1/config/agents': {
319
+ get: {
320
+ tags: ['config'],
321
+ operationId: 'getAgents',
322
+ summary: 'Get available agents',
323
+ parameters: [projectQueryParam()],
324
+ responses: {
325
+ 200: {
326
+ description: 'OK',
327
+ content: {
328
+ 'application/json': {
329
+ schema: {
330
+ type: 'object',
331
+ properties: {
332
+ agents: {
333
+ type: 'array',
334
+ items: { type: 'string' },
335
+ },
336
+ default: { type: 'string' },
337
+ },
338
+ required: ['agents', 'default'],
339
+ },
340
+ },
341
+ },
342
+ },
343
+ },
344
+ },
345
+ },
346
+ '/v1/config/providers': {
347
+ get: {
348
+ tags: ['config'],
349
+ operationId: 'getProviders',
350
+ summary: 'Get available providers',
351
+ description: 'Returns only providers that have been authorized',
352
+ parameters: [projectQueryParam()],
353
+ responses: {
354
+ 200: {
355
+ description: 'OK',
356
+ content: {
357
+ 'application/json': {
358
+ schema: {
359
+ type: 'object',
360
+ properties: {
361
+ providers: {
362
+ type: 'array',
363
+ items: { $ref: '#/components/schemas/Provider' },
364
+ },
365
+ default: { $ref: '#/components/schemas/Provider' },
366
+ },
367
+ required: ['providers', 'default'],
368
+ },
369
+ },
370
+ },
371
+ },
372
+ },
373
+ },
374
+ },
375
+ '/v1/config/providers/{provider}/models': {
376
+ get: {
377
+ tags: ['config'],
378
+ operationId: 'getProviderModels',
379
+ summary: 'Get available models for a provider',
380
+ parameters: [
381
+ projectQueryParam(),
382
+ {
383
+ in: 'path',
384
+ name: 'provider',
385
+ required: true,
386
+ schema: { $ref: '#/components/schemas/Provider' },
387
+ },
388
+ ],
389
+ responses: {
390
+ 200: {
391
+ description: 'OK',
392
+ content: {
393
+ 'application/json': {
394
+ schema: {
395
+ type: 'object',
396
+ properties: {
397
+ models: {
398
+ type: 'array',
399
+ items: { $ref: '#/components/schemas/Model' },
400
+ },
401
+ default: { type: 'string', nullable: true },
402
+ },
403
+ required: ['models'],
404
+ },
405
+ },
406
+ },
407
+ },
408
+ 403: {
409
+ description: 'Provider not authorized',
410
+ content: {
411
+ 'application/json': {
412
+ schema: {
413
+ type: 'object',
414
+ properties: { error: { type: 'string' } },
415
+ required: ['error'],
416
+ },
417
+ },
418
+ },
419
+ },
420
+ 404: {
421
+ description: 'Provider not found',
422
+ content: {
423
+ 'application/json': {
424
+ schema: {
425
+ type: 'object',
426
+ properties: { error: { type: 'string' } },
427
+ required: ['error'],
428
+ },
429
+ },
430
+ },
431
+ },
432
+ },
433
+ },
434
+ },
435
+ '/v1/git/status': {
436
+ get: {
437
+ tags: ['git'],
438
+ operationId: 'getGitStatus',
439
+ summary: 'Get git status',
440
+ description:
441
+ 'Returns current git status including staged, unstaged, and untracked files',
442
+ parameters: [projectQueryParam()],
443
+ responses: {
444
+ 200: {
445
+ description: 'OK',
446
+ content: {
447
+ 'application/json': {
448
+ schema: {
449
+ type: 'object',
450
+ properties: {
451
+ status: { type: 'string', enum: ['ok'] },
452
+ data: { $ref: '#/components/schemas/GitStatus' },
453
+ },
454
+ required: ['status', 'data'],
455
+ },
456
+ },
457
+ },
458
+ },
459
+ 400: gitErrorResponse(),
460
+ 500: gitErrorResponse(),
461
+ },
462
+ },
463
+ },
464
+ '/v1/git/diff': {
465
+ get: {
466
+ tags: ['git'],
467
+ operationId: 'getGitDiff',
468
+ summary: 'Get git diff for a file',
469
+ parameters: [
470
+ projectQueryParam(),
471
+ {
472
+ in: 'query',
473
+ name: 'file',
474
+ required: true,
475
+ schema: { type: 'string' },
476
+ description: 'File path to get diff for',
477
+ },
478
+ {
479
+ in: 'query',
480
+ name: 'staged',
481
+ required: false,
482
+ schema: { type: 'string', enum: ['true', 'false'] },
483
+ description: 'Show staged diff (default: unstaged)',
484
+ },
485
+ ],
486
+ responses: {
487
+ 200: {
488
+ description: 'OK',
489
+ content: {
490
+ 'application/json': {
491
+ schema: {
492
+ type: 'object',
493
+ properties: {
494
+ status: { type: 'string', enum: ['ok'] },
495
+ data: { $ref: '#/components/schemas/GitDiff' },
496
+ },
497
+ required: ['status', 'data'],
498
+ },
499
+ },
500
+ },
501
+ },
502
+ 400: gitErrorResponse(),
503
+ 500: gitErrorResponse(),
504
+ },
505
+ },
506
+ },
507
+ '/v1/git/branch': {
508
+ get: {
509
+ tags: ['git'],
510
+ operationId: 'getGitBranch',
511
+ summary: 'Get git branch information',
512
+ parameters: [projectQueryParam()],
513
+ responses: {
514
+ 200: {
515
+ description: 'OK',
516
+ content: {
517
+ 'application/json': {
518
+ schema: {
519
+ type: 'object',
520
+ properties: {
521
+ status: { type: 'string', enum: ['ok'] },
522
+ data: { $ref: '#/components/schemas/GitBranch' },
523
+ },
524
+ required: ['status', 'data'],
525
+ },
526
+ },
527
+ },
528
+ },
529
+ 400: gitErrorResponse(),
530
+ 500: gitErrorResponse(),
531
+ },
532
+ },
533
+ },
534
+ '/v1/git/stage': {
535
+ post: {
536
+ tags: ['git'],
537
+ operationId: 'stageFiles',
538
+ summary: 'Stage files',
539
+ requestBody: {
540
+ required: true,
541
+ content: {
542
+ 'application/json': {
543
+ schema: {
544
+ type: 'object',
545
+ properties: {
546
+ project: { type: 'string' },
547
+ files: {
548
+ type: 'array',
549
+ items: { type: 'string' },
550
+ },
551
+ },
552
+ required: ['files'],
553
+ },
554
+ },
555
+ },
556
+ },
557
+ responses: {
558
+ 200: {
559
+ description: 'OK',
560
+ content: {
561
+ 'application/json': {
562
+ schema: {
563
+ type: 'object',
564
+ properties: {
565
+ status: { type: 'string', enum: ['ok'] },
566
+ data: {
567
+ type: 'object',
568
+ properties: {
569
+ staged: {
570
+ type: 'array',
571
+ items: { type: 'string' },
572
+ },
573
+ failed: {
574
+ type: 'array',
575
+ items: { type: 'string' },
576
+ },
577
+ },
578
+ required: ['staged', 'failed'],
579
+ },
580
+ },
581
+ required: ['status', 'data'],
582
+ },
583
+ },
584
+ },
585
+ },
586
+ 500: gitErrorResponse(),
587
+ },
588
+ },
589
+ },
590
+ '/v1/git/unstage': {
591
+ post: {
592
+ tags: ['git'],
593
+ operationId: 'unstageFiles',
594
+ summary: 'Unstage files',
595
+ requestBody: {
596
+ required: true,
597
+ content: {
598
+ 'application/json': {
599
+ schema: {
600
+ type: 'object',
601
+ properties: {
602
+ project: { type: 'string' },
603
+ files: {
604
+ type: 'array',
605
+ items: { type: 'string' },
606
+ },
607
+ },
608
+ required: ['files'],
609
+ },
610
+ },
611
+ },
612
+ },
613
+ responses: {
614
+ 200: {
615
+ description: 'OK',
616
+ content: {
617
+ 'application/json': {
618
+ schema: {
619
+ type: 'object',
620
+ properties: {
621
+ status: { type: 'string', enum: ['ok'] },
622
+ data: {
623
+ type: 'object',
624
+ properties: {
625
+ unstaged: {
626
+ type: 'array',
627
+ items: { type: 'string' },
628
+ },
629
+ failed: {
630
+ type: 'array',
631
+ items: { type: 'string' },
632
+ },
633
+ },
634
+ required: ['unstaged', 'failed'],
635
+ },
636
+ },
637
+ required: ['status', 'data'],
638
+ },
639
+ },
640
+ },
641
+ },
642
+ 500: gitErrorResponse(),
643
+ },
644
+ },
645
+ },
646
+ '/v1/git/commit': {
647
+ post: {
648
+ tags: ['git'],
649
+ operationId: 'commitChanges',
650
+ summary: 'Commit staged changes',
651
+ requestBody: {
652
+ required: true,
653
+ content: {
654
+ 'application/json': {
655
+ schema: {
656
+ type: 'object',
657
+ properties: {
658
+ project: { type: 'string' },
659
+ message: { type: 'string', minLength: 1 },
660
+ },
661
+ required: ['message'],
662
+ },
663
+ },
664
+ },
665
+ },
666
+ responses: {
667
+ 200: {
668
+ description: 'OK',
669
+ content: {
670
+ 'application/json': {
671
+ schema: {
672
+ type: 'object',
673
+ properties: {
674
+ status: { type: 'string', enum: ['ok'] },
675
+ data: { $ref: '#/components/schemas/GitCommit' },
676
+ },
677
+ required: ['status', 'data'],
678
+ },
679
+ },
680
+ },
681
+ },
682
+ 400: gitErrorResponse(),
683
+ 500: gitErrorResponse(),
684
+ },
685
+ },
686
+ },
687
+ '/v1/git/generate-commit-message': {
688
+ post: {
689
+ tags: ['git'],
690
+ operationId: 'generateCommitMessage',
691
+ summary: 'Generate AI-powered commit message',
692
+ description:
693
+ 'Uses AI to generate a commit message based on staged changes',
694
+ requestBody: {
695
+ required: false,
696
+ content: {
697
+ 'application/json': {
698
+ schema: {
699
+ type: 'object',
700
+ properties: {
701
+ project: { type: 'string' },
702
+ },
703
+ },
704
+ },
705
+ },
706
+ },
707
+ responses: {
708
+ 200: {
709
+ description: 'OK',
710
+ content: {
711
+ 'application/json': {
712
+ schema: {
713
+ type: 'object',
714
+ properties: {
715
+ status: { type: 'string', enum: ['ok'] },
716
+ data: {
717
+ type: 'object',
718
+ properties: {
719
+ message: { type: 'string' },
720
+ },
721
+ required: ['message'],
722
+ },
723
+ },
724
+ required: ['status', 'data'],
725
+ },
726
+ },
727
+ },
728
+ },
729
+ 400: gitErrorResponse(),
730
+ 500: gitErrorResponse(),
731
+ },
732
+ },
733
+ },
240
734
  },
241
735
  components: {
242
736
  schemas: {
@@ -421,6 +915,134 @@ export function getOpenAPISpec() {
421
915
  },
422
916
  required: ['kind', 'path'],
423
917
  },
918
+ Config: {
919
+ type: 'object',
920
+ properties: {
921
+ agents: {
922
+ type: 'array',
923
+ items: { type: 'string' },
924
+ },
925
+ providers: {
926
+ type: 'array',
927
+ items: { $ref: '#/components/schemas/Provider' },
928
+ },
929
+ defaults: {
930
+ type: 'object',
931
+ properties: {
932
+ agent: { type: 'string' },
933
+ provider: { $ref: '#/components/schemas/Provider' },
934
+ model: { type: 'string' },
935
+ },
936
+ required: ['agent', 'provider', 'model'],
937
+ },
938
+ },
939
+ required: ['agents', 'providers', 'defaults'],
940
+ },
941
+ Model: {
942
+ type: 'object',
943
+ properties: {
944
+ id: { type: 'string' },
945
+ label: { type: 'string' },
946
+ toolCall: { type: 'boolean' },
947
+ reasoning: { type: 'boolean' },
948
+ },
949
+ required: ['id', 'label'],
950
+ },
951
+ GitStatus: {
952
+ type: 'object',
953
+ properties: {
954
+ branch: { type: 'string' },
955
+ ahead: { type: 'integer' },
956
+ behind: { type: 'integer' },
957
+ staged: {
958
+ type: 'array',
959
+ items: { $ref: '#/components/schemas/GitFile' },
960
+ },
961
+ unstaged: {
962
+ type: 'array',
963
+ items: { $ref: '#/components/schemas/GitFile' },
964
+ },
965
+ untracked: {
966
+ type: 'array',
967
+ items: { $ref: '#/components/schemas/GitFile' },
968
+ },
969
+ hasChanges: { type: 'boolean' },
970
+ },
971
+ required: [
972
+ 'branch',
973
+ 'ahead',
974
+ 'behind',
975
+ 'staged',
976
+ 'unstaged',
977
+ 'untracked',
978
+ 'hasChanges',
979
+ ],
980
+ },
981
+ GitFile: {
982
+ type: 'object',
983
+ properties: {
984
+ path: { type: 'string' },
985
+ status: {
986
+ type: 'string',
987
+ enum: ['modified', 'added', 'deleted', 'renamed', 'untracked'],
988
+ },
989
+ staged: { type: 'boolean' },
990
+ insertions: { type: 'integer' },
991
+ deletions: { type: 'integer' },
992
+ oldPath: { type: 'string' },
993
+ },
994
+ required: ['path', 'status', 'staged'],
995
+ },
996
+ GitDiff: {
997
+ type: 'object',
998
+ properties: {
999
+ file: { type: 'string' },
1000
+ diff: { type: 'string' },
1001
+ insertions: { type: 'integer' },
1002
+ deletions: { type: 'integer' },
1003
+ language: { type: 'string' },
1004
+ binary: { type: 'boolean' },
1005
+ },
1006
+ required: [
1007
+ 'file',
1008
+ 'diff',
1009
+ 'insertions',
1010
+ 'deletions',
1011
+ 'language',
1012
+ 'binary',
1013
+ ],
1014
+ },
1015
+ GitBranch: {
1016
+ type: 'object',
1017
+ properties: {
1018
+ current: { type: 'string' },
1019
+ upstream: { type: 'string' },
1020
+ ahead: { type: 'integer' },
1021
+ behind: { type: 'integer' },
1022
+ all: {
1023
+ type: 'array',
1024
+ items: { type: 'string' },
1025
+ },
1026
+ },
1027
+ required: ['current', 'upstream', 'ahead', 'behind', 'all'],
1028
+ },
1029
+ GitCommit: {
1030
+ type: 'object',
1031
+ properties: {
1032
+ hash: { type: 'string' },
1033
+ message: { type: 'string' },
1034
+ filesChanged: { type: 'integer' },
1035
+ insertions: { type: 'integer' },
1036
+ deletions: { type: 'integer' },
1037
+ },
1038
+ required: [
1039
+ 'hash',
1040
+ 'message',
1041
+ 'filesChanged',
1042
+ 'insertions',
1043
+ 'deletions',
1044
+ ],
1045
+ },
424
1046
  },
425
1047
  },
426
1048
  } as const;
@@ -472,3 +1094,22 @@ function errorResponse() {
472
1094
  },
473
1095
  } as const;
474
1096
  }
1097
+
1098
+ function gitErrorResponse() {
1099
+ return {
1100
+ description: 'Error',
1101
+ content: {
1102
+ 'application/json': {
1103
+ schema: {
1104
+ type: 'object',
1105
+ properties: {
1106
+ status: { type: 'string', enum: ['error'] },
1107
+ error: { type: 'string' },
1108
+ code: { type: 'string' },
1109
+ },
1110
+ required: ['status', 'error'],
1111
+ },
1112
+ },
1113
+ },
1114
+ } as const;
1115
+ }
@@ -10,6 +10,17 @@ interface FileRead {
10
10
  path: string;
11
11
  }
12
12
 
13
+ interface ToolPart {
14
+ type: string;
15
+ input?: {
16
+ path?: string;
17
+ filePattern?: string;
18
+ pattern?: string;
19
+ };
20
+ output?: unknown;
21
+ [key: string]: unknown;
22
+ }
23
+
13
24
  /**
14
25
  * Deduplicates file read results, keeping only the latest version of each file.
15
26
  *
@@ -38,7 +49,8 @@ export function deduplicateFileReads(messages: ModelMessage[]): ModelMessage[] {
38
49
  if (!['read', 'grep', 'glob'].includes(toolName)) return;
39
50
 
40
51
  // Extract file path from input
41
- const input = (part as any).input;
52
+ const toolPart = part as ToolPart;
53
+ const input = toolPart.input;
42
54
  if (!input) return;
43
55
 
44
56
  const path = input.path || input.filePattern || input.pattern;
@@ -49,8 +61,8 @@ export function deduplicateFileReads(messages: ModelMessage[]): ModelMessage[] {
49
61
  fileReads.set(path, []);
50
62
  }
51
63
  fileReads
52
- .get(path)!
53
- .push({ messageIndex: msgIdx, partIndex: partIdx, path });
64
+ .get(path)
65
+ ?.push({ messageIndex: msgIdx, partIndex: partIdx, path });
54
66
  });
55
67
  });
56
68
 
@@ -112,7 +124,8 @@ export function pruneToolResults(
112
124
  if (!toolType.startsWith('tool-')) return;
113
125
 
114
126
  // Check if this has output
115
- const hasOutput = (part as any).output !== undefined;
127
+ const toolPart = part as ToolPart;
128
+ const hasOutput = toolPart.output !== undefined;
116
129
  if (!hasOutput) return;
117
130
 
118
131
  toolResults.push({ messageIndex: msgIdx, partIndex: partIdx });
@@ -142,11 +155,12 @@ export function pruneToolResults(
142
155
  if (!part || typeof part !== 'object') return part;
143
156
  if (!('type' in part)) return part;
144
157
 
145
- const toolType = (part as any).type as string;
158
+ const toolPart = part as ToolPart;
159
+ const toolType = toolPart.type;
146
160
  if (!toolType.startsWith('tool-')) return part;
147
161
 
148
162
  const key = `${msgIdx}-${partIdx}`;
149
- const hasOutput = (part as any).output !== undefined;
163
+ const hasOutput = toolPart.output !== undefined;
150
164
 
151
165
  // If this tool result should be pruned, remove its output
152
166
  if (hasOutput && !toKeep.has(key)) {
@@ -0,0 +1,26 @@
1
+ import type { ModelMessage } from 'ai';
2
+
3
+ /**
4
+ * Truncates conversation history to keep only the most recent messages.
5
+ * This helps manage context window size and improves performance.
6
+ *
7
+ * Strategy:
8
+ * - Keep only the last N messages
9
+ * - Preserve message pairs (assistant + user responses) when possible
10
+ * - Always keep at least the system message if present
11
+ */
12
+ export function truncateHistory(
13
+ messages: ModelMessage[],
14
+ maxMessages: number,
15
+ ): ModelMessage[] {
16
+ if (messages.length <= maxMessages) {
17
+ return messages;
18
+ }
19
+
20
+ // Calculate how many messages to keep
21
+ const keepCount = Math.min(maxMessages, messages.length);
22
+ const startIndex = messages.length - keepCount;
23
+
24
+ // Return the most recent messages
25
+ return messages.slice(startIndex);
26
+ }
@@ -27,7 +27,6 @@ import {
27
27
  type RunnerToolContext,
28
28
  } from './tool-context-setup.ts';
29
29
  import {
30
- updateSessionTokens,
31
30
  updateSessionTokensIncremental,
32
31
  updateMessageTokensIncremental,
33
32
  completeAssistantMessage,
@@ -269,6 +268,7 @@ async function runAssistant(opts: RunOpts) {
269
268
  );
270
269
 
271
270
  try {
271
+ // @ts-expect-error this is fine 🔥
272
272
  const result = streamText({
273
273
  model,
274
274
  tools: toolset,