Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/node/telemetry.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
import type { TextDocument } from 'vscode';
7
import { ChatLocation } from '../../../platform/chat/common/commonTypes';
8
import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';
9
import { ITelemetryService, TelemetryProperties } from '../../../platform/telemetry/common/telemetry';
10
import { TelemetryData } from '../../../platform/telemetry/common/telemetryData';
11
import { generateUuid } from '../../../util/vs/base/common/uuid';
12
import { Conversation } from '../common/conversation';
13
14
export type ConversationalBaseTelemetryData = ConversationalTelemetryData<{ messageId: string }, { promptTokenLen: number; messageCharLen: number }>;
15
16
export function createTelemetryWithId(): ConversationalBaseTelemetryData {
17
const uniqueId = generateUuid();
18
const baseTelemetry = TelemetryData.createAndMarkAsIssued({ messageId: uniqueId });
19
return new ConversationalTelemetryData(baseTelemetry);
20
}
21
22
export class ConversationalTelemetryData<P extends TelemetryProperties, M extends { [key: string]: number }> {
23
24
public get properties(): P { return this.raw.properties as P; }
25
public get measurements(): M { return this.raw.measurements as M; }
26
27
constructor(
28
public readonly raw: TelemetryData
29
) { }
30
31
markAsDisplayed(): void {
32
this.raw.markAsDisplayed();
33
}
34
35
extendedBy<P2 extends TelemetryProperties, M2 extends { [key: string]: number }>(properties?: P2, measurements?: M2): ConversationalTelemetryData<P & P2, M & M2> {
36
const newTelemetryData = this.raw.extendedBy(properties, measurements);
37
return new ConversationalTelemetryData(newTelemetryData);
38
}
39
}
40
41
export function extendUserMessageTelemetryData(
42
conversation: Conversation,
43
conversationId: string,
44
location: ChatLocation,
45
message: string,
46
promptTokenLen: number,
47
suggestion: string | undefined,
48
baseTelemetry: ConversationalBaseTelemetryData
49
): ConversationalBaseTelemetryData {
50
51
const properties: TelemetryProperties = {
52
source: 'user',
53
turnIndex: (conversation.turns.length - 1).toString(),
54
conversationId,
55
uiKind: ChatLocation.toString(location)
56
};
57
const measurements = {
58
promptTokenLen: promptTokenLen,
59
messageCharLen: message.length,
60
};
61
if (suggestion) {
62
properties.suggestion = suggestion;
63
}
64
65
baseTelemetry = baseTelemetry.extendedBy(properties, measurements);
66
67
return baseTelemetry;
68
}
69
70
export function sendUserMessageTelemetry(
71
telemetryService: ITelemetryService,
72
location: ChatLocation,
73
requestId: string,
74
message: string | undefined,
75
offTopic: boolean | undefined,
76
doc: TextDocumentSnapshot | undefined,
77
baseTelemetry: ConversationalBaseTelemetryData,
78
modeName: string,
79
): void {
80
if (offTopic !== undefined) {
81
baseTelemetry = baseTelemetry.extendedBy({ offTopic: offTopic.toString() });
82
}
83
baseTelemetry = baseTelemetry.extendedBy({ headerRequestId: requestId });
84
sendConversationalMessageTelemetry(telemetryService, doc, location, message, { mode: modeName }, {}, baseTelemetry);
85
}
86
87
export function sendModelMessageTelemetry(
88
telemetryService: ITelemetryService,
89
conversation: Conversation,
90
location: ChatLocation,
91
appliedText: string,
92
requestId: string,
93
doc: TextDocumentSnapshot | undefined,
94
baseTelemetry: ConversationalBaseTelemetryData,
95
modeName: string,
96
): void {
97
// Get the languages of code blocks within the message
98
const codeBlockLanguages = getCodeBlocks(appliedText);
99
100
sendConversationalMessageTelemetry(
101
telemetryService,
102
doc,
103
location,
104
appliedText,
105
{
106
source: 'model',
107
turnIndex: conversation.turns.length.toString(),
108
conversationId: conversation.sessionId,
109
headerRequestId: requestId,
110
uiKind: ChatLocation.toString(location),
111
codeBlockLanguages: JSON.stringify({ ...codeBlockLanguages }),
112
mode: modeName,
113
},
114
{ messageCharLen: appliedText.length, numCodeBlocks: codeBlockLanguages.length },
115
baseTelemetry
116
);
117
}
118
119
export function sendOffTopicMessageTelemetry(
120
telemetryService: ITelemetryService,
121
conversation: Conversation,
122
location: ChatLocation,
123
appliedText: string,
124
userMessageId: string,
125
doc: TextDocumentSnapshot | undefined,
126
baseTelemetry: ConversationalBaseTelemetryData
127
): void {
128
sendConversationalMessageTelemetry(
129
telemetryService,
130
doc,
131
location,
132
appliedText,
133
{
134
source: 'offTopic',
135
turnIndex: conversation.turns.length.toString(),
136
conversationId: conversation.sessionId,
137
userMessageId: userMessageId,
138
uiKind: ChatLocation.toString(location),
139
},
140
{ messageCharLen: appliedText.length },
141
baseTelemetry
142
);
143
}
144
145
/** Create new telemetry data based on baseTelemetryData and send `conversation.message` event */
146
export function sendConversationalMessageTelemetry(
147
telemetryService: ITelemetryService,
148
document: TextDocumentSnapshot | undefined,
149
location: ChatLocation,
150
messageText: string | undefined,
151
properties: TelemetryProperties,
152
measurements: { [key: string]: number },
153
baseTelemetry: ConversationalBaseTelemetryData
154
): TelemetryData {
155
156
const enhancedProperties: { [key: string]: string } = {
157
...(messageText ? { messageText: messageText } : {}),
158
...properties,
159
};
160
161
if (document) {
162
properties.languageId = document.languageId;
163
measurements.documentLength = document.getText().length;
164
}
165
166
const standardTelemetryData = baseTelemetry.extendedBy(properties, measurements);
167
const enhancedTelemetryLogger = baseTelemetry.extendedBy(enhancedProperties);
168
169
// Telemetrize the message in standard and enhanced telemetry
170
// Enhanced telemetry will not be sent if the user isn't opted in, same as for ghostText
171
const prefix = telemetryPrefixForLocation(location);
172
173
telemetryService.sendGHTelemetryEvent(`${prefix}.message`, standardTelemetryData.raw.properties, standardTelemetryData.raw.measurements);
174
telemetryService.sendEnhancedGHTelemetryEvent(`${prefix}.messageText`, enhancedTelemetryLogger.raw.properties, enhancedTelemetryLogger.raw.measurements);
175
telemetryService.sendInternalMSFTTelemetryEvent(`${prefix}.messageText`, enhancedTelemetryLogger.raw.properties, enhancedTelemetryLogger.raw.measurements);
176
177
return standardTelemetryData.raw;
178
}
179
180
export function sendSuggestionShownTelemetryData(
181
telemetryService: ITelemetryService,
182
suggestion: string,
183
messageId: string,
184
suggestionId: string,
185
doc: TextDocument | TextDocumentSnapshot | undefined
186
): TelemetryData {
187
const telemetryData = sendUserActionTelemetry(
188
telemetryService,
189
doc,
190
{
191
suggestion: suggestion,
192
messageId: messageId,
193
suggestionId: suggestionId,
194
},
195
{},
196
'conversation.suggestionShown'
197
);
198
return telemetryData;
199
}
200
201
/** Create new telemetry data based on baseTelemetryData and send event with name */
202
export function sendUserActionTelemetry(
203
telemetryService: ITelemetryService,
204
document: TextDocument | TextDocumentSnapshot | undefined,
205
properties: TelemetryProperties,
206
measurements: { [key: string]: number },
207
name: string,
208
baseTelemetry?: TelemetryData
209
): TelemetryData {
210
const telemetryData = baseTelemetry ?? TelemetryData.createAndMarkAsIssued();
211
212
if (document) {
213
properties.languageId = document.languageId;
214
measurements.documentLength = document.getText().length;
215
}
216
217
const standardTelemetryData = telemetryData.extendedBy(properties, measurements);
218
telemetryService.sendGHTelemetryEvent(name, standardTelemetryData.properties, standardTelemetryData.measurements);
219
220
return standardTelemetryData;
221
}
222
223
function telemetryPrefixForLocation(location: ChatLocation): string {
224
switch (location) {
225
case ChatLocation.Editor:
226
return 'inlineConversation';
227
case ChatLocation.EditingSession:
228
return 'editingSession';
229
case ChatLocation.Panel:
230
default:
231
return 'conversation';
232
}
233
}
234
235
export interface ICodeblockDetails {
236
readonly languageId: string;
237
readonly totalLines: number;
238
}
239
240
export function getCodeBlocks(text: string): ICodeblockDetails[] {
241
const lines = text.split('\n');
242
const codeBlocks: ICodeblockDetails[] = [];
243
244
let codeBlockState: undefined | {
245
readonly delimiter: string;
246
readonly languageId: string;
247
totalLines: number;
248
};
249
for (let i = 0; i < lines.length; i++) {
250
const line = lines[i];
251
252
if (codeBlockState) {
253
if (new RegExp(`^\\s*${codeBlockState.delimiter}\\s*$`).test(line)) {
254
codeBlocks.push({
255
languageId: codeBlockState.languageId,
256
totalLines: codeBlockState.totalLines
257
});
258
codeBlockState = undefined;
259
} else {
260
codeBlockState.totalLines++;
261
}
262
} else {
263
const match = line.match(/^(\s*)(`{3,}|~{3,})(\w*)/);
264
if (match) {
265
codeBlockState = {
266
delimiter: match[2],
267
languageId: match[3],
268
totalLines: 0
269
};
270
}
271
}
272
}
273
return codeBlocks;
274
}
275
276