Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/conversation/vscode-node/userActions.ts
13399 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
7
import * as vscode from 'vscode';
8
import { editsAgentName, getChatParticipantIdFromName } from '../../../platform/chat/common/chatAgents';
9
import { EditSurvivalResult } from '../../../platform/editSurvivalTracking/common/editSurvivalReporter';
10
import { IGitService } from '../../../platform/git/common/gitService';
11
import { ILanguageDiagnosticsService } from '../../../platform/languages/common/languageDiagnosticsService';
12
import { IMultiFileEditInternalTelemetryService } from '../../../platform/multiFileEdit/common/multiFileEditQualityTelemetry';
13
import { INotebookService } from '../../../platform/notebook/common/notebookService';
14
import type { EditOutcome } from '../../../platform/otel/common/genAiAttributes';
15
import { emitEditFeedbackEvent, emitEditHunkActionEvent, emitEditSurvivalEvent, emitInlineDoneEvent, emitUserFeedbackEvent } from '../../../platform/otel/common/genAiEvents';
16
import { GenAiMetrics } from '../../../platform/otel/common/genAiMetrics';
17
import { IOTelService } from '../../../platform/otel/common/otelService';
18
import { resolveWorkspaceOTelMetadata } from '../../../platform/otel/common/workspaceOTelMetadata';
19
import { ISurveyService } from '../../../platform/survey/common/surveyService';
20
import { ITelemetryService, TelemetryEventMeasurements, TelemetryEventProperties } from '../../../platform/telemetry/common/telemetry';
21
import { isNotebookCellOrNotebookChatInput } from '../../../util/common/notebooks';
22
import { createServiceIdentifier } from '../../../util/common/services';
23
import { Schemas } from '../../../util/vs/base/common/network';
24
import { Intent } from '../../common/constants';
25
import { IConversationStore } from '../../conversationStore/node/conversationStore';
26
import { findDiagnosticsTelemetry } from '../../inlineChat/node/diagnosticsTelemetry';
27
import { CopilotInteractiveEditorResponse, InteractionOutcome } from '../../inlineChat/node/promptCraftingTypes';
28
import { participantIdToModeName } from '../../intents/common/intents';
29
import { EditCodeStepTurnMetaData } from '../../intents/node/editCodeStep';
30
import { Conversation, ICopilotChatResultIn } from '../../prompt/common/conversation';
31
import { IFeedbackReporter } from '../../prompt/node/feedbackReporter';
32
import { sendUserActionTelemetry } from '../../prompt/node/telemetry';
33
import { resolveModelIdForTelemetry } from './resolveModelId';
34
35
export const IUserFeedbackService = createServiceIdentifier<IUserFeedbackService>('IUserFeedbackService');
36
export interface IUserFeedbackService {
37
_serviceBrand: undefined;
38
39
handleUserAction(e: vscode.ChatUserActionEvent, participantId: string): void;
40
handleFeedback(e: vscode.ChatResultFeedback, participantId: string): void;
41
}
42
43
export class UserFeedbackService implements IUserFeedbackService {
44
_serviceBrand: undefined;
45
46
constructor(
47
@ITelemetryService private readonly telemetryService: ITelemetryService,
48
@IConversationStore private readonly conversationStore: IConversationStore,
49
@IFeedbackReporter private readonly feedbackReporter: IFeedbackReporter,
50
@ISurveyService private readonly surveyService: ISurveyService,
51
@ILanguageDiagnosticsService private readonly languageDiagnosticsService: ILanguageDiagnosticsService,
52
@IMultiFileEditInternalTelemetryService private readonly multiFileEditTelemetryService: IMultiFileEditInternalTelemetryService,
53
@INotebookService private readonly notebookService: INotebookService,
54
@IOTelService private readonly otelService: IOTelService,
55
@IGitService private readonly gitService: IGitService
56
) { }
57
58
handleUserAction(e: vscode.ChatUserActionEvent, agentId: string): void {
59
const document = vscode.window.activeTextEditor?.document;
60
const selection = vscode.window.activeTextEditor?.selection;
61
62
const result = e.result as ICopilotChatResultIn;
63
const conversation = result.metadata?.responseId && this.conversationStore.getConversation(result.metadata.responseId);
64
65
if (typeof conversation === 'object' && conversation.getLatestTurn().getMetadata(CopilotInteractiveEditorResponse)) {
66
this._handleChatUserAction(result.metadata?.sessionId, conversation, e);
67
return;
68
}
69
70
// Don't use e.action.responseId, it will go away
71
switch (e.action.kind) {
72
case 'copy':
73
/* __GDPR__
74
"panel.action.copy" : {
75
"owner": "digitarald",
76
"comment": "Counts copied code blocks from a chat panel response",
77
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },
78
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for this message request." },
79
"codeBlockIndex": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Index of the code block in the response." },
80
"copyType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "If the copy was done via the context menu or the toolbar." },
81
"characterCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Number of characters copied." },
82
"lineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Number of lines copied." },
83
"participant": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "comment": "The name of the chat participant for this message." },
84
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "comment": "The command used for the chat participant." }
85
}
86
*/
87
this.telemetryService.sendMSFTTelemetryEvent('panel.action.copy', {
88
languageId: document?.languageId,
89
requestId: result.metadata?.responseId,
90
participant: agentId,
91
command: result.metadata?.command,
92
}, {
93
codeBlockIndex: e.action.codeBlockIndex,
94
copyType: e.action.copyKind,
95
characterCount: e.action.copiedCharacters,
96
lineCount: e.action.copiedText.split('\n').length,
97
});
98
GenAiMetrics.incrementUserActionCount(this.otelService, 'copy');
99
break;
100
case 'insert':
101
/* __GDPR__
102
"panel.action.insert" : {
103
"owner": "digitarald",
104
"comment": "Counts inserts on a chat panel response",
105
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },
106
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for this message request." },
107
"codeBlockIndex": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Index of the code block in the response." },
108
"characterCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Number of characters copied." },
109
"participant": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "comment": "The name of the chat participant for this message." },
110
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "comment": "The command used for the chat participant." },
111
"newFile": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "If the insert was done in a new file." }
112
}
113
*/
114
this.telemetryService.sendMSFTTelemetryEvent('panel.action.insert', {
115
languageId: document?.languageId,
116
requestId: result.metadata?.responseId,
117
participant: agentId,
118
command: result.metadata?.command,
119
}, {
120
codeBlockIndex: e.action.codeBlockIndex,
121
characterCount: e.action.totalCharacters,
122
newFile: e.action.newFile ? 1 : 0
123
});
124
GenAiMetrics.incrementUserActionCount(this.otelService, 'insert');
125
break;
126
case 'followUp':
127
/* __GDPR__
128
"panel.action.followup" : {
129
"owner": "digitarald",
130
"comment": "Counts generic actions on a chat panel response",
131
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },
132
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for the message request that is being followed-up." },
133
"participant": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The name of the chat participant for this message." },
134
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command used for the chat participant." }
135
}
136
*/
137
this.telemetryService.sendMSFTTelemetryEvent('panel.action.followup', {
138
languageId: document?.languageId,
139
requestId: result.metadata?.responseId,
140
participant: agentId,
141
command: result.metadata?.command,
142
});
143
GenAiMetrics.incrementUserActionCount(this.otelService, 'followup');
144
break;
145
case 'bug':
146
if (conversation) {
147
this.feedbackReporter.reportChat(conversation.getLatestTurn());
148
} else {
149
vscode.window.showInformationMessage('Conversation not found, is it restored? Please try again.');
150
}
151
break;
152
case 'chatEditingSessionAction':
153
if (conversation instanceof Conversation) {
154
const editCodeStep = conversation.getLatestTurn().getMetadata(EditCodeStepTurnMetaData)?.value;
155
if (editCodeStep && (e.action.outcome === vscode.ChatEditingSessionActionOutcome.Accepted || e.action.outcome === vscode.ChatEditingSessionActionOutcome.Rejected)
156
) {
157
editCodeStep.setWorkingSetEntryState(e.action.uri, {
158
accepted: e.action.outcome === vscode.ChatEditingSessionActionOutcome.Accepted,
159
hasRemainingEdits: e.action.hasRemainingEdits
160
});
161
}
162
163
/* __GDPR__
164
"panel.edit.feedback" : {
165
"owner": "joyceerhl",
166
"comment": "Counts accept/reject actions for a proposed edit from panel chat",
167
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },
168
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for the message request that is being followed-up." },
169
"participant": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The name of the chat participant for this message." },
170
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command used for the chat participant." },
171
"outcome": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The user decision taken for the edited file" },
172
"hasRemainingEdits": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Whether there are additional unactioned edits in the file." },
173
"isNotebook": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Whether the document is a notebook." },
174
"isNotebookCell": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Whether the document is a notebook cell." }
175
}
176
*/
177
this.telemetryService.sendMSFTTelemetryEvent('panel.edit.feedback', {
178
languageId: document?.languageId,
179
requestId: result.metadata?.responseId,
180
participant: agentId,
181
command: result.metadata?.command,
182
outcome: outcomes.get(e.action.outcome) ?? 'unknown',
183
hasRemainingEdits: String(e.action.hasRemainingEdits),
184
}, {
185
isNotebook: this.notebookService.hasSupportedNotebooks(e.action.uri) ? 1 : 0,
186
isNotebookCell: e.action.uri.scheme === Schemas.vscodeNotebookCell ? 1 : 0
187
});
188
189
this.telemetryService.sendGHTelemetryEvent('panel.edit.feedback', {
190
languageId: document?.languageId,
191
requestId: result.metadata?.responseId,
192
participant: agentId,
193
command: result.metadata?.command,
194
outcome: outcomes.get(e.action.outcome) ?? 'unknown',
195
hasRemainingEdits: String(e.action.hasRemainingEdits),
196
}, {
197
isNotebook: this.notebookService.hasSupportedNotebooks(e.action.uri) ? 1 : 0,
198
isNotebookCell: e.action.uri.scheme === Schemas.vscodeNotebookCell ? 1 : 0
199
});
200
201
202
this.telemetryService.sendInternalMSFTTelemetryEvent('panel.edit.feedback', {
203
languageId: document?.languageId,
204
requestId: result.metadata?.responseId,
205
participant: agentId,
206
command: result.metadata?.command,
207
outcome: outcomes.get(e.action.outcome) ?? 'unknown',
208
hasRemainingEdits: String(e.action.hasRemainingEdits),
209
}, {
210
isNotebook: this.notebookService.hasSupportedNotebooks(e.action.uri) ? 1 : 0,
211
isNotebookCell: e.action.uri.scheme === Schemas.vscodeNotebookCell ? 1 : 0
212
});
213
214
{
215
const otelOutcome = outcomes.get(e.action.outcome) ?? 'unknown';
216
const workspace = resolveWorkspaceOTelMetadata(this.gitService, e.action.uri);
217
emitEditFeedbackEvent(this.otelService, otelOutcome, document?.languageId ?? '', agentId, result.metadata?.responseId ?? '', 'agent', e.action.hasRemainingEdits, this.notebookService.hasSupportedNotebooks(e.action.uri), workspace);
218
if (e.action.outcome === vscode.ChatEditingSessionActionOutcome.Accepted
219
|| e.action.outcome === vscode.ChatEditingSessionActionOutcome.Rejected) {
220
GenAiMetrics.recordEditAcceptance(this.otelService, 'chat_editing', otelOutcome, document?.languageId);
221
}
222
GenAiMetrics.recordChatEditOutcome(this.otelService, 'chat_editing', otelOutcome, document?.languageId, e.action.hasRemainingEdits);
223
}
224
225
if (result.metadata?.responseId
226
&& (e.action.outcome === vscode.ChatEditingSessionActionOutcome.Accepted
227
|| e.action.outcome === vscode.ChatEditingSessionActionOutcome.Rejected)
228
) {
229
const outcome = e.action.outcome === vscode.ChatEditingSessionActionOutcome.Accepted ? 'accept' : 'reject';
230
this.multiFileEditTelemetryService.sendEditPromptAndResult({ chatRequestId: result.metadata.responseId }, e.action.uri, outcome);
231
}
232
}
233
break;
234
case 'chatEditingHunkAction': {
235
const outcome = outcomes.get(e.action.outcome);
236
if (outcome) {
237
238
const properties = {
239
requestId: result.metadata?.responseId ?? '',
240
languageId: document?.languageId ?? '',
241
outcome,
242
};
243
const measurements = {
244
hasRemainingEdits: e.action.hasRemainingEdits ? 1 : 0,
245
isNotebook: this.notebookService.hasSupportedNotebooks(e.action.uri) ? 1 : 0,
246
isNotebookCell: e.action.uri.scheme === Schemas.vscodeNotebookCell ? 1 : 0,
247
lineCount: e.action.lineCount,
248
linesAdded: e.action.linesAdded,
249
linesRemoved: e.action.linesRemoved,
250
};
251
252
sendUserActionTelemetry(
253
this.telemetryService,
254
document ?? vscode.window.activeTextEditor?.document,
255
properties,
256
measurements,
257
'edit.hunk.action'
258
);
259
260
emitEditHunkActionEvent(this.otelService, outcome, document?.languageId ?? '', result.metadata?.responseId ?? '', e.action.lineCount, e.action.linesAdded, e.action.linesRemoved, resolveWorkspaceOTelMetadata(this.gitService, e.action.uri));
261
GenAiMetrics.recordEditAcceptance(this.otelService, 'chat_editing_hunk', outcome, document?.languageId ?? '');
262
if (outcome === 'accepted') {
263
GenAiMetrics.incrementLinesOfCode(this.otelService, 'added', document?.languageId ?? '', e.action.linesAdded);
264
GenAiMetrics.incrementLinesOfCode(this.otelService, 'removed', document?.languageId ?? '', e.action.linesRemoved);
265
}
266
}
267
break;
268
}
269
}
270
271
if (e.action.kind === 'copy' || e.action.kind === 'insert') {
272
let measurements = {};
273
274
// Both copy and insert actions have a totalCharacters property
275
measurements = {
276
totalCharacters: e.action.totalCharacters,
277
totalLines: e.action.totalLines,
278
isAgent: agentId === getChatParticipantIdFromName(editsAgentName) ? 1 : 0,
279
};
280
281
// Copy actions have a copiedCharacters/Lines property since this includes manual copying which can be partial
282
let compType: 'full' | 'partial' = 'full';
283
if (e.action.kind === 'copy') {
284
measurements = {
285
...measurements,
286
copiedCharacters: e.action.copiedCharacters,
287
copiedLines: e.action.copiedLines,
288
};
289
if (e.action.copiedCharacters !== e.action.totalCharacters) {
290
compType = 'partial';
291
}
292
}
293
294
// If there is a document and selection, include cursor location
295
if (document && selection) {
296
measurements = {
297
...measurements,
298
cursorLocation: document.offsetAt(selection.active),
299
};
300
}
301
sendUserActionTelemetry(
302
this.telemetryService,
303
vscode.window.activeTextEditor?.document,
304
{
305
codeBlockIndex: e.action.codeBlockIndex.toString(),
306
messageId: result.metadata?.modelMessageId ?? '',
307
headerRequestId: result.metadata?.responseId ?? '',
308
participant: agentId,
309
languageId: e.action.languageId ?? '',
310
modelId: resolveModelIdForTelemetry(e.action.modelId ?? '', result.metadata?.resolvedModel),
311
comp_type: compType,
312
mode: participantIdToModeName(agentId),
313
},
314
measurements,
315
e.action.kind === 'copy' ? 'conversation.acceptedCopy' : 'conversation.acceptedInsert'
316
);
317
}
318
319
if (e.action.kind === 'apply') {
320
// Note- this event is fired after a "keep"
321
this.handleApplyAction(e.action, agentId, result);
322
}
323
}
324
325
private handleApplyAction(e: vscode.ChatApplyAction, agentId: string, result: ICopilotChatResultIn): void {
326
sendUserActionTelemetry(
327
this.telemetryService,
328
vscode.window.activeTextEditor?.document,
329
{
330
codeBlockIndex: e.codeBlockIndex.toString(),
331
messageId: result.metadata?.modelMessageId ?? '',
332
headerRequestId: result.metadata?.responseId ?? '',
333
participant: agentId,
334
languageId: e.languageId ?? '',
335
modelId: resolveModelIdForTelemetry(e.modelId, result.metadata?.resolvedModel),
336
mode: participantIdToModeName(agentId),
337
},
338
{
339
isAgent: agentId === getChatParticipantIdFromName(editsAgentName) ? 1 : 0,
340
totalLines: e.totalLines,
341
},
342
'conversation.appliedCodeblock'
343
);
344
GenAiMetrics.incrementUserActionCount(this.otelService, 'apply');
345
}
346
347
handleFeedback(e: vscode.ChatResultFeedback, agentId: string): void {
348
const document = vscode.window.activeTextEditor?.document;
349
350
const result = e.result as ICopilotChatResultIn;
351
352
/* __GDPR__
353
"panel.action.vote" : {
354
"owner": "digitarald",
355
"comment": "Counts votes on a chat panel response",
356
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Language of the currently open document." },
357
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Id for this message request." },
358
"direction": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "If the vote was positive or negative." },
359
"participant": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "comment": "The name of the chat participant for this message." },
360
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": false, "comment": "The command used for the chat participant." },
361
"conversationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The id of the conversation." }
362
}
363
*/
364
this.telemetryService.sendMSFTTelemetryEvent('panel.action.vote', {
365
languageId: document?.languageId,
366
requestId: result.metadata?.responseId,
367
participant: agentId,
368
command: result.metadata?.command,
369
conversationId: result.metadata?.sessionId
370
}, {
371
direction: e.kind === vscode.ChatResultFeedbackKind.Helpful ? 1 : 2, // map to previous enum values
372
});
373
374
sendUserActionTelemetry(
375
this.telemetryService,
376
document,
377
{
378
rating: e.kind === vscode.ChatResultFeedbackKind.Helpful ? 'positive' : 'negative',
379
messageId: result.metadata?.modelMessageId ?? '',
380
headerRequestId: result.metadata?.responseId ?? '',
381
},
382
{},
383
'conversation.messageRating'
384
);
385
386
const otelRating = e.kind === vscode.ChatResultFeedbackKind.Helpful ? 'positive' : 'negative';
387
emitUserFeedbackEvent(this.otelService, otelRating, agentId, result.metadata?.sessionId ?? '', result.metadata?.responseId ?? '');
388
GenAiMetrics.incrementUserFeedbackCount(this.otelService, otelRating);
389
}
390
391
// --- inline
392
393
394
private _handleChatUserAction(sessionId: string | undefined, conversation: Conversation, event: vscode.ChatUserActionEvent) {
395
396
enum InteractiveEditorResponseFeedbackKind {
397
Undone = 2,
398
Accepted = 3,
399
Bug = 4
400
}
401
402
if (!sessionId) {
403
return;
404
}
405
406
let kind: InteractiveEditorResponseFeedbackKind | undefined;
407
if (event.action.kind === 'editor') {
408
kind = event.action.accepted ? InteractiveEditorResponseFeedbackKind.Accepted : InteractiveEditorResponseFeedbackKind.Undone;
409
} else if (event.action.kind === 'bug') {
410
kind = InteractiveEditorResponseFeedbackKind.Bug;
411
}
412
413
if (kind === undefined) {
414
return;
415
}
416
417
const response = conversation.getLatestTurn().getMetadata(CopilotInteractiveEditorResponse);
418
if (!response) {
419
return;
420
}
421
422
let interactionOutcome = conversation.getLatestTurn().getMetadata(InteractionOutcome);
423
if (!interactionOutcome) {
424
interactionOutcome = new InteractionOutcome(!response.telemetry?.editCount ? 'none' : 'inlineEdit', []);
425
}
426
427
if (kind === InteractiveEditorResponseFeedbackKind.Bug && conversation) {
428
this.feedbackReporter.reportInline(conversation, response.promptQuery, interactionOutcome);
429
return;
430
}
431
432
const userActionProperties: { messageId: string; action?: 'undo' | 'accept' } = {
433
messageId: response.messageId,
434
};
435
let telemetryEventName: string;
436
437
const { selection, wholeRange, intent, query } = response.promptQuery;
438
439
const requestId = conversation?.getLatestTurn().id;
440
const intentId = intent?.id;
441
const languageId = response.promptQuery.document.languageId;
442
443
// TODO: Only collect for /fix
444
const diagnosticsTelemetryData = (
445
intentId === Intent.Fix
446
? findDiagnosticsTelemetry(selection, this.languageDiagnosticsService.getDiagnostics(response.promptQuery.document.uri))
447
: undefined
448
);
449
const isNotebookDocument = isNotebookCellOrNotebookChatInput(response.promptQuery.document.uri) ? 1 : 0;
450
451
this.surveyService.signalUsage(`inline.${intentId ?? 'default'}`, languageId);
452
453
const sharedProps = {
454
languageId: languageId,
455
replyType: interactionOutcome.kind,
456
conversationId: sessionId,
457
requestId: requestId,
458
command: intentId
459
};
460
const editCount = response.telemetry?.editCount ?? 0;
461
const editLineCount = response.telemetry?.editLineCount ?? 0;
462
const sharedMeasures: TelemetryEventMeasurements = {
463
selectionLineCount: selection ? Math.abs(selection.end.line - selection.start.line) : -1,
464
wholeRangeLineCount: wholeRange ? Math.abs(wholeRange.end.line - wholeRange.start.line) : -1,
465
editCount: editCount > 0 ? editCount : -1,
466
editLineCount: editLineCount > 0 ? editLineCount : -1,
467
isNotebook: isNotebookDocument,
468
problemsCount: diagnosticsTelemetryData?.fileDiagnosticsTelemetry.problemsCount ?? 0,
469
selectionProblemsCount: diagnosticsTelemetryData?.selectionDiagnosticsTelemetry.problemsCount ?? 0,
470
diagnosticsCount: diagnosticsTelemetryData?.fileDiagnosticsTelemetry.diagnosticsCount ?? 0,
471
selectionDiagnosticsCount: diagnosticsTelemetryData?.selectionDiagnosticsTelemetry.diagnosticsCount ?? 0,
472
};
473
474
if (kind === InteractiveEditorResponseFeedbackKind.Accepted && response.editSurvivalTracker) {
475
response.editSurvivalTracker.startReporter(res => reportInlineEditSurvivalEvent(res, sharedProps, sharedMeasures, this.otelService));
476
}
477
(response as any).editSurvivalTracker = undefined; // TODO@jrieken
478
479
const accepted = (kind === InteractiveEditorResponseFeedbackKind.Accepted) ? 1 : 0;
480
/* __GDPR__
481
"inline.done" : {
482
"owner": "digitarald",
483
"comment": "Metadata about an inline code suggestion being accepted or undone",
484
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The current file language." },
485
"replyType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "How response is shown in the interface." },
486
"conversationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The id of the inline assistant conversation." },
487
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The id of the current request turn." },
488
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command which was used in providing the response." },
489
"accepted": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Whether the user accepted the suggested code or discarded it." },
490
"selectionLineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many lines are in the current selection." },
491
"wholeRangeLineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many lines are in the expanded whole range." },
492
"editCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many edits are suggested." },
493
"editLineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many lines are in all suggested edits." },
494
"problemsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many problems are in the current code." },
495
"selectionProblemsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many problems are in the current selected code." },
496
"diagnosticsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many diagnostic codes are in the current code." },
497
"selectionDiagnosticsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many diagnostic codes are in the current code." },
498
"isNotebook": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Whether the document is a notebook." }
499
}
500
*/
501
this.telemetryService.sendMSFTTelemetryEvent('inline.done', sharedProps, {
502
...sharedMeasures, accepted
503
});
504
this.telemetryService.sendGHTelemetryEvent('inline.done', sharedProps, {
505
...sharedMeasures, accepted
506
});
507
508
emitInlineDoneEvent(this.otelService, accepted === 1, languageId, editCount, editLineCount, interactionOutcome.kind, isNotebookDocument === 1, resolveWorkspaceOTelMetadata(this.gitService, response.promptQuery.document.uri));
509
GenAiMetrics.recordEditAcceptance(this.otelService, 'inline_chat', accepted === 1 ? 'accepted' : 'rejected', languageId);
510
511
this.telemetryService.sendInternalMSFTTelemetryEvent('interactiveSessionDone', {
512
language: languageId,
513
intent: intentId,
514
query: query,
515
conversationId: sessionId,
516
requestId: requestId,
517
replyType: interactionOutcome.kind,
518
problems: diagnosticsTelemetryData?.fileDiagnosticsTelemetry.problems ?? '',
519
selectionProblems: diagnosticsTelemetryData?.selectionDiagnosticsTelemetry.problems ?? '',
520
diagnosticCodes: diagnosticsTelemetryData?.fileDiagnosticsTelemetry.diagnosticCodes ?? '',
521
selectionDiagnosticCodes: diagnosticsTelemetryData?.selectionDiagnosticsTelemetry.diagnosticCodes ?? '',
522
}, { isNotebook: isNotebookDocument, accepted });
523
524
switch (kind) {
525
case InteractiveEditorResponseFeedbackKind.Undone:
526
userActionProperties['action'] = 'undo';
527
telemetryEventName = 'inlineConversation.undo';
528
break;
529
case InteractiveEditorResponseFeedbackKind.Accepted:
530
userActionProperties['action'] = 'accept';
531
telemetryEventName = 'inlineConversation.accept';
532
break;
533
case InteractiveEditorResponseFeedbackKind.Bug:
534
telemetryEventName = '';
535
break;
536
}
537
538
if (telemetryEventName) {
539
sendUserActionTelemetry(this.telemetryService, response.promptQuery.document, userActionProperties, {}, telemetryEventName);
540
}
541
}
542
}
543
544
function reportInlineEditSurvivalEvent(res: EditSurvivalResult, sharedProps: TelemetryEventProperties | undefined, sharedMeasures: TelemetryEventMeasurements | undefined, otelService: IOTelService) {
545
/* __GDPR__
546
"inline.trackEditSurvival" : {
547
"owner": "hediet",
548
"comment": "Tracks how much percent of the AI edits surived after 5 minutes of accepting",
549
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The current file language." },
550
"replyType": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "How response is shown in the interface." },
551
"conversationId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The id of the inline assistant conversation." },
552
"requestId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The id of the current request turn." },
553
"command": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command which was used in providing the response." },
554
"survivalRateFourGram": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The rate between 0 and 1 of how much of the AI edit is still present in the document." },
555
"survivalRateNoRevert": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The rate between 0 and 1 of how much of the ranges the AI touched ended up being reverted." },
556
"didBranchChange": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Indicates if the branch changed in the meantime. If the branch changed (value is 1), this event should probably be ignored." },
557
"timeDelayMs": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "The time delay between the user accepting the edit and measuring the survival rate." },
558
"selectionLineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many lines are in the current selection." },
559
"wholeRangeLineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many lines are in the expanded whole range." },
560
"editCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many edits are suggested." },
561
"editLineCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many lines are in all suggested edits." },
562
"problemsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many problems are in the current code." },
563
"selectionProblemsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many problems are in the current selected code." },
564
"diagnosticsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many diagnostic codes are in the current code." },
565
"selectionDiagnosticsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "How many diagnostic codes are in the current selected code." },
566
"isNotebook": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Whether the document is a notebook" }
567
}
568
*/
569
res.telemetryService.sendMSFTTelemetryEvent('inline.trackEditSurvival', sharedProps, {
570
...sharedMeasures,
571
survivalRateFourGram: res.fourGram,
572
survivalRateNoRevert: res.noRevert,
573
timeDelayMs: res.timeDelayMs,
574
didBranchChange: res.didBranchChange ? 1 : 0,
575
});
576
res.telemetryService.sendGHTelemetryEvent('inline.trackEditSurvival', {
577
...sharedProps,
578
headBranchName: res.workspace?.headBranchName,
579
headCommitHash: res.workspace?.headCommitHash,
580
remoteUrl: res.workspace?.remoteUrl,
581
fileRelativePath: res.workspace?.fileRelativePath,
582
}, {
583
...sharedMeasures,
584
survivalRateFourGram: res.fourGram,
585
survivalRateNoRevert: res.noRevert,
586
timeDelayMs: res.timeDelayMs,
587
didBranchChange: res.didBranchChange ? 1 : 0,
588
});
589
590
emitEditSurvivalEvent(otelService, 'inline_chat', res.fourGram, res.noRevert, res.timeDelayMs, res.didBranchChange, String(sharedProps?.requestId ?? ''), res.workspace);
591
GenAiMetrics.recordEditSurvivalFourGram(otelService, 'inline_chat', res.fourGram, res.timeDelayMs);
592
GenAiMetrics.recordEditSurvivalNoRevert(otelService, 'inline_chat', res.noRevert, res.timeDelayMs);
593
}
594
595
const outcomes = new Map<vscode.ChatEditingSessionActionOutcome, EditOutcome>([
596
[vscode.ChatEditingSessionActionOutcome.Accepted, 'accepted'],
597
[vscode.ChatEditingSessionActionOutcome.Rejected, 'rejected'],
598
[vscode.ChatEditingSessionActionOutcome.Saved, 'saved']
599
]);
600
601