Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/platform/multiFileEdit/common/editLogService.ts
13401 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 { Raw } from '@vscode/prompt-tsx';
7
import type { Uri } from 'vscode';
8
import { createServiceIdentifier } from '../../../util/common/services';
9
import { VSBuffer } from '../../../util/vs/base/common/buffer';
10
import { URI } from '../../../util/vs/base/common/uri';
11
import { ConfigKey, IConfigurationService } from '../../configuration/common/configurationService';
12
import { IVSCodeExtensionContext } from '../../extContext/common/extensionContext';
13
import { IFileSystemService } from '../../filesystem/common/fileSystemService';
14
import { ILogService } from '../../log/common/logService';
15
16
export const IEditLogService = createServiceIdentifier<IEditLogService>('IEditLogService');
17
export interface IEditLogService {
18
_serviceBrand: undefined;
19
20
/**
21
* Logs a chat request made during an edit session.
22
* @param turnId - The unique identifier for the turn.
23
* @param prompt - The chat messages that were part of the request.
24
* @param response - The response generated for the chat request.
25
*/
26
logEditChatRequest(turnId: string, prompt: ReadonlyArray<Raw.ChatMessage>, response: string): void;
27
28
/**
29
* Logs a speculation request made during an edit session.
30
* @param turnId - The unique identifier for the turn.
31
* @param uri - The URI of the file being edited.
32
* @param prompt - The prompt provided for the speculation request.
33
* @param originalContent - The original content of the file before the edit.
34
* @param editedContent - The content of the file after the edit.
35
*/
36
logSpeculationRequest(turnId: string, uri: Uri, prompt: string, originalContent: string, editedContent: string): void;
37
38
/**
39
* Marks a turn as completed with the given outcome.
40
* @param turnId - The unique identifier for the turn.
41
* @param outcome - The outcome of the turn, either 'success' or 'error'.
42
* @returns A promise that resolves when the operation is complete.
43
*/
44
markCompleted(turnId: string, outcome: 'success' | 'error'): Promise<void>;
45
46
/**
47
* Retrieves the edit log for a given turn.
48
* @param turnId - The unique identifier for the turn.
49
* @returns A promise that resolves to the edit log entries, or undefined if not found.
50
*/
51
getEditLog(turnId: string): Promise<{ prompt: string; response: string }[] | undefined>;
52
}
53
54
interface IEditLogEntry {
55
prompt: ReadonlyArray<Raw.ChatMessage>;
56
response: string;
57
edits: {
58
uri: string;
59
prompt: string;
60
originalContent: string;
61
editedContent: string;
62
}[];
63
}
64
65
export class EditLogService implements IEditLogService {
66
declare readonly _serviceBrand: undefined;
67
68
public readonly LOG_DIR = URI.joinPath(this._vscodeExtensionContext.globalStorageUri, 'editRecordings');
69
70
private readonly _edits = new Map<string, IEditLogEntry>();
71
72
constructor(
73
@IVSCodeExtensionContext private readonly _vscodeExtensionContext: IVSCodeExtensionContext,
74
@IFileSystemService private readonly _fileSystemService: IFileSystemService,
75
@IConfigurationService private readonly _configurationService: IConfigurationService,
76
@ILogService private readonly _logService: ILogService,
77
) { }
78
79
private _isEnabled() {
80
return this._configurationService.getConfig(ConfigKey.Advanced.EditRecordingEnabled);
81
}
82
83
logEditChatRequest(turnId: string, prompt: ReadonlyArray<Raw.ChatMessage>, response: string): void {
84
if (!this._isEnabled()) { return; }
85
86
const entry: IEditLogEntry = this._edits.get(turnId) ?? { prompt, response, edits: [] };
87
entry.prompt = prompt;
88
entry.response = response;
89
this._edits.set(turnId, entry);
90
}
91
92
logSpeculationRequest(turnId: string, uri: Uri, prompt: string, originalContent: string, editedContent: string): void {
93
if (!this._isEnabled()) { return; }
94
95
const entry: IEditLogEntry = this._edits.get(turnId) ?? { prompt: [], response: '', edits: [] };
96
entry.edits.push({
97
uri: uri.toString(),
98
prompt,
99
originalContent,
100
editedContent,
101
});
102
this._edits.set(turnId, entry);
103
}
104
105
async getEditLog(turnId: string): Promise<{ prompt: string; response: string }[] | undefined> {
106
if (!this._isEnabled()) { return; }
107
try {
108
const data = await this._fileSystemService.readFile(URI.joinPath(this.LOG_DIR, `${turnId}.json`));
109
const log = JSON.parse(data.toString()) as IEditLogEntry;
110
return log.edits.map((edit) => ({ prompt: edit.prompt, response: edit.editedContent }));
111
} catch { }
112
}
113
114
async markCompleted(turnId: string, outcome: 'success' | 'error') {
115
if (!this._isEnabled()) { return; }
116
117
const edit = this._edits.get(turnId);
118
if (!edit) {
119
// No edit happened in this turn
120
return;
121
}
122
if (edit.edits.length) {
123
const path = URI.joinPath(this.LOG_DIR, `${turnId}.json`);
124
this._logService.debug(`Edit recording: ${path.toString()}`);
125
await this._fileSystemService.writeFile(path, VSBuffer.fromString(JSON.stringify(edit, undefined, 4)).buffer);
126
}
127
this._edits.delete(turnId);
128
}
129
}
130
131