This repository was archived by the owner on Feb 25, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 295
/
Copy pathcsEventHandler.ts
248 lines (225 loc) · 7.82 KB
/
csEventHandler.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getSymbolNavigationActionTypeLabel } from '../utilities/stringifyEvent';
import { SidecarContextEvent, SidecarRequestRange } from '../server/types';
type UsageRequest = {
type: 'InlineCompletion' | 'ChatRequest' | 'InlineCodeEdit' | 'AgenticCodeEdit';
units: number;
timestamp: Date;
};
const USAGE_EVENTS_KEY = 'codestory.usageEvents';
const MAX_RETRIES = 5;
const RETRY_DELAY_MS = 1000; // 1 second
export class CSEventHandler implements vscode.CSEventHandler, vscode.Disposable {
private _disposable: vscode.Disposable;
private _subscriptionsAPIBase: string | null = null;
// The current recording session which the user is going through over here
private _currentSession: SidecarContextEvent[];
constructor(private readonly _context: vscode.ExtensionContext, _editorUrl: string | undefined) {
this._disposable = vscode.csevents.registerCSEventHandler(this);
this._currentSession = [];
if (vscode.env.uriScheme === 'aide') {
this._subscriptionsAPIBase = 'https://api.codestory.ai';
} else {
this._subscriptionsAPIBase = 'https://staging-api.codestory.ai';
}
}
async handleSymbolNavigation(event: vscode.SymbolNavigationEvent): Promise<void> {
const textDocument = await vscode.workspace.openTextDocument(vscode.Uri.file(event.uri.fsPath));
const wordRange = textDocument.getWordRangeAtPosition(event.position);
let textAtRange = undefined;
if (wordRange !== undefined) {
textAtRange = textDocument.getText(wordRange);
}
this._currentSession.push({
LSPContextEvent: {
fs_file_path: event.uri.fsPath,
position: {
line: event.position.line,
character: event.position.character,
byteOffset: 0,
},
source_word: textAtRange,
destination: null,
event_type: getSymbolNavigationActionTypeLabel(event.action),
}
});
console.log('handleSymbolNavigation');
console.log(event);
}
async handleAgentCodeEdit(event: { accepted: boolean; added: number; removed: number }): Promise<void> {
if (!event.accepted) {
return;
}
const usageRequest: UsageRequest = {
type: 'AgenticCodeEdit',
units: event.added + event.removed,
timestamp: new Date(),
};
const persistedEvents = this._context.globalState.get<UsageRequest[]>(USAGE_EVENTS_KEY, []);
persistedEvents.push(usageRequest);
this._context.globalState.update(USAGE_EVENTS_KEY, persistedEvents);
this.sendUsageEvents(persistedEvents);
}
private async sendUsageEvents(events: UsageRequest[]): Promise<void> {
await this.sendUsageEventsWithRetry(events, 0);
}
private async sendUsageEventsWithRetry(events: UsageRequest[], retryCount: number): Promise<void> {
if (retryCount >= MAX_RETRIES) {
console.error('Maximum retries exceeded for sending usage events.');
return;
}
const session = await vscode.csAuthentication.getSession();
if (!session) {
console.error('Failed to get authentication session.');
return;
}
const success = await this.sendUsageEvent(events, session);
if (success) {
this._context.globalState.update(USAGE_EVENTS_KEY, []);
} else {
const delay = RETRY_DELAY_MS * Math.pow(2, retryCount);
console.log(`Failed to send usage events. Retrying in ${delay} ms...`);
setTimeout(() => this.sendUsageEventsWithRetry(events, retryCount + 1), delay);
}
}
private async sendUsageEvent(events: UsageRequest[], session: vscode.CSAuthenticationSession): Promise<boolean> {
try {
const response = await fetch(
`${this._subscriptionsAPIBase}/v1/usage`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${session.accessToken}`,
},
method: 'POST',
body: JSON.stringify({ events }),
}
);
if (response.ok) {
return true;
} else if (response.status === 401) {
await vscode.commands.executeCommand('codestory.refreshTokens');
return false; // Retry after refreshing token
} else {
console.error(`Failed to send usage events. Status code: ${response.status}`);
return true; // Don't retry for other errors
}
} catch (error) {
console.error('Failed to send usage events:', error);
return true; // Don't retry on error
}
}
/**
* Starts recording the user activity over here and keeps track of the set of events the user is doing
*/
async startRecording() {
this._currentSession = [];
}
/**
* Stops recording the user activity and returns an object which can be used for inferencing
*/
async stopRecording(): Promise<SidecarContextEvent[]> {
const currentSession = this._currentSession;
this._currentSession = [];
return currentSession;
}
/**
* We are changing to a new text document or focussing on something new, its important
* to record this event
*/
async onDidChangeTextDocument(filePath: string) {
console.log('cs_event_handler::on_did_change_text_document', filePath);
// this._currentSession.push({
// OpenFile: {
// fs_file_path: filePath,
// }
// });
}
/**
* We are going to record the fact that the selection was changed in the editor
* This allows the the user to give context to the LLM by literally just doing what
* they would normally do in the editor
*/
async onDidChangeTextDocumentSelection(filePath: string, selections: readonly vscode.Selection[]) {
// we are getting multiple selections for the same file, so figure out what to do
// over here
if (this._currentSession.length === 0) {
this._currentSession.push({
Selection: {
fs_file_path: filePath,
range: this.selectionToSidecarRange(selections[0]),
}
});
return;
}
const lastEvent = this._currentSession.at(-1);
const currentSelectionRange = this.selectionToSidecarRange(selections[0]);
// If we have a lsp context event then we most likely here have the destination
// location over here
if (lastEvent !== undefined && lastEvent.LSPContextEvent !== undefined) {
lastEvent.LSPContextEvent.destination = {
position: currentSelectionRange.startPosition,
fs_file_path: filePath,
};
console.log('onDidChangeTextDocumentSelection::update_destination');
return;
}
if (lastEvent !== undefined && lastEvent.Selection !== null && lastEvent.Selection !== undefined) {
// we compare both the start and the end position line numbers here
// because the selection can be from the top dragging or the bottom
// dragging
if (lastEvent.Selection.range.startPosition.line === currentSelectionRange.startPosition.line || lastEvent.Selection.range.endPosition.line === currentSelectionRange.endPosition.line) {
this._currentSession[this._currentSession.length - 1] = {
Selection: {
fs_file_path: filePath,
range: currentSelectionRange,
}
};
return;
}
}
this._currentSession.push({
Selection: {
fs_file_path: filePath,
range: currentSelectionRange,
}
});
console.log('selectionLenght', selections.length);
}
selectionToSidecarRange(selection: vscode.Selection): SidecarRequestRange {
if (selection.isReversed) {
return {
startPosition: {
line: selection.active.line,
character: selection.active.character,
byteOffset: 0,
},
endPosition: {
line: selection.anchor.line,
character: selection.anchor.character,
byteOffset: 0,
}
};
} else {
return {
startPosition: {
line: selection.anchor.line,
character: selection.anchor.character,
byteOffset: 0,
},
endPosition: {
line: selection.active.line,
character: selection.active.character,
byteOffset: 0,
}
};
}
}
dispose(): void {
this._disposable.dispose();
}
}