Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/common/chatServiceTelemetry.ts
3296 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
import { URI } from '../../../../base/common/uri.js';
7
import { isLocation } from '../../../../editor/common/languages.js';
8
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
9
import { IChatAgentData } from './chatAgents.js';
10
import { ChatRequestModel, IChatRequestVariableData } from './chatModel.js';
11
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from './chatParserTypes.js';
12
import { ChatAgentVoteDirection, ChatCopyKind, IChatSendRequestOptions, IChatUserActionEvent } from './chatService.js';
13
import { isImageVariableEntry } from './chatVariableEntries.js';
14
import { ChatAgentLocation } from './constants.js';
15
import { ILanguageModelsService } from './languageModels.js';
16
17
type ChatVoteEvent = {
18
direction: 'up' | 'down';
19
agentId: string;
20
command: string | undefined;
21
reason: string | undefined;
22
};
23
24
type ChatVoteClassification = {
25
direction: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user voted up or down.' };
26
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this vote is for.' };
27
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command that this vote is for.' };
28
reason: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The reason selected by the user for voting down.' };
29
owner: 'roblourens';
30
comment: 'Provides insight into the performance of Chat agents.';
31
};
32
33
type ChatCopyEvent = {
34
copyKind: 'action' | 'toolbar';
35
agentId: string;
36
command: string | undefined;
37
};
38
39
type ChatCopyClassification = {
40
copyKind: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'How the copy was initiated.' };
41
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that the copy acted on.' };
42
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command the copy acted on.' };
43
owner: 'roblourens';
44
comment: 'Provides insight into the usage of Chat features.';
45
};
46
47
type ChatInsertEvent = {
48
newFile: boolean;
49
agentId: string;
50
command: string | undefined;
51
};
52
53
type ChatInsertClassification = {
54
newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' };
55
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this insertion is for.' };
56
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command that this insertion is for.' };
57
owner: 'roblourens';
58
comment: 'Provides insight into the usage of Chat features.';
59
};
60
61
type ChatApplyEvent = {
62
newFile: boolean;
63
agentId: string;
64
command: string | undefined;
65
codeMapper: string | undefined;
66
editsProposed: boolean;
67
};
68
69
type ChatApplyClassification = {
70
newFile: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the code was inserted into a new untitled file.' };
71
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the chat agent that this insertion is for.' };
72
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the slash command that this insertion is for.' };
73
codeMapper: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The code mapper that wa used to compute the edit.' };
74
editsProposed: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether there was a change proposed to the user.' };
75
owner: 'aeschli';
76
comment: 'Provides insight into the usage of Chat features.';
77
};
78
79
type ChatFollowupEvent = {
80
agentId: string;
81
command: string | undefined;
82
};
83
84
type ChatFollowupClassification = {
85
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' };
86
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' };
87
owner: 'roblourens';
88
comment: 'Provides insight into the usage of Chat features.';
89
};
90
91
type ChatTerminalEvent = {
92
languageId: string;
93
agentId: string;
94
command: string | undefined;
95
};
96
97
type ChatTerminalClassification = {
98
languageId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The language of the code that was run in the terminal.' };
99
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' };
100
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' };
101
owner: 'roblourens';
102
comment: 'Provides insight into the usage of Chat features.';
103
};
104
105
type ChatFollowupsRetrievedEvent = {
106
agentId: string;
107
command: string | undefined;
108
numFollowups: number;
109
};
110
111
type ChatFollowupsRetrievedClassification = {
112
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' };
113
command: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The name of the related slash command.' };
114
numFollowups: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of followup prompts returned by the agent.' };
115
owner: 'roblourens';
116
comment: 'Provides insight into the usage of Chat features.';
117
};
118
119
type ChatEditHunkEvent = {
120
agentId: string;
121
outcome: 'accepted' | 'rejected';
122
lineCount: number;
123
hasRemainingEdits: boolean;
124
};
125
126
type ChatEditHunkClassification = {
127
agentId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The ID of the related chat agent.' };
128
outcome: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The outcome of the edit hunk action.' };
129
lineCount: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of lines in the relevant change.' };
130
hasRemainingEdits: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether there are remaining edits in the file after this action.' };
131
owner: 'roblourens';
132
comment: 'Provides insight into the usage of Chat features.';
133
};
134
135
export type ChatProviderInvokedEvent = {
136
timeToFirstProgress: number | undefined;
137
totalTime: number | undefined;
138
result: 'success' | 'error' | 'errorWithOutput' | 'cancelled' | 'filtered';
139
requestType: 'string' | 'followup' | 'slashCommand';
140
chatSessionId: string;
141
agent: string;
142
agentExtensionId: string | undefined;
143
slashCommand: string | undefined;
144
location: ChatAgentLocation;
145
citations: number;
146
numCodeBlocks: number;
147
isParticipantDetected: boolean;
148
enableCommandDetection: boolean;
149
attachmentKinds: string[];
150
model: string | undefined;
151
};
152
153
export type ChatProviderInvokedClassification = {
154
timeToFirstProgress: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The time in milliseconds from invoking the provider to getting the first data.' };
155
totalTime: { classification: 'SystemMetaData'; purpose: 'PerformanceAndHealth'; comment: 'The total time it took to run the provider\'s `provideResponseWithProgress`.' };
156
result: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether invoking the ChatProvider resulted in an error.' };
157
requestType: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of request that the user made.' };
158
chatSessionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'A random ID for the session.' };
159
agent: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of agent used.' };
160
agentExtensionId: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The extension that contributed the agent.' };
161
slashCommand?: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The type of slashCommand used.' };
162
location: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The location at which chat request was made.' };
163
citations: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of public code citations that were returned with the response.' };
164
numCodeBlocks: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The number of code blocks in the response.' };
165
isParticipantDetected: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the participant was automatically detected.' };
166
enableCommandDetection: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether participation detection was disabled for this invocation.' };
167
attachmentKinds: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The types of variables/attachments that the user included with their query.' };
168
model: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The model used to generate the response.' };
169
owner: 'roblourens';
170
comment: 'Provides insight into the performance of Chat agents.';
171
};
172
173
export class ChatServiceTelemetry {
174
constructor(
175
@ITelemetryService private readonly telemetryService: ITelemetryService,
176
) { }
177
178
notifyUserAction(action: IChatUserActionEvent): void {
179
if (action.action.kind === 'vote') {
180
this.telemetryService.publicLog2<ChatVoteEvent, ChatVoteClassification>('interactiveSessionVote', {
181
direction: action.action.direction === ChatAgentVoteDirection.Up ? 'up' : 'down',
182
agentId: action.agentId ?? '',
183
command: action.command,
184
reason: action.action.reason,
185
});
186
} else if (action.action.kind === 'copy') {
187
this.telemetryService.publicLog2<ChatCopyEvent, ChatCopyClassification>('interactiveSessionCopy', {
188
copyKind: action.action.copyKind === ChatCopyKind.Action ? 'action' : 'toolbar',
189
agentId: action.agentId ?? '',
190
command: action.command,
191
});
192
} else if (action.action.kind === 'insert') {
193
this.telemetryService.publicLog2<ChatInsertEvent, ChatInsertClassification>('interactiveSessionInsert', {
194
newFile: !!action.action.newFile,
195
agentId: action.agentId ?? '',
196
command: action.command,
197
});
198
} else if (action.action.kind === 'apply') {
199
this.telemetryService.publicLog2<ChatApplyEvent, ChatApplyClassification>('interactiveSessionApply', {
200
newFile: !!action.action.newFile,
201
codeMapper: action.action.codeMapper,
202
agentId: action.agentId ?? '',
203
command: action.command,
204
editsProposed: !!action.action.editsProposed,
205
});
206
} else if (action.action.kind === 'runInTerminal') {
207
this.telemetryService.publicLog2<ChatTerminalEvent, ChatTerminalClassification>('interactiveSessionRunInTerminal', {
208
languageId: action.action.languageId ?? '',
209
agentId: action.agentId ?? '',
210
command: action.command,
211
});
212
} else if (action.action.kind === 'followUp') {
213
this.telemetryService.publicLog2<ChatFollowupEvent, ChatFollowupClassification>('chatFollowupClicked', {
214
agentId: action.agentId ?? '',
215
command: action.command,
216
});
217
} else if (action.action.kind === 'chatEditingHunkAction') {
218
this.telemetryService.publicLog2<ChatEditHunkEvent, ChatEditHunkClassification>('chatEditHunk', {
219
agentId: action.agentId ?? '',
220
outcome: action.action.outcome,
221
lineCount: action.action.lineCount,
222
hasRemainingEdits: action.action.hasRemainingEdits,
223
});
224
}
225
}
226
227
retrievedFollowups(agentId: string, command: string | undefined, numFollowups: number): void {
228
this.telemetryService.publicLog2<ChatFollowupsRetrievedEvent, ChatFollowupsRetrievedClassification>('chatFollowupsRetrieved', {
229
agentId,
230
command,
231
numFollowups,
232
});
233
}
234
}
235
236
function getCodeBlocks(text: string): string[] {
237
const lines = text.split('\n');
238
const codeBlockLanguages: string[] = [];
239
240
let codeBlockState: undefined | { readonly delimiter: string; readonly languageId: string };
241
for (let i = 0; i < lines.length; i++) {
242
const line = lines[i];
243
244
if (codeBlockState) {
245
if (new RegExp(`^\\s*${codeBlockState.delimiter}\\s*$`).test(line)) {
246
codeBlockLanguages.push(codeBlockState.languageId);
247
codeBlockState = undefined;
248
}
249
} else {
250
const match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)/);
251
if (match) {
252
codeBlockState = { delimiter: match[2], languageId: match[3] };
253
}
254
}
255
}
256
return codeBlockLanguages;
257
}
258
259
export class ChatRequestTelemetry {
260
private isComplete = false;
261
262
constructor(private readonly opts: {
263
agentPart: ChatRequestAgentPart | undefined;
264
agentSlashCommandPart: ChatRequestAgentSubcommandPart | undefined;
265
commandPart: ChatRequestSlashCommandPart | undefined;
266
sessionId: string;
267
location: ChatAgentLocation;
268
options: IChatSendRequestOptions | undefined;
269
enableCommandDetection: boolean;
270
},
271
@ITelemetryService private readonly telemetryService: ITelemetryService,
272
@ILanguageModelsService private readonly languageModelsService: ILanguageModelsService
273
) { }
274
275
complete({ timeToFirstProgress, totalTime, result, requestType, request, detectedAgent }: {
276
timeToFirstProgress: number | undefined;
277
totalTime: number | undefined;
278
result: ChatProviderInvokedEvent['result'];
279
requestType: ChatProviderInvokedEvent['requestType'];
280
// Should rearrange so these 2 can be in the constructor
281
request: ChatRequestModel;
282
detectedAgent: IChatAgentData | undefined;
283
}) {
284
if (this.isComplete) {
285
return;
286
}
287
288
this.isComplete = true;
289
this.telemetryService.publicLog2<ChatProviderInvokedEvent, ChatProviderInvokedClassification>('interactiveSessionProviderInvoked', {
290
timeToFirstProgress,
291
totalTime,
292
result,
293
requestType,
294
agent: detectedAgent?.id ?? this.opts.agentPart?.agent.id ?? '',
295
agentExtensionId: detectedAgent?.extensionId.value ?? this.opts.agentPart?.agent.extensionId.value ?? '',
296
slashCommand: this.opts.agentSlashCommandPart ? this.opts.agentSlashCommandPart.command.name : this.opts.commandPart?.slashCommand.command,
297
chatSessionId: this.opts.sessionId,
298
enableCommandDetection: this.opts.enableCommandDetection,
299
isParticipantDetected: !!detectedAgent,
300
location: this.opts.location,
301
citations: request.response?.codeCitations.length ?? 0,
302
numCodeBlocks: getCodeBlocks(request.response?.response.toString() ?? '').length,
303
attachmentKinds: this.attachmentKindsForTelemetry(request.variableData),
304
model: this.resolveModelId(this.opts.options?.userSelectedModelId),
305
});
306
}
307
308
private attachmentKindsForTelemetry(variableData: IChatRequestVariableData): string[] {
309
// this shows why attachments still have to be cleaned up somewhat
310
return variableData.variables.map(v => {
311
if (v.kind === 'implicit') {
312
return 'implicit';
313
} else if (v.range) {
314
// 'range' is range within the prompt text
315
if (v.kind === 'tool') {
316
return 'toolInPrompt';
317
} else if (v.kind === 'toolset') {
318
return 'toolsetInPrompt';
319
} else {
320
return 'fileInPrompt';
321
}
322
} else if (v.kind === 'command') {
323
return 'command';
324
} else if (v.kind === 'symbol') {
325
return 'symbol';
326
} else if (isImageVariableEntry(v)) {
327
return 'image';
328
} else if (v.kind === 'directory') {
329
return 'directory';
330
} else if (v.kind === 'tool') {
331
return 'tool';
332
} else if (v.kind === 'toolset') {
333
return 'toolset';
334
} else {
335
if (URI.isUri(v.value)) {
336
return 'file';
337
} else if (isLocation(v.value)) {
338
return 'location';
339
} else {
340
return 'otherAttachment';
341
}
342
}
343
});
344
}
345
346
private resolveModelId(userSelectedModelId: string | undefined): string | undefined {
347
return userSelectedModelId && this.languageModelsService.lookupLanguageModel(userSelectedModelId)?.id;
348
}
349
}
350
351