1
1
import type { CancellationToken , Disposable , MessageItem , ProgressOptions , QuickInputButton } from 'vscode' ;
2
2
import { env , ThemeIcon , Uri , window } from 'vscode' ;
3
- import type { AIModels , AIProviders , SupportedAIModels } from '../constants' ;
3
+ import type {
4
+ AIGenerateDraftEvent ,
5
+ AIModels ,
6
+ AIProviders ,
7
+ Sources ,
8
+ SupportedAIModels ,
9
+ TelemetryEvents ,
10
+ } from '../constants' ;
4
11
import type { Container } from '../container' ;
12
+ import { CancellationError } from '../errors' ;
5
13
import type { GitCommit } from '../git/models/commit' ;
6
14
import { assertsCommitHasFullDetails , isCommit } from '../git/models/commit' ;
7
15
import { uncommitted , uncommittedStaged } from '../git/models/constants' ;
@@ -13,6 +21,7 @@ import { configuration } from '../system/configuration';
13
21
import { getSettledValue } from '../system/promise' ;
14
22
import type { Storage } from '../system/storage' ;
15
23
import { supportedInVSCodeVersion } from '../system/utils' ;
24
+ import type { TelemetryService } from '../telemetry/telemetry' ;
16
25
import { AnthropicProvider } from './anthropicProvider' ;
17
26
import { GeminiProvider } from './geminiProvider' ;
18
27
import { OpenAIProvider } from './openaiProvider' ;
@@ -195,18 +204,22 @@ export class AIProviderService implements Disposable {
195
204
196
205
async generateCommitMessage (
197
206
changes : string [ ] ,
207
+ sourceContext : { source : Sources } ,
198
208
options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
199
209
) : Promise < string | undefined > ;
200
210
async generateCommitMessage (
201
211
repoPath : Uri ,
212
+ sourceContext : { source : Sources } ,
202
213
options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
203
214
) : Promise < string | undefined > ;
204
215
async generateCommitMessage (
205
216
repository : Repository ,
217
+ sourceContext : { source : Sources } ,
206
218
options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
207
219
) : Promise < string | undefined > ;
208
220
async generateCommitMessage (
209
221
changesOrRepoOrPath : string [ ] | Repository | Uri ,
222
+ sourceContext : { source : Sources } ,
210
223
options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
211
224
) : Promise < string | undefined > {
212
225
const changes : string | undefined = await this . getChanges ( changesOrRepoOrPath ) ;
@@ -217,26 +230,68 @@ export class AIProviderService implements Disposable {
217
230
218
231
const provider = this . _provider ! ;
219
232
233
+ const payload : TelemetryEvents [ 'ai/generate' ] = {
234
+ type : 'commitMessage' ,
235
+ model : { id : model . id , provider : { id : model . provider . id , name : model . provider . name } } ,
236
+ } ;
237
+ const source : Parameters < TelemetryService [ 'sendEvent' ] > [ 2 ] = { source : sourceContext . source } ;
238
+
220
239
const confirmed = await confirmAIProviderToS ( model , this . container . storage ) ;
221
- if ( ! confirmed ) return undefined ;
222
- if ( options ?. cancellation ?. isCancellationRequested ) return undefined ;
223
-
224
- if ( options ?. progress != null ) {
225
- return window . withProgress ( options . progress , async ( ) =>
226
- provider . generateCommitMessage ( model , changes , {
227
- cancellation : options ?. cancellation ,
228
- context : options ?. context ,
229
- } ) ,
240
+ if ( ! confirmed ) {
241
+ this . container . telemetry . sendEvent (
242
+ 'ai/generate' ,
243
+ { ...payload , failed : { reason : 'user-declined' } } ,
244
+ source ,
230
245
) ;
246
+
247
+ return undefined ;
231
248
}
232
- return provider . generateCommitMessage ( model , changes , {
249
+
250
+ if ( options ?. cancellation ?. isCancellationRequested ) {
251
+ this . container . telemetry . sendEvent (
252
+ 'ai/generate' ,
253
+ { ...payload , failed : { reason : 'user-cancelled' } } ,
254
+ source ,
255
+ ) ;
256
+
257
+ return undefined ;
258
+ }
259
+
260
+ const promise = provider . generateCommitMessage ( model , changes , {
233
261
cancellation : options ?. cancellation ,
234
262
context : options ?. context ,
235
263
} ) ;
264
+
265
+ const start = Date . now ( ) ;
266
+ try {
267
+ const result = await ( options ?. progress != null
268
+ ? window . withProgress ( options . progress , ( ) => promise )
269
+ : promise ) ;
270
+
271
+ this . container . telemetry . sendEvent ( 'ai/generate' , { ...payload , duration : Date . now ( ) - start } , source ) ;
272
+
273
+ return result ;
274
+ } catch ( ex ) {
275
+ this . container . telemetry . sendEvent (
276
+ 'ai/generate' ,
277
+ {
278
+ ...payload ,
279
+ duration : Date . now ( ) - start ,
280
+ failed :
281
+ ex instanceof CancellationError
282
+ ? { reason : 'user-cancelled' }
283
+ : { reason : 'error' , error : String ( ex ) } ,
284
+ } ,
285
+ source ,
286
+ ) ;
287
+
288
+ throw ex ;
289
+ }
236
290
}
237
291
238
292
async generateDraftMessage (
239
293
changesOrRepoOrPath : string [ ] | Repository | Uri ,
294
+ sourceContext : { source : Sources ; type : AIGenerateDraftEvent [ 'draftType' ] } ,
240
295
options ?: {
241
296
cancellation ?: CancellationToken ;
242
297
context ?: string ;
@@ -252,24 +307,65 @@ export class AIProviderService implements Disposable {
252
307
253
308
const provider = this . _provider ! ;
254
309
310
+ const payload : TelemetryEvents [ 'ai/generate' ] = {
311
+ type : 'draftMessage' ,
312
+ draftType : sourceContext . type ,
313
+ model : { id : model . id , provider : { id : model . provider . id , name : model . provider . name } } ,
314
+ } ;
315
+ const source : Parameters < TelemetryService [ 'sendEvent' ] > [ 2 ] = { source : sourceContext . source } ;
316
+
255
317
const confirmed = await confirmAIProviderToS ( model , this . container . storage ) ;
256
- if ( ! confirmed ) return undefined ;
257
- if ( options ?. cancellation ?. isCancellationRequested ) return undefined ;
258
-
259
- if ( options ?. progress != null ) {
260
- return window . withProgress ( options . progress , async ( ) =>
261
- provider . generateDraftMessage ( model , changes , {
262
- cancellation : options ?. cancellation ,
263
- context : options ?. context ,
264
- codeSuggestion : options ?. codeSuggestion ,
265
- } ) ,
318
+ if ( ! confirmed ) {
319
+ this . container . telemetry . sendEvent (
320
+ 'ai/generate' ,
321
+ { ...payload , failed : { reason : 'user-declined' } } ,
322
+ source ,
266
323
) ;
324
+
325
+ return undefined ;
326
+ }
327
+
328
+ if ( options ?. cancellation ?. isCancellationRequested ) {
329
+ this . container . telemetry . sendEvent (
330
+ 'ai/generate' ,
331
+ { ...payload , failed : { reason : 'user-cancelled' } } ,
332
+ source ,
333
+ ) ;
334
+
335
+ return undefined ;
267
336
}
268
- return provider . generateDraftMessage ( model , changes , {
337
+
338
+ const promise = provider . generateDraftMessage ( model , changes , {
269
339
cancellation : options ?. cancellation ,
270
340
context : options ?. context ,
271
341
codeSuggestion : options ?. codeSuggestion ,
272
342
} ) ;
343
+
344
+ const start = Date . now ( ) ;
345
+ try {
346
+ const result = await ( options ?. progress != null
347
+ ? window . withProgress ( options . progress , ( ) => promise )
348
+ : promise ) ;
349
+
350
+ this . container . telemetry . sendEvent ( 'ai/generate' , { ...payload , duration : Date . now ( ) - start } , source ) ;
351
+
352
+ return result ;
353
+ } catch ( ex ) {
354
+ this . container . telemetry . sendEvent (
355
+ 'ai/generate' ,
356
+ {
357
+ ...payload ,
358
+ duration : Date . now ( ) - start ,
359
+ failed :
360
+ ex instanceof CancellationError
361
+ ? { reason : 'user-cancelled' }
362
+ : { reason : 'error' , error : String ( ex ) } ,
363
+ } ,
364
+ source ,
365
+ ) ;
366
+
367
+ throw ex ;
368
+ }
273
369
}
274
370
275
371
private async getChanges (
@@ -299,60 +395,85 @@ export class AIProviderService implements Disposable {
299
395
}
300
396
301
397
async explainCommit (
302
- repoPath : string | Uri ,
303
- sha : string ,
304
- options ?: { cancellation ?: CancellationToken ; progress ?: ProgressOptions } ,
305
- ) : Promise < string | undefined > ;
306
- async explainCommit (
307
- commit : GitRevisionReference | GitCommit ,
308
- options ?: { cancellation ?: CancellationToken ; progress ?: ProgressOptions } ,
309
- ) : Promise < string | undefined > ;
310
- async explainCommit (
311
- commitOrRepoPath : string | Uri | GitRevisionReference | GitCommit ,
312
- shaOrOptions ?: string | { progress ?: ProgressOptions } ,
398
+ commitOrRevision : GitRevisionReference | GitCommit ,
399
+ sourceContext : { source : Sources ; type : TelemetryEvents [ 'ai/explain' ] [ 'changeType' ] } ,
313
400
options ?: { cancellation ?: CancellationToken ; progress ?: ProgressOptions } ,
314
401
) : Promise < string | undefined > {
315
- let commit : GitCommit | undefined ;
316
- if ( typeof commitOrRepoPath === 'string' || commitOrRepoPath instanceof Uri ) {
317
- if ( typeof shaOrOptions !== 'string' || ! shaOrOptions ) throw new Error ( 'Invalid arguments provided' ) ;
318
-
319
- commit = await this . container . git . getCommit ( commitOrRepoPath , shaOrOptions ) ;
320
- } else {
321
- if ( typeof shaOrOptions === 'string' ) throw new Error ( 'Invalid arguments provided' ) ;
322
-
323
- commit = isCommit ( commitOrRepoPath )
324
- ? commitOrRepoPath
325
- : await this . container . git . getCommit ( commitOrRepoPath . repoPath , commitOrRepoPath . ref ) ;
326
- options = shaOrOptions ;
327
- }
328
- if ( commit == null ) throw new Error ( 'Unable to find commit' ) ;
329
-
330
- const diff = await this . container . git . getDiff ( commit . repoPath , commit . sha ) ;
402
+ const diff = await this . container . git . getDiff ( commitOrRevision . repoPath , commitOrRevision . ref ) ;
331
403
if ( ! diff ?. contents ) throw new Error ( 'No changes found to explain.' ) ;
332
404
333
405
const model = await this . getModel ( ) ;
334
406
if ( model == null ) return undefined ;
335
407
336
408
const provider = this . _provider ! ;
337
409
410
+ const payload : TelemetryEvents [ 'ai/explain' ] = {
411
+ type : 'change' ,
412
+ changeType : sourceContext . type ,
413
+ model : { id : model . id , provider : { id : model . provider . id , name : model . provider . name } } ,
414
+ } ;
415
+ const source : Parameters < TelemetryService [ 'sendEvent' ] > [ 2 ] = { source : sourceContext . source } ;
416
+
338
417
const confirmed = await confirmAIProviderToS ( model , this . container . storage ) ;
339
- if ( ! confirmed ) return undefined ;
418
+ if ( ! confirmed ) {
419
+ this . container . telemetry . sendEvent (
420
+ 'ai/explain' ,
421
+ { ...payload , failed : { reason : 'user-declined' } } ,
422
+ source ,
423
+ ) ;
424
+
425
+ return undefined ;
426
+ }
427
+
428
+ const commit = isCommit ( commitOrRevision )
429
+ ? commitOrRevision
430
+ : await this . container . git . getCommit ( commitOrRevision . repoPath , commitOrRevision . ref ) ;
431
+ if ( commit == null ) throw new Error ( 'Unable to find commit' ) ;
340
432
341
433
if ( ! commit . hasFullDetails ( ) ) {
342
434
await commit . ensureFullDetails ( ) ;
343
435
assertsCommitHasFullDetails ( commit ) ;
344
436
}
345
437
346
- if ( options ?. progress != null ) {
347
- return window . withProgress ( options . progress , async ( ) =>
348
- provider . explainChanges ( model , commit . message , diff . contents , {
349
- cancellation : options ?. cancellation ,
350
- } ) ,
438
+ if ( options ?. cancellation ?. isCancellationRequested ) {
439
+ this . container . telemetry . sendEvent (
440
+ 'ai/explain' ,
441
+ { ... payload , failed : { reason : 'user-cancelled' } } ,
442
+ source ,
351
443
) ;
444
+
445
+ return undefined ;
352
446
}
353
- return provider . explainChanges ( model , commit . message , diff . contents , {
447
+
448
+ const promise = provider . explainChanges ( model , commit . message , diff . contents , {
354
449
cancellation : options ?. cancellation ,
355
450
} ) ;
451
+
452
+ const start = Date . now ( ) ;
453
+ try {
454
+ const result = await ( options ?. progress != null
455
+ ? window . withProgress ( options . progress , ( ) => promise )
456
+ : promise ) ;
457
+
458
+ this . container . telemetry . sendEvent ( 'ai/explain' , { ...payload , duration : Date . now ( ) - start } , source ) ;
459
+
460
+ return result ;
461
+ } catch ( ex ) {
462
+ this . container . telemetry . sendEvent (
463
+ 'ai/explain' ,
464
+ {
465
+ ...payload ,
466
+ duration : Date . now ( ) - start ,
467
+ failed :
468
+ ex instanceof CancellationError
469
+ ? { reason : 'user-cancelled' }
470
+ : { reason : 'error' , error : String ( ex ) } ,
471
+ } ,
472
+ source ,
473
+ ) ;
474
+
475
+ throw ex ;
476
+ }
356
477
}
357
478
358
479
async reset ( all ?: boolean ) {
0 commit comments