@@ -15,6 +15,7 @@ import { configuration } from '../system/-webview/configuration';
15
15
import type { Storage } from '../system/-webview/storage' ;
16
16
import { supportedInVSCodeVersion } from '../system/-webview/vscode' ;
17
17
import { formatNumeric } from '../system/date' ;
18
+ import type { Lazy } from '../system/lazy' ;
18
19
import type { Deferred } from '../system/promise' ;
19
20
import { getSettledValue } from '../system/promise' ;
20
21
import { getPossessiveForm } from '../system/string' ;
@@ -33,6 +34,11 @@ export interface AIResult {
33
34
body : string ;
34
35
}
35
36
37
+ export interface AIGenerateChangelogChange {
38
+ message : string ;
39
+ issues : { id : string ; url : string ; title : string | undefined } [ ] ;
40
+ }
41
+
36
42
export interface AIModel < Provider extends AIProviders = AIProviders , Model extends string = string > {
37
43
readonly id : Model ;
38
44
readonly name : string ;
@@ -95,6 +101,12 @@ export interface AIProvider<Provider extends AIProviders = AIProviders> extends
95
101
reporting : TelemetryEvents [ 'ai/generate' ] ,
96
102
options ?: { cancellation ?: CancellationToken ; context ?: string ; codeSuggestion ?: boolean } ,
97
103
) : Promise < string | undefined > ;
104
+ generateChangelog (
105
+ model : AIModel < Provider > ,
106
+ changes : AIGenerateChangelogChange [ ] ,
107
+ reporting : TelemetryEvents [ 'ai/generate' ] ,
108
+ options ?: { cancellation ?: CancellationToken } ,
109
+ ) : Promise < string | undefined > ;
98
110
}
99
111
100
112
export class AIProviderService implements Disposable {
@@ -231,7 +243,7 @@ export class AIProviderService implements Disposable {
231
243
const changes : string | undefined = await this . getChanges ( changesOrRepo ) ;
232
244
if ( changes == null ) return undefined ;
233
245
234
- const { confirmed, model } = await getModelAndConfirmAIProviderToS ( this , this . container . storage ) ;
246
+ const { confirmed, model } = await getModelAndConfirmAIProviderToS ( 'diff' , this , this . container . storage ) ;
235
247
if ( model == null ) {
236
248
options ?. generating ?. cancel ( ) ;
237
249
return undefined ;
@@ -315,7 +327,7 @@ export class AIProviderService implements Disposable {
315
327
const changes : string | undefined = await this . getChanges ( changesOrRepo ) ;
316
328
if ( changes == null ) return undefined ;
317
329
318
- const { confirmed, model } = await getModelAndConfirmAIProviderToS ( this , this . container . storage ) ;
330
+ const { confirmed, model } = await getModelAndConfirmAIProviderToS ( 'diff' , this , this . container . storage ) ;
319
331
if ( model == null ) {
320
332
options ?. generating ?. cancel ( ) ;
321
333
return undefined ;
@@ -400,7 +412,7 @@ export class AIProviderService implements Disposable {
400
412
return undefined ;
401
413
}
402
414
403
- const { confirmed, model } = await getModelAndConfirmAIProviderToS ( this , this . container . storage ) ;
415
+ const { confirmed, model } = await getModelAndConfirmAIProviderToS ( 'diff' , this , this . container . storage ) ;
404
416
if ( model == null ) {
405
417
options ?. generating ?. cancel ( ) ;
406
418
return undefined ;
@@ -470,6 +482,73 @@ export class AIProviderService implements Disposable {
470
482
}
471
483
}
472
484
485
+ async generateChangelog (
486
+ changes : Lazy < Promise < AIGenerateChangelogChange [ ] > > ,
487
+ sourceContext : { source : Sources } ,
488
+ options ?: { cancellation ?: CancellationToken ; progress ?: ProgressOptions } ,
489
+ ) : Promise < string | undefined > {
490
+ const { confirmed, model } = await getModelAndConfirmAIProviderToS ( 'data' , this , this . container . storage ) ;
491
+ if ( model == null ) return undefined ;
492
+
493
+ const payload : TelemetryEvents [ 'ai/generate' ] = {
494
+ type : 'changelog' ,
495
+ 'model.id' : model . id ,
496
+ 'model.provider.id' : model . provider . id ,
497
+ 'model.provider.name' : model . provider . name ,
498
+ 'retry.count' : 0 ,
499
+ } ;
500
+ const source : Parameters < TelemetryService [ 'sendEvent' ] > [ 2 ] = { source : sourceContext . source } ;
501
+
502
+ if ( ! confirmed ) {
503
+ this . container . telemetry . sendEvent ( 'ai/generate' , { ...payload , 'failed.reason' : 'user-declined' } , source ) ;
504
+ return undefined ;
505
+ }
506
+
507
+ if ( options ?. cancellation ?. isCancellationRequested ) {
508
+ this . container . telemetry . sendEvent (
509
+ 'ai/generate' ,
510
+ { ...payload , 'failed.reason' : 'user-cancelled' } ,
511
+ source ,
512
+ ) ;
513
+ return undefined ;
514
+ }
515
+
516
+ const promise = changes . value . then ( changes =>
517
+ this . _provider ! . generateChangelog ( model , changes , payload , {
518
+ cancellation : options ?. cancellation ,
519
+ } ) ,
520
+ ) ;
521
+
522
+ const start = Date . now ( ) ;
523
+ try {
524
+ const result = await ( options ?. progress != null
525
+ ? window . withProgress (
526
+ { ...options . progress , title : `Generating changelog with ${ model . name } ...` } ,
527
+ ( ) => promise ,
528
+ )
529
+ : promise ) ;
530
+
531
+ payload [ 'output.length' ] = result ?. length ;
532
+ this . container . telemetry . sendEvent ( 'ai/generate' , { ...payload , duration : Date . now ( ) - start } , source ) ;
533
+
534
+ return result ;
535
+ } catch ( ex ) {
536
+ this . container . telemetry . sendEvent (
537
+ 'ai/generate' ,
538
+ {
539
+ ...payload ,
540
+ duration : Date . now ( ) - start ,
541
+ ...( ex instanceof CancellationError
542
+ ? { 'failed.reason' : 'user-cancelled' }
543
+ : { 'failed.reason' : 'error' , 'failed.error' : String ( ex ) } ) ,
544
+ } ,
545
+ source ,
546
+ ) ;
547
+
548
+ throw ex ;
549
+ }
550
+ }
551
+
473
552
private async getChanges (
474
553
changesOrRepo : string | string [ ] | Repository ,
475
554
options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
@@ -501,7 +580,7 @@ export class AIProviderService implements Disposable {
501
580
const diff = await this . container . git . getDiff ( commitOrRevision . repoPath , commitOrRevision . ref ) ;
502
581
if ( ! diff ?. contents ) throw new Error ( 'No changes found to explain.' ) ;
503
582
504
- const { confirmed, model } = await getModelAndConfirmAIProviderToS ( this , this . container . storage ) ;
583
+ const { confirmed, model } = await getModelAndConfirmAIProviderToS ( 'diff' , this , this . container . storage ) ;
505
584
if ( model == null ) return undefined ;
506
585
507
586
const payload : TelemetryEvents [ 'ai/explain' ] = {
@@ -632,6 +711,7 @@ export class AIProviderService implements Disposable {
632
711
}
633
712
634
713
async function getModelAndConfirmAIProviderToS (
714
+ confirmationType : 'data' | 'diff' ,
635
715
service : AIProviderService ,
636
716
storage : Storage ,
637
717
) : Promise < { confirmed : boolean ; model : AIModel | undefined } > {
@@ -651,7 +731,11 @@ async function getModelAndConfirmAIProviderToS(
651
731
const decline : MessageItem = { title : 'Cancel' , isCloseAffordance : true } ;
652
732
653
733
const result = await window . showInformationMessage (
654
- `GitLens AI features require sending a diff of the code changes to ${ model . provider . name } for analysis. This may contain sensitive information.\n\nDo you want to continue?` ,
734
+ `GitLens AI features require sending ${
735
+ confirmationType === 'data' ? 'data' : 'a diff of the code changes'
736
+ } to ${
737
+ model . provider . name
738
+ } for analysis. This may contain sensitive information.\n\nDo you want to continue?`,
655
739
{ modal : true } ,
656
740
accept ,
657
741
switchModel ,
@@ -796,6 +880,14 @@ export function showDiffTruncationWarning(maxCodeCharacters: number, model: AIMo
796
880
) ;
797
881
}
798
882
883
+ export function showPromptTruncationWarning ( maxCodeCharacters : number , model : AIModel ) : void {
884
+ void window . showWarningMessage (
885
+ `The prompt had to be truncated to ${ formatNumeric (
886
+ maxCodeCharacters ,
887
+ ) } characters to fit within the ${ getPossessiveForm ( model . provider . name ) } limits.`,
888
+ ) ;
889
+ }
890
+
799
891
export function getValidatedTemperature ( modelTemperature ?: number | null ) : number | undefined {
800
892
if ( modelTemperature === null ) return undefined ;
801
893
if ( modelTemperature != null ) return modelTemperature ;
0 commit comments