Path: blob/main/extensions/copilot/script/alternativeAction/index.ts
13389 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 csvParse from 'csv-parse';6import * as fs from 'fs/promises';7import minimist from 'minimist';8import { IAlternativeAction } from '../../src/extension/inlineEdits/node/nextEditProviderTelemetry';9import { coalesce } from '../../src/util/vs/base/common/arrays';10import { Processor } from '../../test/pipeline/alternativeAction/processor';11import { IData, Scoring } from '../../test/pipeline/alternativeAction/types';12import { Either, log } from '../../test/pipeline/alternativeAction/util';1314async function extractFromCsv(csvContents: string): Promise<(Scoring.t | undefined)[]> {15const options = {16columns: true as const, // Use first row as column headers17delimiter: ',', // Comma delimiter18quote: '"', // Double quotes19escape: '"', // Standard CSV escape character20skip_empty_lines: true, // Skip any empty rows21trim: true, // Remove whitespace around fields22relax_quotes: true, // Handle quotes within fields more flexibly23bom: true, // Handle UTF-8 BOM24cast: false // Keep all values as strings initially25} as const;2627type CsvRecord = { Data: string };2829const objects = (await new Promise<CsvRecord[]>((resolve, reject) =>30csvParse.parse<CsvRecord>(csvContents, options, (err, result) => {31if (err) {32reject(err);33} else {34if (result.every((item: any) => typeof item === 'object' && 'Data' in item && typeof item['Data'] === 'string')) {35resolve(result);36} else {37reject(new Error('Invalid CSV format'));38}39}40})41)).map(record => JSON.parse(record.Data) as IData);4243const scoredEdits = objects.map((obj: IData) => {44const altAction: IAlternativeAction = obj.altAction;45if (!altAction || !altAction.recording) {46return undefined;47}48return Processor.createScoringForAlternativeAction(altAction, coalesce([parseSuggestedEdit(obj.postProcessingOutcome.suggestedEdit)]), false);49});5051return scoredEdits;52}5354function writeFiles(basename: string, scoring: Scoring.t) {55return [56fs.writeFile(`${basename}.scoredEdits.w.json`, JSON.stringify(scoring, null, 2)),57fs.writeFile(`${basename}.recording.w.json`, JSON.stringify(scoring.scoringContext.recording, null, 2)),58];59}6061async function handleCsv(inputFilePath: string) {62log('Handling CSV file:', inputFilePath);63const csvContents = await fs.readFile(inputFilePath, 'utf8');64log('CSV contents read, length:', csvContents.length);65const extracted = await extractFromCsv(csvContents);66log('Extraction complete, number of scored edits:', extracted.filter(e => e).length);67try {68await Promise.all(extracted.flatMap((obj: Scoring.t | undefined, idx: number) => {69if (!obj) {70return [];71}72return writeFiles(idx.toString(), obj);73}));74log('All files written successfully');75} catch (e) {76log('Error writing files:', e);77}78}7980function parseFile(fileContents: string): Either<IData, IAlternativeAction> | undefined {81let parsedObj: unknown;82try {83parsedObj = JSON.parse(fileContents);84} catch (e) {85console.error('Failed to parse JSON:', e);86return undefined;87}88if (parsedObj && typeof parsedObj === 'object' && 'prompt' in parsedObj) {89return Either.left(parsedObj as IData);90}9192return Either.right(parsedObj as IAlternativeAction);93}9495async function handleAlternativeActionJson(inputFilePath: string) {96log('Handling alternative action JSON file:', inputFilePath);97const fileContents = await fs.readFile(inputFilePath, 'utf8');98log('File contents read, length:', fileContents.length);99const obj = parseFile(fileContents);100if (!obj) {101console.error('Failed to parse alternative action JSON file');102return;103}104const altAction = obj.isLeft() ? obj.value.altAction : obj.value;105const edits: [start: number, endEx: number, text: string][] = [];106let isAccepted = false;107if (obj.isLeft()) {108const data = obj.value;109const parsedEdit = parseSuggestedEdit(data.postProcessingOutcome.suggestedEdit);110if (parsedEdit) {111edits.push(parsedEdit);112}113isAccepted = data.suggestionStatus === 'accepted';114}115const scoring = Processor.createScoringForAlternativeAction(altAction, edits, isAccepted);116if (!scoring) {117console.error('Failed to create scoring from alternative action');118return;119}120const outputFilePath = inputFilePath.replace(/\.json$/, '.scoredEdits.json');121await Promise.all(writeFiles(outputFilePath.replace(/\.scoredEdits\.json$/, ''), scoring));122log('Scoring written to:', outputFilePath);123}124125function parseSuggestedEdit(suggestedEditStr: string): [number, number, string] | null {126const [stringifiedRange, quotedText] = suggestedEditStr.split(' -> ');127const match = stringifiedRange.match(/^\[(\d+), (\d+)\)$/);128if (match) {129const start = parseInt(match[1], 10);130const endEx = parseInt(match[2], 10);131const text = quotedText.slice(1, -1); // Remove surrounding quotes132return [start, endEx, text];133}134return null;135}136137async function main() {138const argv = minimist(process.argv.slice(2), {139alias: {140p: 'path',141s: 'single',142c: 'csv'143},144boolean: ['single', 'csv'],145string: ['path']146});147148if (!argv.path) {149console.error('Please provide a path to an alternative action JSON file using --path or -p');150process.exit(1);151}152153const inputFilePath = argv.path;154155if (argv.csv) {156await handleCsv(inputFilePath);157return;158}159160await handleAlternativeActionJson(inputFilePath);161return;162}163164main();165166167