Path: blob/main/extensions/copilot/src/platform/inlineEdits/common/inlineEditLogContext.ts
13400 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 { InlineCompletionContext } from 'vscode';7import * as yaml from 'yaml';8import { ErrorUtils } from '../../../util/common/errors';9import { isCancellationError } from '../../../util/vs/base/common/errors';10import { Emitter, Event } from '../../../util/vs/base/common/event';11import { ThemeIcon } from '../../../util/vs/base/common/themables';12import { SerializedLineEdit } from '../../../util/vs/editor/common/core/edits/lineEdit';13import { SerializedEdit } from './dataTypes/editUtils';14import { FetchCancellationError } from './dataTypes/fetchCancellationError';15import { LanguageContextResponse, SerializedContextResponse, serializeLanguageContext } from './dataTypes/languageContext';16import { RootedLineEdit } from './dataTypes/rootedLineEdit';17import { DebugRecorderBookmark } from './debugRecorderBookmark';18import { ISerializedNextEditRequest, StatelessNextEditRequest } from './statelessNextEditProvider';19import { stringifyChatMessages } from './utils/stringifyChatMessages';20import { Icon, now } from './utils/utils';21import { HistoryContext } from './workspaceEditTracker/historyContextProvider';2223export interface MarkdownLoggable {24toMarkdown(): string;25}2627/**28* The outcome of a log context request. Determines the icon shown in the log tree.29* - `pending`: no outcome yet (shows spinner or check depending on completion)30* - `succeeded`: model returned suggestions31* - `noSuggestions`: model returned no suggestions32* - `cached`: result is from NES cache33* - `cachedFromGhostText`: result is from ghost text cache34* - `skipped`: request was skipped or fetch-cancelled35* - `cancelled`: request was cancelled via CancellationToken (shown as skipped)36* - `errored`: an error occurred37* - `previouslyRejected`: result matches a suggestion that was previously rejected38*/39type LogContextOutcome = 'pending' | 'succeeded' | 'noSuggestions' | 'cached' | 'cachedFromGhostText' | 'reusedInFlight' | 'skipped' | 'cancelled' | 'errored' | 'previouslyRejected';4041export class InlineEditRequestLogContext {4243private static _id = 0;4445public readonly requestId = InlineEditRequestLogContext._id++;4647public readonly time = now();4849/** Tweaks visibility of this log element in the log tree */50protected _isVisible: boolean = false;5152get includeInLogTree(): boolean {53return this._isVisible;54}5556private _isCompleted: boolean = false;5758/** Mark this request as completed (no longer in progress). */59markCompleted(): void {60if (this._isCompleted) {61console.warn(`[InlineEditRequestLogContext] markCompleted called twice (request #${this.requestId})`);62}63this._isCompleted = true;64this.fireDidChange();65}6667private readonly _onDidChange = new Emitter<void>();68/** Fires when state changes, allowing live log entries to refresh their content. */69public readonly onDidChange: Event<void> = this._onDidChange.event;7071protected fireDidChange(): void {72this._onDidChange.fire();73}7475constructor(76public readonly filePath: string,77public readonly version: number,78private _context: InlineCompletionContext | undefined,79) { }8081public recordingBookmark: DebugRecorderBookmark | undefined = undefined;8283toLogDocument(): string {84const lines: string[] = [];85lines.push('# ' + this.getMarkdownTitle() + ` (Request #${this.requestId})`);8687if (!this._isCompleted) {88lines.push('\n⏳ **In progress…**\n');89}9091lines.push('💡 Tip: double-click anywhere to open this file as text to copy-paste content into an issue.\n');9293lines.push('<details><summary>Explanation for icons</summary>\n');94lines.push(`- ${Icon.lightbulbFull.svg} - model had suggestions\n`);95lines.push(`- ${Icon.circleSlash.svg} - model had NO suggestions\n`);96lines.push(`- ${Icon.database.svg} - response is from cache\n`);97lines.push(`- ${Icon.gitMerge.svg} - joined an in-flight request (async or speculative reuse)\n`);98lines.push(`- ${Icon.error.svg} - error happened\n`);99lines.push(`- ${Icon.skipped.svg} - fetching started but got cancelled\n`);100lines.push('</details>\n');101102lines.push(`Inline Edit Provider: ${this._statelessNextEditProviderId ?? '<NOT-SET>'}\n`);103104lines.push(`Chat Endpoint`);105lines.push('```');106lines.push(`Model name: ${this._endpointInfo?.modelName ?? '<NOT-SET>'}`);107lines.push(`URL: ${this._endpointInfo?.url ?? '<NOT-SET>'}`);108lines.push('```');109110const fromCacheStatus = this._logContextOfCachedEdit ? `(cached #${this._logContextOfCachedEdit.requestId})` : '(not cached)';111112lines.push(`Opportunity ID: ${this._context ? this._context.requestUuid : '<NOT-SET>'}`);113if (this.headerRequestId) {114lines.push('');115lines.push(`Header Request ID: ${this.headerRequestId} ${fromCacheStatus}`);116}117118if (this._nextEditRequest) {119lines.push(`## Latest user edits ${fromCacheStatus}`);120lines.push('<details open><summary>Edit</summary>\n');121lines.push(this._nextEditRequest.toMarkdown());122lines.push('\n</details>\n');123}124125if (this._diagnosticsResultEdit) {126lines.push(`## Proposed diagnostics suggestion ${this._nesTypePicked === 'diagnostics' ? '(Picked)' : '(Not Picked)'}`);127lines.push('<details open><summary>Edit</summary>\n');128lines.push('``` patch');129lines.push(this._diagnosticsResultEdit.toString());130lines.push('```');131lines.push('\n</details>\n');132}133134if (this._resultEdit) {135lines.push(`## Proposed inline suggestion ${fromCacheStatus}`);136lines.push('<details open><summary>Edit</summary>\n');137lines.push('``` patch');138lines.push(this._resultEdit.toString());139lines.push('```');140lines.push('\n</details>\n');141}142143if (this.prompt) {144lines.push(`## Prompt ${fromCacheStatus}`);145lines.push('<details><summary>Click to view</summary>\n');146const e = this.prompt;147lines.push('````');148lines.push(...e.split('\n'));149lines.push('````');150lines.push('\n</details>\n');151}152153if (this.error) {154lines.push(`## Error ${fromCacheStatus}`);155lines.push('```');156lines.push(ErrorUtils.toString(ErrorUtils.fromUnknown(this.error)));157lines.push('```');158}159160if (this.response) {161lines.push(`## Response ${fromCacheStatus}`);162lines.push('<details><summary>Click to view</summary>\n');163lines.push('````');164lines.push(this.response);165lines.push('````');166lines.push('\n</details>\n');167}168169if (this._responseResults) {170lines.push(`## Response Results ${fromCacheStatus}`);171lines.push('<details><summary>Click to view</summary>\n');172lines.push('```');173lines.push(yaml.stringify(this._responseResults, null, '\t'));174lines.push('```');175lines.push('\n</details>\n');176}177178if (this._isAccepted !== undefined) {179lines.push(`## Accepted : ${this._isAccepted ? 'Yes' : 'No'}`);180}181182if (this._rebaseFailure) {183lines.push('## Rebase Failure');184lines.push('<details><summary>Click to view</summary>\n');185lines.push(this._rebaseFailure.toMarkdown());186lines.push('\n</details>\n');187}188189if (this._logs.length > 0) {190lines.push('## Logs');191lines.push('<details open><summary>Logs</summary>\n');192lines.push(...this._logs);193lines.push('\n</details>\n');194}195196lines.push(...this._renderTraceDiagram());197198if (this._trace.length > 0) {199lines.push('## Trace');200lines.push('<details><summary>Trace</summary>\n');201lines.push('```');202lines.push(...this._trace);203lines.push('```');204lines.push('\n</details>\n');205}206207return lines.join('\n');208}209210toMinimalLog(): string {211// Does not include the users files, but just the relevant edits212const lines: string[] = [];213214if (this._nesTypePicked === 'diagnostics' && this._diagnosticsResultEdit) {215lines.push(`## Result (Diagnostics):`);216lines.push('``` patch');217lines.push(this._diagnosticsResultEdit.toString());218lines.push('```');219} else if (this._nesTypePicked === 'llm' && this._resultEdit) {220lines.push(`## Result:`);221lines.push('``` patch');222if (typeof this._resultEdit === 'string') {223lines.push(this._resultEdit);224} else {225lines.push(this._resultEdit.toString());226}227lines.push('```');228} else {229lines.push(`## Result: <NOT-SET>`);230}231232if (this.error) {233lines.push(`## Error:`);234lines.push('```');235lines.push(ErrorUtils.toString(ErrorUtils.fromUnknown(this.error)));236lines.push('```');237}238239lines.push(`### Info:`);240lines.push(`**From cache:** ${this._logContextOfCachedEdit ? `YES (Request: ${this._logContextOfCachedEdit.requestId})` : 'NO'}`);241if (this._context) {242lines.push(`**Trigger Kind:** ${this._context.triggerKind === 0 ? 'Manual' : 'Automatic'}`);243lines.push(`**Request UUID:** ${this._context.requestUuid}`);244}245246return lines.join('\n');247}248249private _statelessNextEditProviderId: string | undefined = undefined;250251setStatelessNextEditProviderId(id: string) {252this._statelessNextEditProviderId = id;253}254255private _nextEditRequest: StatelessNextEditRequest | undefined = undefined;256257setRequestInput(nextEditRequest: StatelessNextEditRequest): void {258this._isVisible = true;259this._nextEditRequest = nextEditRequest;260this.fireDidChange();261}262263private _resultEdit: RootedLineEdit | string | undefined = undefined;264265setResult(resultEditOrPatchString: RootedLineEdit | string) {266this._isVisible = true;267this._resultEdit = resultEditOrPatchString;268this.fireDidChange();269}270271protected _diagnosticsResultEdit: RootedLineEdit | undefined = undefined;272273setDiagnosticsResult(resultEdit: RootedLineEdit) {274this._isVisible = true;275this._diagnosticsResultEdit = resultEdit;276this.fireDidChange();277}278279private _nesTypePicked: 'llm' | 'diagnostics' | undefined;280281public setPickedNESType(nesTypePicked: 'llm' | 'diagnostics'): this {282this._nesTypePicked = nesTypePicked;283return this;284}285286private _logContextOfCachedEdit: InlineEditRequestLogContext | undefined = undefined;287288setIsCachedResult(logContextOfCachedEdit: InlineEditRequestLogContext): void {289this._logContextOfCachedEdit = logContextOfCachedEdit;290291// Direct field copy — avoids triggering outcome transitions from the292// public setters (e.g. setResponseResults -> succeeded, setError -> errored).293// The final outcome is always 'cached'.294this.recordingBookmark = logContextOfCachedEdit.recordingBookmark;295this._nextEditRequest = logContextOfCachedEdit._nextEditRequest ?? this._nextEditRequest;296this._resultEdit = logContextOfCachedEdit._resultEdit ?? this._resultEdit;297this._diagnosticsResultEdit = logContextOfCachedEdit._diagnosticsResultEdit ?? this._diagnosticsResultEdit;298this._endpointInfo = logContextOfCachedEdit._endpointInfo ?? this._endpointInfo;299this._headerRequestId = logContextOfCachedEdit._headerRequestId ?? this._headerRequestId;300if (logContextOfCachedEdit._prompt) {301this._prompt = logContextOfCachedEdit._prompt;302}303this.response = logContextOfCachedEdit.response ?? this.response;304this._responseResults = logContextOfCachedEdit._responseResults ?? this._responseResults;305if (logContextOfCachedEdit.fullResponsePromise) {306this.setFullResponse(logContextOfCachedEdit.fullResponsePromise);307}308this._error = logContextOfCachedEdit._error ?? this._error;309310this._isVisible = true;311this._outcome = 'cached';312this.fireDidChange();313}314315/**316* Marks this log context as having joined an already in-flight request317* (async pending or speculative). The icon shows git-merge to distinguish318* from a true cache hit.319*/320setIsReusedInFlightResult(logContextOfReusedRequest: InlineEditRequestLogContext): void {321this._logContextOfCachedEdit = logContextOfReusedRequest;322323this.recordingBookmark = logContextOfReusedRequest.recordingBookmark;324this._nextEditRequest = logContextOfReusedRequest._nextEditRequest ?? this._nextEditRequest;325this._resultEdit = logContextOfReusedRequest._resultEdit ?? this._resultEdit;326this._diagnosticsResultEdit = logContextOfReusedRequest._diagnosticsResultEdit ?? this._diagnosticsResultEdit;327this._endpointInfo = logContextOfReusedRequest._endpointInfo ?? this._endpointInfo;328this._headerRequestId = logContextOfReusedRequest._headerRequestId ?? this._headerRequestId;329if (logContextOfReusedRequest._prompt) {330this._prompt = logContextOfReusedRequest._prompt;331}332this.response = logContextOfReusedRequest.response ?? this.response;333this._responseResults = logContextOfReusedRequest._responseResults ?? this._responseResults;334if (logContextOfReusedRequest.fullResponsePromise) {335this.setFullResponse(logContextOfReusedRequest.fullResponsePromise);336}337this._error = logContextOfReusedRequest._error ?? this._error;338339this._isVisible = true;340this._outcome = 'reusedInFlight';341this.fireDidChange();342}343344private _endpointInfo: { url: string; modelName: string } | undefined;345346public setEndpointInfo(url: string, modelName: string): void {347this._endpointInfo = { url, modelName };348this.fireDidChange();349}350351public get endpointInfo(): { url: string; modelName: string } | undefined {352return this._endpointInfo;353}354355private _headerRequestId: string | undefined = undefined;356public setHeaderRequestId(headerRequestId: string): void {357this._headerRequestId = headerRequestId;358this.fireDidChange();359}360get headerRequestId(): string | undefined {361return this._headerRequestId;362}363364public _prompt: string | undefined = undefined;365private _rawMessages: Raw.ChatMessage[] | undefined = undefined;366367get prompt(): string | undefined {368return this._prompt;369}370371get rawMessages(): Raw.ChatMessage[] | undefined {372return this._rawMessages;373}374375setPrompt(prompt: string | Raw.ChatMessage[]) {376this._isVisible = true;377if (typeof prompt === 'string') {378this._prompt = prompt;379} else {380this._rawMessages = prompt;381this._prompt = stringifyChatMessages(prompt);382}383this.fireDidChange();384}385386private _outcome: LogContextOutcome = 'pending';387388/**389* Sets the outcome, warning if already set (i.e., not `pending`).390* Use direct `this._outcome = ...` assignment to bypass the guard391* (e.g., in `setIsCachedResult` which intentionally overrides any inherited outcome).392*/393private _setOutcome(outcome: LogContextOutcome): void {394// 'reusedInFlight' is an intermediate state set when joining an in-flight395// request (before the result arrives), so it can legitimately transition396// to the final outcome (skipped, errored, etc.) just like 'pending'.397if (this._outcome !== 'pending' && this._outcome !== 'reusedInFlight') {398console.warn(`[InlineEditRequestLogContext] outcome transition from '${this._outcome}' to '${outcome}' (request #${this.requestId})`);399}400this._outcome = outcome;401}402403private _resolveIcon(): Icon.t {404switch (this._outcome) {405case 'pending': return this._isCompleted ? Icon.check : Icon.loading;406case 'succeeded': return Icon.lightbulbFull;407case 'noSuggestions': return Icon.circleSlash;408case 'cached':409case 'cachedFromGhostText': return Icon.database;410case 'reusedInFlight': return Icon.gitMerge;411case 'skipped':412case 'cancelled': return Icon.skipped;413case 'errored': return Icon.error;414case 'previouslyRejected': return Icon.thumbsdown;415}416}417418getIcon(): ThemeIcon {419return this._resolveIcon().themeIcon;420}421422public setIsSkipped() {423this._setOutcome('skipped');424this._isVisible = false;425this.fireDidChange();426}427428public markAsFromCache() {429this._setOutcome('cachedFromGhostText');430this._isVisible = true;431this.fireDidChange();432}433434public markAsNoSuggestions() {435this._setOutcome('noSuggestions');436this._isVisible = true;437this.fireDidChange();438}439440public markAsPreviouslyRejected() {441// Direct assignment — bypasses _setOutcome guard because this transition442// legitimately overrides 'succeeded' when a fetched edit turns out to be rejected.443this._outcome = 'previouslyRejected';444this._isVisible = true;445this.fireDidChange();446}447448private _error: unknown | undefined = undefined;449450get error(): unknown | undefined {451return this._error;452}453454setError(e: unknown): void {455this._isVisible = true;456this._error = e;457458if (this._error instanceof FetchCancellationError) {459this._setOutcome('skipped');460} else if (isCancellationError(this._error)) {461this._setOutcome('cancelled');462this._isVisible = false;463} else {464this._setOutcome('errored');465}466this.fireDidChange();467}468469/**470* Model Response471*/472private response: string | undefined = undefined;473setResponse(v: string): void {474this._isVisible = true;475this.response = v;476this.fireDidChange();477}478479private fullResponsePromise: Promise<string | undefined> | undefined = undefined;480private fullResponse: string | undefined = undefined;481setFullResponse(promise: Promise<string | undefined>): void {482this.fullResponsePromise = promise;483promise.then(response => this.fullResponse = response);484}485486async allPromisesResolved(): Promise<void> {487await this.fullResponsePromise;488}489490private providerStartTime: number | undefined = undefined;491setProviderStartTime(): void {492this.providerStartTime = Date.now();493this.fireDidChange();494}495496private providerEndTime: number | undefined = undefined;497setProviderEndTime(): void {498this.providerEndTime = Date.now();499this.fireDidChange();500}501502private fetchStartTime: number | undefined = undefined;503setFetchStartTime(): void {504this.fetchStartTime = Date.now();505this.fireDidChange();506}507508private fetchEndTime: number | undefined = undefined;509setFetchEndTime(): void {510this.fetchEndTime = Date.now();511this.fireDidChange();512}513514/**515* Each of edit suggestions from model516*/517private _responseResults: readonly unknown[] | undefined = undefined;518519get responseResults(): readonly unknown[] | undefined {520return this._responseResults;521}522523setResponseResults(v: readonly unknown[]): void {524this._isVisible = true;525this._responseResults = v;526if (this._outcome === 'pending') {527this._outcome = 'succeeded';528}529this.fireDidChange();530}531532getDebugName(): string {533return `NES | ${basename(this.filePath)} (v${this.version})`;534}535536getMarkdownTitle(): string {537const icon = this._resolveIcon();538return `${icon.svg} ` + this.getDebugName();539}540541protected _recentEdit: HistoryContext | undefined = undefined;542543setRecentEdit(edit: HistoryContext): void {544this._recentEdit = edit;545}546547private _trace: string[] = [];548trace(msg: string): void {549this._trace.push(msg);550this.fireDidChange();551}552553private _renderTraceDiagram(): string[] {554if (this._trace.length === 0) {555return [];556}557558const lines: string[] = [];559lines.push('## Trace Diagram');560lines.push('<details open><summary>Trace Diagram</summary>\n');561lines.push('```');562563// Parse trace lines into structured data564const parsedTraces = this._trace.map(line => {565const timeMatch = line.match(/^\[\s*(\d+)ms\]/);566const timestamp = timeMatch ? parseInt(timeMatch[1], 10) : 0;567568// Extract the bracketed path segments and the message569const afterTime = line.replace(/^\[\s*\d+ms\]\s*/, '');570const segments: string[] = [];571let remaining = afterTime;572let bracketMatch;573while ((bracketMatch = remaining.match(/^\[([^\]]+)\]/))) {574segments.push(bracketMatch[1]);575remaining = remaining.slice(bracketMatch[0].length);576}577const message = remaining.trim();578579return { timestamp, segments, message };580});581582if (parsedTraces.length === 0) {583lines.push('(no trace data)');584lines.push('```');585lines.push('\n</details>\n');586return lines;587}588589// Find the maximum timestamp for time width calculation590const maxTime = Math.max(...parsedTraces.map(t => t.timestamp));591const timeWidth = Math.max(6, String(maxTime).length + 3);592593// Build a map of segment paths to track when they start/end594const activeSegments = new Map<string, { startTime: number; depth: number }>();595const segmentLifetimes: { path: string; startTime: number; endTime: number; depth: number; name: string }[] = [];596597parsedTraces.forEach((trace, idx) => {598const currentPath = trace.segments.join('|');599600// Check for segments that are no longer active601for (const [path, info] of activeSegments) {602if (!currentPath.startsWith(path) && currentPath !== path) {603segmentLifetimes.push({604path,605startTime: info.startTime,606endTime: trace.timestamp,607depth: info.depth,608name: path.split('|').pop() || ''609});610activeSegments.delete(path);611}612}613614// Add new segments615let pathSoFar = '';616trace.segments.forEach((segment, depth) => {617pathSoFar = pathSoFar ? `${pathSoFar}|${segment}` : segment;618if (!activeSegments.has(pathSoFar)) {619activeSegments.set(pathSoFar, { startTime: trace.timestamp, depth });620}621});622});623624// Close any remaining active segments625const lastTimestamp = parsedTraces[parsedTraces.length - 1]?.timestamp || 0;626for (const [path, info] of activeSegments) {627segmentLifetimes.push({628path,629startTime: info.startTime,630endTime: lastTimestamp,631depth: info.depth,632name: path.split('|').pop() || ''633});634}635636// Render timeline header637lines.push('');638lines.push('Timeline (nested call hierarchy):');639lines.push('─'.repeat(60));640641// Track what's currently shown at each depth to avoid redundant output642const currentAtDepth: string[] = [];643644for (const trace of parsedTraces) {645const timeStr = `[${String(trace.timestamp).padStart(timeWidth - 3)}ms]`;646const indentUnit = '│ ';647const newBranchUnit = '├── ';648649// Determine which segments are new vs continuing650let indent = '';651let displaySegment = '';652let hasNewSegment = false;653654for (let d = 0; d < trace.segments.length; d++) {655const seg = trace.segments[d];656if (currentAtDepth[d] !== seg) {657// This is a new segment at this depth658hasNewSegment = true;659currentAtDepth[d] = seg;660// Clear deeper levels661currentAtDepth.length = d + 1;662displaySegment = seg;663indent = indentUnit.repeat(d);664break;665}666indent = indentUnit.repeat(d + 1);667}668669if (hasNewSegment) {670// Show the new segment671const prefix = indent + newBranchUnit;672lines.push(`${timeStr} ${prefix}[${displaySegment}]`);673if (trace.message) {674const msgIndent = indentUnit.repeat(trace.segments.length);675lines.push(`${' '.repeat(timeWidth + 1)} ${msgIndent}↳ ${trace.message}`);676}677} else if (trace.message) {678// Just a message at the current depth679const msgIndent = indentUnit.repeat(trace.segments.length);680lines.push(`${timeStr} ${msgIndent}↳ ${trace.message}`);681}682}683684lines.push('─'.repeat(60));685lines.push('```');686lines.push('\n</details>\n');687688return lines;689}690691private _logs: string[] = [];692addLog(content: string): void {693this._logs.push(content.replace('\n', '\\n').replace('\t', '\\t').replace('`', '\`') + '\n');694this.fireDidChange();695}696697private _rebaseFailure: MarkdownLoggable | undefined;698699setRebaseFailure(failure: MarkdownLoggable): void {700this._rebaseFailure = failure;701}702703704private _isAccepted: boolean | undefined = undefined;705setAccepted(isAccepted: boolean): void {706this._isAccepted = isAccepted;707}708709addListToLog(list: string[]): void {710list.forEach(l => this.addLog(`- ${l}`));711}712713addCodeblockToLog(code: string, language: string = ''): void {714this._logs.push(`\`\`\`${language}\n${code}\n\`\`\`\n`);715}716717private _fileDiagnostics: string | undefined;718setDiagnosticsData(fileDiagnostics: string): void {719this._fileDiagnostics = fileDiagnostics;720}721722private _terminalOutput: string | undefined;723setTerminalData(terminalOutput: string): void {724this._terminalOutput = terminalOutput;725}726727private _languageContext: LanguageContextResponse | undefined;728setLanguageContext(langCtx: LanguageContextResponse): void {729this._languageContext = langCtx;730}731732/**733* Convert the current instance into a JSON format to enable serialization734* @returns JSON representation of the current state735*/736toJSON(): ISerializedInlineEditLogContext {737return {738requestId: this.requestId,739time: this.time,740filePath: this.filePath,741version: this.version,742statelessNextEditProviderId: this._statelessNextEditProviderId,743nextEditRequest: this._nextEditRequest?.serialize(),744diagnosticsResultEdit: this._diagnosticsResultEdit?.toString(),745resultEdit: this._resultEdit?.toString(),746isCachedResult: !!this._logContextOfCachedEdit,747prompt: this.prompt,748error: String(this.error),749response: this.fullResponse,750responseResults: yaml.stringify(this._responseResults, null, '\t'),751providerStartTime: this.providerStartTime,752providerEndTime: this.providerEndTime,753fetchStartTime: this.fetchStartTime,754fetchEndTime: this.fetchEndTime,755logs: this._logs,756isAccepted: this._isAccepted,757languageContext: this._languageContext ? serializeLanguageContext(this._languageContext) : undefined,758diagnostics: this._fileDiagnostics,759terminalOutput: this._terminalOutput,760};761}762}763764function basename(path: string): string {765const slash = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\'));766if (slash === -1) { return path; }767return path.slice(slash + 1);768}769770export interface INextEditProviderTest {771// from least recent to most recent772recentWorkspaceEdits: { path: string; initialText: string; edit: SerializedEdit }[];773recentWorkspaceEditsActiveDocumentIdx?: number; // by default the last document774statelessDocuments?: { initialText: string; edit: SerializedLineEdit }[];775statelessActiveDocumentIdx?: number; // by default the last document776statelessLLMPrompt?: string;777statelessLLMResponse?: string;778statelessNextEdit?: SerializedLineEdit;779780nextEdit?: SerializedEdit;781}782783export interface ISerializedInlineEditLogContext {784requestId: number;785time: number;786filePath: string;787version: number;788statelessNextEditProviderId: string | undefined;789nextEditRequest: ISerializedNextEditRequest | undefined;790diagnosticsResultEdit: string | undefined;791resultEdit: string | undefined;792isCachedResult: boolean;793prompt: string | undefined;794error: string;795response: string | undefined;796responseResults: string;797providerStartTime: number | undefined;798providerEndTime: number | undefined;799fetchStartTime: number | undefined;800fetchEndTime: number | undefined;801logs: string[];802isAccepted: boolean | undefined;803languageContext: SerializedContextResponse | undefined;804diagnostics: string | undefined;805terminalOutput: string | undefined;806}807808809