Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/test/pipeline/replayRecording.ts
13389 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 { IRecordingInformation, ObservableWorkspaceRecordingReplayer } from '../../src/extension/inlineEdits/common/observableWorkspaceRecordingReplayer';
7
import { DocumentId } from '../../src/platform/inlineEdits/common/dataTypes/documentId';
8
import { IObservableDocument, MutableObservableWorkspace } from '../../src/platform/inlineEdits/common/observableWorkspace';
9
import { coalesce } from '../../src/util/vs/base/common/arrays';
10
import { Processor } from './alternativeAction/processor';
11
import { IInputRow } from './parseInput';
12
13
/**
14
* Result of processing a single input row: replayed workspace + oracle edit.
15
*/
16
export interface IProcessedRow {
17
readonly originalRowIndex: number;
18
readonly row: IInputRow;
19
readonly replayer: ObservableWorkspaceRecordingReplayer;
20
readonly workspace: MutableObservableWorkspace;
21
readonly activeDocId: DocumentId;
22
readonly activeDocument: IObservableDocument;
23
readonly activeFilePath: string;
24
/** What the user actually typed next (from post-request recording). */
25
readonly nextUserEdit: {
26
readonly edit: readonly (readonly [start: number, endEx: number, text: string])[];
27
readonly relativePath: string;
28
readonly originalOpIdx: number;
29
};
30
readonly recordingInfo: IRecordingInformation;
31
}
32
33
/**
34
* Parse a suggestedEdit string like `[978, 1021) -> "foo"` into `[start, endEx, text]`.
35
* The text portion is JSON-encoded (from `JSON.stringify`), so we parse it back.
36
*/
37
export function parseSuggestedEdit(suggestedEditStr: string): [start: number, endEx: number, text: string] | null {
38
const separator = ' -> ';
39
const delimiterIdx = suggestedEditStr.indexOf(separator);
40
if (delimiterIdx === -1) {
41
return null;
42
}
43
const stringifiedRange = suggestedEditStr.substring(0, delimiterIdx);
44
const quotedText = suggestedEditStr.substring(delimiterIdx + separator.length);
45
const match = stringifiedRange.match(/^\[(\d+), (\d+)\)$/);
46
if (!match || !quotedText) {
47
return null;
48
}
49
const start = parseInt(match[1], 10);
50
const endEx = parseInt(match[2], 10);
51
try {
52
const text = JSON.parse(quotedText) as string;
53
return [start, endEx, text];
54
} catch {
55
return null;
56
}
57
}
58
59
function formatError(e: unknown): string {
60
if (e instanceof Error) {
61
if (e.message === 'An unexpected bug occurred.' && e.stack) {
62
const frames = e.stack.split('\n').slice(1, 4).map(f => f.trim()).join(' <- ');
63
return `${e.message} Stack: ${frames}`;
64
}
65
return e.message;
66
}
67
return String(e);
68
}
69
70
/**
71
* Process a single input row: split recording at request time, replay
72
* the pre-request portion and extract the oracle edit.
73
*/
74
export function processRow(row: IInputRow): IProcessedRow | { error: string } {
75
try {
76
return _processRow(row);
77
} catch (e: unknown) {
78
return { error: `Unexpected error: ${formatError(e)}` };
79
}
80
}
81
82
function _processRow(row: IInputRow): IProcessedRow | { error: string } {
83
const proposedEdits = coalesce([parseSuggestedEdit(row.postProcessingOutcome.suggestedEdit)]);
84
const isAccepted = row.suggestionStatus === 'accepted';
85
86
const scoring = Processor.createScoringForAlternativeAction(
87
row.alternativeAction,
88
proposedEdits,
89
isAccepted,
90
);
91
92
if (!scoring) {
93
const entryCount = row.alternativeAction?.recording?.entries?.length ?? 0;
94
return { error: `Processor.createScoringForAlternativeAction returned undefined (${entryCount} entries, lang: ${row.activeDocumentLanguageId})` };
95
}
96
97
const recording = scoring.scoringContext.recording;
98
99
const recordingInfo: IRecordingInformation = {
100
log: recording.log,
101
nextUserEdit: {
102
relativePath: recording.nextUserEdit.relativePath,
103
edit: recording.nextUserEdit.edit,
104
},
105
};
106
107
const replayer = new ObservableWorkspaceRecordingReplayer(recordingInfo);
108
let lastDocId: DocumentId;
109
try {
110
const result = replayer.replay();
111
lastDocId = result.lastDocId;
112
} catch (e) {
113
replayer.dispose();
114
return { error: `Replay failed (${recording.log.length} entries, file: ${recording.nextUserEdit?.relativePath ?? 'unknown'}): ${formatError(e)}` };
115
}
116
117
const workspace = replayer.workspace;
118
const activeDocument = workspace.getDocument(lastDocId);
119
if (!activeDocument) {
120
replayer.dispose();
121
return { error: `Active document not found after replay: ${lastDocId}` };
122
}
123
124
// Prefer scoring edit URI, fall back to oracle path
125
const activeFilePath = scoring.edits[0]?.documentUri ?? recording.nextUserEdit?.relativePath ?? 'unknown';
126
127
return {
128
originalRowIndex: row.originalRowIndex,
129
row,
130
replayer,
131
workspace,
132
activeDocId: lastDocId,
133
activeDocument,
134
activeFilePath,
135
nextUserEdit: recording.nextUserEdit,
136
recordingInfo,
137
};
138
}
139
140
/**
141
* Process all input rows.
142
* Each returned `IProcessedRow` holds a live replayer that must be disposed by the caller.
143
*/
144
export function processAllRows(rows: readonly IInputRow[]): {
145
processed: IProcessedRow[];
146
errors: { rowIndex: number; error: string }[];
147
} {
148
const processed: IProcessedRow[] = [];
149
const errors: { rowIndex: number; error: string }[] = [];
150
151
for (let i = 0; i < rows.length; i++) {
152
const result = processRow(rows[i]);
153
if ('error' in result) {
154
errors.push({ rowIndex: i, error: result.error });
155
} else {
156
processed.push(result);
157
}
158
}
159
160
return { processed, errors };
161
}
162
163