mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
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:
129
packages/core/src/tools/diffOptions.test.ts
Normal file
129
packages/core/src/tools/diffOptions.test.ts
Normal 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,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user