Path: blob/main/extensions/copilot/src/platform/multiFileEdit/common/editLogService.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Raw } from '@vscode/prompt-tsx';6import type { Uri } from 'vscode';7import { createServiceIdentifier } from '../../../util/common/services';8import { VSBuffer } from '../../../util/vs/base/common/buffer';9import { URI } from '../../../util/vs/base/common/uri';10import { ConfigKey, IConfigurationService } from '../../configuration/common/configurationService';11import { IVSCodeExtensionContext } from '../../extContext/common/extensionContext';12import { IFileSystemService } from '../../filesystem/common/fileSystemService';13import { ILogService } from '../../log/common/logService';1415export const IEditLogService = createServiceIdentifier<IEditLogService>('IEditLogService');16export interface IEditLogService {17_serviceBrand: undefined;1819/**20* Logs a chat request made during an edit session.21* @param turnId - The unique identifier for the turn.22* @param prompt - The chat messages that were part of the request.23* @param response - The response generated for the chat request.24*/25logEditChatRequest(turnId: string, prompt: ReadonlyArray<Raw.ChatMessage>, response: string): void;2627/**28* Logs a speculation request made during an edit session.29* @param turnId - The unique identifier for the turn.30* @param uri - The URI of the file being edited.31* @param prompt - The prompt provided for the speculation request.32* @param originalContent - The original content of the file before the edit.33* @param editedContent - The content of the file after the edit.34*/35logSpeculationRequest(turnId: string, uri: Uri, prompt: string, originalContent: string, editedContent: string): void;3637/**38* Marks a turn as completed with the given outcome.39* @param turnId - The unique identifier for the turn.40* @param outcome - The outcome of the turn, either 'success' or 'error'.41* @returns A promise that resolves when the operation is complete.42*/43markCompleted(turnId: string, outcome: 'success' | 'error'): Promise<void>;4445/**46* Retrieves the edit log for a given turn.47* @param turnId - The unique identifier for the turn.48* @returns A promise that resolves to the edit log entries, or undefined if not found.49*/50getEditLog(turnId: string): Promise<{ prompt: string; response: string }[] | undefined>;51}5253interface IEditLogEntry {54prompt: ReadonlyArray<Raw.ChatMessage>;55response: string;56edits: {57uri: string;58prompt: string;59originalContent: string;60editedContent: string;61}[];62}6364export class EditLogService implements IEditLogService {65declare readonly _serviceBrand: undefined;6667public readonly LOG_DIR = URI.joinPath(this._vscodeExtensionContext.globalStorageUri, 'editRecordings');6869private readonly _edits = new Map<string, IEditLogEntry>();7071constructor(72@IVSCodeExtensionContext private readonly _vscodeExtensionContext: IVSCodeExtensionContext,73@IFileSystemService private readonly _fileSystemService: IFileSystemService,74@IConfigurationService private readonly _configurationService: IConfigurationService,75@ILogService private readonly _logService: ILogService,76) { }7778private _isEnabled() {79return this._configurationService.getConfig(ConfigKey.Advanced.EditRecordingEnabled);80}8182logEditChatRequest(turnId: string, prompt: ReadonlyArray<Raw.ChatMessage>, response: string): void {83if (!this._isEnabled()) { return; }8485const entry: IEditLogEntry = this._edits.get(turnId) ?? { prompt, response, edits: [] };86entry.prompt = prompt;87entry.response = response;88this._edits.set(turnId, entry);89}9091logSpeculationRequest(turnId: string, uri: Uri, prompt: string, originalContent: string, editedContent: string): void {92if (!this._isEnabled()) { return; }9394const entry: IEditLogEntry = this._edits.get(turnId) ?? { prompt: [], response: '', edits: [] };95entry.edits.push({96uri: uri.toString(),97prompt,98originalContent,99editedContent,100});101this._edits.set(turnId, entry);102}103104async getEditLog(turnId: string): Promise<{ prompt: string; response: string }[] | undefined> {105if (!this._isEnabled()) { return; }106try {107const data = await this._fileSystemService.readFile(URI.joinPath(this.LOG_DIR, `${turnId}.json`));108const log = JSON.parse(data.toString()) as IEditLogEntry;109return log.edits.map((edit) => ({ prompt: edit.prompt, response: edit.editedContent }));110} catch { }111}112113async markCompleted(turnId: string, outcome: 'success' | 'error') {114if (!this._isEnabled()) { return; }115116const edit = this._edits.get(turnId);117if (!edit) {118// No edit happened in this turn119return;120}121if (edit.edits.length) {122const path = URI.joinPath(this.LOG_DIR, `${turnId}.json`);123this._logService.debug(`Edit recording: ${path.toString()}`);124await this._fileSystemService.writeFile(path, VSBuffer.fromString(JSON.stringify(edit, undefined, 4)).buffer);125}126this._edits.delete(turnId);127}128}129130131