Fix for git issue 5657 to add lines of code added/removed telemetry (#5823)

Co-authored-by: Ravikant Agarwal <ravikantag@google.com>
This commit is contained in:
agarwalravikant
2025-08-08 10:08:07 +05:30
committed by GitHub
parent 86eaa03f8a
commit 5ab184fcaf
12 changed files with 408 additions and 14 deletions

View File

@@ -0,0 +1,129 @@
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { describe, expect, it } from 'vitest';
import { getDiffStat } from './diffOptions.js';
describe('getDiffStat', () => {
const fileName = 'test.txt';
it('should return 0 for all stats when there are no changes', () => {
const oldStr = 'line1\nline2\n';
const aiStr = 'line1\nline2\n';
const userStr = 'line1\nline2\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 0,
ai_removed_lines: 0,
user_added_lines: 0,
user_removed_lines: 0,
});
});
it('should correctly report AI additions', () => {
const oldStr = 'line1\nline2\n';
const aiStr = 'line1\nline2\nline3\n';
const userStr = 'line1\nline2\nline3\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 1,
ai_removed_lines: 0,
user_added_lines: 0,
user_removed_lines: 0,
});
});
it('should correctly report AI removals', () => {
const oldStr = 'line1\nline2\nline3\n';
const aiStr = 'line1\nline3\n';
const userStr = 'line1\nline3\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 0,
ai_removed_lines: 1,
user_added_lines: 0,
user_removed_lines: 0,
});
});
it('should correctly report AI modifications', () => {
const oldStr = 'line1\nline2\nline3\n';
const aiStr = 'line1\nline_two\nline3\n';
const userStr = 'line1\nline_two\nline3\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 1,
ai_removed_lines: 1,
user_added_lines: 0,
user_removed_lines: 0,
});
});
it('should correctly report user additions', () => {
const oldStr = 'line1\nline2\n';
const aiStr = 'line1\nline2\nline3\n';
const userStr = 'line1\nline2\nline3\nline4\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 1,
ai_removed_lines: 0,
user_added_lines: 1,
user_removed_lines: 0,
});
});
it('should correctly report user removals', () => {
const oldStr = 'line1\nline2\n';
const aiStr = 'line1\nline2\nline3\n';
const userStr = 'line1\nline2\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 1,
ai_removed_lines: 0,
user_added_lines: 0,
user_removed_lines: 1,
});
});
it('should correctly report user modifications', () => {
const oldStr = 'line1\nline2\n';
const aiStr = 'line1\nline2\nline3\n';
const userStr = 'line1\nline2\nline_three\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 1,
ai_removed_lines: 0,
user_added_lines: 1,
user_removed_lines: 1,
});
});
it('should handle complex changes from both AI and user', () => {
const oldStr = 'line1\nline2\nline3\nline4\n';
const aiStr = 'line_one\nline2\nline_three\nline4\n';
const userStr = 'line_one\nline_two\nline_three\nline4\nline5\n';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 2,
ai_removed_lines: 2,
user_added_lines: 2,
user_removed_lines: 1,
});
});
it('should report a single line modification as one addition and one removal', () => {
const oldStr = 'hello world';
const aiStr = 'hello universe';
const userStr = 'hello universe';
const diffStat = getDiffStat(fileName, oldStr, aiStr, userStr);
expect(diffStat).toEqual({
ai_added_lines: 1,
ai_removed_lines: 1,
user_added_lines: 0,
user_removed_lines: 0,
});
});
});

View File

@@ -5,8 +5,61 @@
*/
import * as Diff from 'diff';
import { DiffStat } from './tools.js';
export const DEFAULT_DIFF_OPTIONS: Diff.PatchOptions = {
context: 3,
ignoreWhitespace: true,
};
export function getDiffStat(
fileName: string,
oldStr: string,
aiStr: string,
userStr: string,
): DiffStat {
const countLines = (patch: Diff.ParsedDiff) => {
let added = 0;
let removed = 0;
patch.hunks.forEach((hunk: Diff.Hunk) => {
hunk.lines.forEach((line: string) => {
if (line.startsWith('+')) {
added++;
} else if (line.startsWith('-')) {
removed++;
}
});
});
return { added, removed };
};
const patch = Diff.structuredPatch(
fileName,
fileName,
oldStr,
aiStr,
'Current',
'Proposed',
DEFAULT_DIFF_OPTIONS,
);
const { added: aiAddedLines, removed: aiRemovedLines } = countLines(patch);
const userPatch = Diff.structuredPatch(
fileName,
fileName,
aiStr,
userStr,
'Proposed',
'User',
DEFAULT_DIFF_OPTIONS,
);
const { added: userAddedLines, removed: userRemovedLines } =
countLines(userPatch);
return {
ai_added_lines: aiAddedLines,
ai_removed_lines: aiRemovedLines,
user_added_lines: userAddedLines,
user_removed_lines: userRemovedLines,
};
}

View File

@@ -25,7 +25,7 @@ import { makeRelative, shortenPath } from '../utils/paths.js';
import { isNodeError } from '../utils/errors.js';
import { Config, ApprovalMode } from '../config/config.js';
import { ensureCorrectEdit } from '../utils/editCorrector.js';
import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js';
import { DEFAULT_DIFF_OPTIONS, getDiffStat } from './diffOptions.js';
import { ReadFileTool } from './read-file.js';
import { ModifiableDeclarativeTool, ModifyContext } from './modifiable-tool.js';
import { IDEConnectionStatus } from '../ide/ide-client.js';
@@ -79,6 +79,11 @@ export interface EditToolParams {
* Whether the edit was modified manually by the user.
*/
modified_by_user?: boolean;
/**
* Initially proposed string.
*/
ai_proposed_string?: string;
}
interface CalculatedEdit {
@@ -353,11 +358,20 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
'Proposed',
DEFAULT_DIFF_OPTIONS,
);
const originallyProposedContent =
this.params.ai_proposed_string || this.params.new_string;
const diffStat = getDiffStat(
fileName,
editData.currentContent ?? '',
originallyProposedContent,
this.params.new_string,
);
displayResult = {
fileDiff,
fileName,
originalContent: editData.currentContent,
newContent: editData.newContent,
diffStat,
};
}
@@ -513,12 +527,16 @@ Expectation for required parameters:
oldContent: string,
modifiedProposedContent: string,
originalParams: EditToolParams,
): EditToolParams => ({
...originalParams,
old_string: oldContent,
new_string: modifiedProposedContent,
modified_by_user: true,
}),
): EditToolParams => {
const content = originalParams.new_string;
return {
...originalParams,
ai_proposed_string: content,
old_string: oldContent,
new_string: modifiedProposedContent,
modified_by_user: true,
};
},
};
}
}

View File

@@ -498,6 +498,14 @@ export interface FileDiff {
fileName: string;
originalContent: string | null;
newContent: string;
diffStat?: DiffStat;
}
export interface DiffStat {
ai_removed_lines: number;
ai_added_lines: number;
user_added_lines: number;
user_removed_lines: number;
}
export interface ToolEditConfirmationDetails {

View File

@@ -25,7 +25,7 @@ import {
ensureCorrectEdit,
ensureCorrectFileContent,
} from '../utils/editCorrector.js';
import { DEFAULT_DIFF_OPTIONS } from './diffOptions.js';
import { DEFAULT_DIFF_OPTIONS, getDiffStat } from './diffOptions.js';
import { ModifiableDeclarativeTool, ModifyContext } from './modifiable-tool.js';
import { getSpecificMimeType } from '../utils/fileUtils.js';
import {
@@ -52,6 +52,11 @@ export interface WriteFileToolParams {
* Whether the proposed content was modified by the user.
*/
modified_by_user?: boolean;
/**
* Initially proposed content.
*/
ai_proposed_content?: string;
}
interface GetCorrectedFileContentResult {
@@ -283,6 +288,15 @@ export class WriteFileTool
DEFAULT_DIFF_OPTIONS,
);
const originallyProposedContent =
params.ai_proposed_content || params.content;
const diffStat = getDiffStat(
fileName,
currentContentForDiff,
originallyProposedContent,
params.content,
);
const llmSuccessMessageParts = [
isNewFile
? `Successfully created and wrote to new file: ${params.file_path}.`
@@ -299,6 +313,7 @@ export class WriteFileTool
fileName,
originalContent: correctedContentResult.originalContent,
newContent: correctedContentResult.correctedContent,
diffStat,
};
const lines = fileContent.split('\n').length;
@@ -311,6 +326,7 @@ export class WriteFileTool
lines,
mimetype,
extension,
diffStat,
);
} else {
recordFileOperationMetric(
@@ -319,6 +335,7 @@ export class WriteFileTool
lines,
mimetype,
extension,
diffStat,
);
}
@@ -418,11 +435,15 @@ export class WriteFileTool
_oldContent: string,
modifiedProposedContent: string,
originalParams: WriteFileToolParams,
) => ({
...originalParams,
content: modifiedProposedContent,
modified_by_user: true,
}),
) => {
const content = originalParams.content;
return {
...originalParams,
ai_proposed_content: content,
content: modifiedProposedContent,
modified_by_user: true,
};
},
};
}
}