11import type { CancellationToken , Disposable , MessageItem , ProgressOptions , QuickInputButton } from 'vscode' ;
22import { 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' ;
411import type { Container } from '../container' ;
12+ import { CancellationError } from '../errors' ;
513import type { GitCommit } from '../git/models/commit' ;
614import { assertsCommitHasFullDetails , isCommit } from '../git/models/commit' ;
715import { uncommitted , uncommittedStaged } from '../git/models/constants' ;
@@ -13,6 +21,7 @@ import { configuration } from '../system/configuration';
1321import { getSettledValue } from '../system/promise' ;
1422import type { Storage } from '../system/storage' ;
1523import { supportedInVSCodeVersion } from '../system/utils' ;
24+ import type { TelemetryService } from '../telemetry/telemetry' ;
1625import { AnthropicProvider } from './anthropicProvider' ;
1726import { GeminiProvider } from './geminiProvider' ;
1827import { OpenAIProvider } from './openaiProvider' ;
@@ -195,18 +204,22 @@ export class AIProviderService implements Disposable {
195204
196205 async generateCommitMessage (
197206 changes : string [ ] ,
207+ sourceContext : { source : Sources } ,
198208 options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
199209 ) : Promise < string | undefined > ;
200210 async generateCommitMessage (
201211 repoPath : Uri ,
212+ sourceContext : { source : Sources } ,
202213 options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
203214 ) : Promise < string | undefined > ;
204215 async generateCommitMessage (
205216 repository : Repository ,
217+ sourceContext : { source : Sources } ,
206218 options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
207219 ) : Promise < string | undefined > ;
208220 async generateCommitMessage (
209221 changesOrRepoOrPath : string [ ] | Repository | Uri ,
222+ sourceContext : { source : Sources } ,
210223 options ?: { cancellation ?: CancellationToken ; context ?: string ; progress ?: ProgressOptions } ,
211224 ) : Promise < string | undefined > {
212225 const changes : string | undefined = await this . getChanges ( changesOrRepoOrPath ) ;
@@ -217,26 +230,68 @@ export class AIProviderService implements Disposable {
217230
218231 const provider = this . _provider ! ;
219232
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+
220239 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 ,
230245 ) ;
246+
247+ return undefined ;
231248 }
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 , {
233261 cancellation : options ?. cancellation ,
234262 context : options ?. context ,
235263 } ) ;
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+ }
236290 }
237291
238292 async generateDraftMessage (
239293 changesOrRepoOrPath : string [ ] | Repository | Uri ,
294+ sourceContext : { source : Sources ; type : AIGenerateDraftEvent [ 'draftType' ] } ,
240295 options ?: {
241296 cancellation ?: CancellationToken ;
242297 context ?: string ;
@@ -252,24 +307,65 @@ export class AIProviderService implements Disposable {
252307
253308 const provider = this . _provider ! ;
254309
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+
255317 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 ,
266323 ) ;
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 ;
267336 }
268- return provider . generateDraftMessage ( model , changes , {
337+
338+ const promise = provider . generateDraftMessage ( model , changes , {
269339 cancellation : options ?. cancellation ,
270340 context : options ?. context ,
271341 codeSuggestion : options ?. codeSuggestion ,
272342 } ) ;
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+ }
273369 }
274370
275371 private async getChanges (
@@ -299,60 +395,85 @@ export class AIProviderService implements Disposable {
299395 }
300396
301397 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' ] } ,
313400 options ?: { cancellation ?: CancellationToken ; progress ?: ProgressOptions } ,
314401 ) : 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 ) ;
331403 if ( ! diff ?. contents ) throw new Error ( 'No changes found to explain.' ) ;
332404
333405 const model = await this . getModel ( ) ;
334406 if ( model == null ) return undefined ;
335407
336408 const provider = this . _provider ! ;
337409
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+
338417 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' ) ;
340432
341433 if ( ! commit . hasFullDetails ( ) ) {
342434 await commit . ensureFullDetails ( ) ;
343435 assertsCommitHasFullDetails ( commit ) ;
344436 }
345437
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 ,
351443 ) ;
444+
445+ return undefined ;
352446 }
353- return provider . explainChanges ( model , commit . message , diff . contents , {
447+
448+ const promise = provider . explainChanges ( model , commit . message , diff . contents , {
354449 cancellation : options ?. cancellation ,
355450 } ) ;
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+ }
356477 }
357478
358479 async reset ( all ?: boolean ) {
0 commit comments