mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 08:47:44 +00:00
feat: Add programming language to CLI events (#6071)
Co-authored-by: christine betts <chrstn@uw.edu> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> Co-authored-by: Adam Weidman <65992621+adamfweidman@users.noreply.github.com> Co-authored-by: JaeHo Jang <diehreo@gmail.com> Co-authored-by: Jacob Richman <jacob314@gmail.com> Co-authored-by: Victor May <mayvic@google.com> Co-authored-by: Gaurav <39389231+gsquared94@users.noreply.github.com> Co-authored-by: joshualitt <joshualitt@google.com> Co-authored-by: Billy Biggs <bbiggs@google.com> Co-authored-by: Ricardo Fabbri <rfabbri@gmail.com> Co-authored-by: Arya Gummadi <aryagummadi@google.com> Co-authored-by: Tommaso Sciortino <sciortino@gmail.com> Co-authored-by: Gal Zahavi <38544478+galz10@users.noreply.github.com> Co-authored-by: Shreya Keshive <skeshive@gmail.com> Co-authored-by: Ben Guo <36952867+HunDun0Ben@users.noreply.github.com> Co-authored-by: Ben Guo <hundunben@gmail.com> Co-authored-by: mkusaka <hinoshita1992@gmail.com>
This commit is contained in:
@@ -19,6 +19,7 @@ import {
|
||||
IdeConnectionEvent,
|
||||
KittySequenceOverflowEvent,
|
||||
ChatCompressionEvent,
|
||||
FileOperationEvent,
|
||||
} from '../types.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import { Config } from '../../config/config.js';
|
||||
@@ -33,6 +34,7 @@ export enum EventNames {
|
||||
START_SESSION = 'start_session',
|
||||
NEW_PROMPT = 'new_prompt',
|
||||
TOOL_CALL = 'tool_call',
|
||||
FILE_OPERATION = 'file_operation',
|
||||
API_REQUEST = 'api_request',
|
||||
API_RESPONSE = 'api_response',
|
||||
API_ERROR = 'api_error',
|
||||
@@ -476,6 +478,64 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logFileOperationEvent(event: FileOperationEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_TOOL_CALL_NAME,
|
||||
value: JSON.stringify(event.tool_name),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_TYPE,
|
||||
value: JSON.stringify(event.operation),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_LINES,
|
||||
value: JSON.stringify(event.lines),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_MIMETYPE,
|
||||
value: JSON.stringify(event.mimetype),
|
||||
},
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_FILE_OPERATION_EXTENSION,
|
||||
value: JSON.stringify(event.extension),
|
||||
},
|
||||
];
|
||||
|
||||
if (event.programming_language) {
|
||||
data.push({
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_PROGRAMMING_LANGUAGE,
|
||||
value: event.programming_language,
|
||||
});
|
||||
}
|
||||
|
||||
if (event.diff_stat) {
|
||||
const metadataMapping: { [key: string]: EventMetadataKey } = {
|
||||
ai_added_lines: EventMetadataKey.GEMINI_CLI_AI_ADDED_LINES,
|
||||
ai_removed_lines: EventMetadataKey.GEMINI_CLI_AI_REMOVED_LINES,
|
||||
user_added_lines: EventMetadataKey.GEMINI_CLI_USER_ADDED_LINES,
|
||||
user_removed_lines: EventMetadataKey.GEMINI_CLI_USER_REMOVED_LINES,
|
||||
};
|
||||
|
||||
for (const [key, gemini_cli_key] of Object.entries(metadataMapping)) {
|
||||
if (
|
||||
event.diff_stat[key as keyof typeof event.diff_stat] !== undefined
|
||||
) {
|
||||
data.push({
|
||||
gemini_cli_key,
|
||||
value: JSON.stringify(
|
||||
event.diff_stat[key as keyof typeof event.diff_stat],
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logEvent = this.createLogEvent(EventNames.FILE_OPERATION, data);
|
||||
this.enqueueLogEvent(logEvent);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logApiRequestEvent(event: ApiRequestEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
|
||||
@@ -219,6 +219,9 @@ export enum EventMetadataKey {
|
||||
// Logs user removed lines in edit/write tool response.
|
||||
GEMINI_CLI_USER_REMOVED_LINES = 50,
|
||||
|
||||
// Logs the programming language of the project.
|
||||
GEMINI_CLI_PROGRAMMING_LANGUAGE = 56,
|
||||
|
||||
// ==========================================================================
|
||||
// Kitty Sequence Overflow Event Keys
|
||||
// ===========================================================================
|
||||
@@ -246,4 +249,20 @@ export enum EventMetadataKey {
|
||||
|
||||
// Logs name of MCP tools as comma seperated string
|
||||
GEMINI_CLI_START_SESSION_MCP_TOOLS = 65,
|
||||
|
||||
// ==========================================================================
|
||||
// File Operation Event Keys
|
||||
// ===========================================================================
|
||||
|
||||
// Logs the operation type of the file operation.
|
||||
GEMINI_CLI_FILE_OPERATION_TYPE = 66,
|
||||
|
||||
// Logs the number of lines in the file operation.
|
||||
GEMINI_CLI_FILE_OPERATION_LINES = 67,
|
||||
|
||||
// Logs the mimetype of the file in the file operation.
|
||||
GEMINI_CLI_FILE_OPERATION_MIMETYPE = 68,
|
||||
|
||||
// Logs the extension of the file in the file operation.
|
||||
GEMINI_CLI_FILE_OPERATION_EXTENSION = 69,
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
ApiErrorEvent,
|
||||
ApiRequestEvent,
|
||||
ApiResponseEvent,
|
||||
FileOperationEvent,
|
||||
IdeConnectionEvent,
|
||||
StartSessionEvent,
|
||||
ToolCallEvent,
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
recordApiResponseMetrics,
|
||||
recordToolCallMetrics,
|
||||
recordChatCompressionMetrics,
|
||||
recordFileOperationMetric,
|
||||
} from './metrics.js';
|
||||
import { isTelemetrySdkInitialized } from './sdk.js';
|
||||
import { uiTelemetryService, UiEvent } from './uiTelemetry.js';
|
||||
@@ -155,6 +157,24 @@ export function logToolCall(config: Config, event: ToolCallEvent): void {
|
||||
);
|
||||
}
|
||||
|
||||
export function logFileOperation(
|
||||
config: Config,
|
||||
event: FileOperationEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logFileOperationEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
recordFileOperationMetric(
|
||||
config,
|
||||
event.operation,
|
||||
event.lines,
|
||||
event.mimetype,
|
||||
event.extension,
|
||||
event.diff_stat,
|
||||
event.programming_language,
|
||||
);
|
||||
}
|
||||
|
||||
export function logApiRequest(config: Config, event: ApiRequestEvent): void {
|
||||
ClearcutLogger.getInstance(config)?.logApiRequestEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
@@ -210,6 +210,7 @@ export function recordFileOperationMetric(
|
||||
mimetype?: string,
|
||||
extension?: string,
|
||||
diffStat?: DiffStat,
|
||||
programming_language?: string,
|
||||
): void {
|
||||
if (!fileOperationCounter || !isMetricsInitialized) return;
|
||||
const attributes: Attributes = {
|
||||
@@ -225,5 +226,8 @@ export function recordFileOperationMetric(
|
||||
attributes['user_added_lines'] = diffStat.user_added_lines;
|
||||
attributes['user_removed_lines'] = diffStat.user_removed_lines;
|
||||
}
|
||||
if (programming_language !== undefined) {
|
||||
attributes['programming_language'] = programming_language;
|
||||
}
|
||||
fileOperationCounter.add(1, attributes);
|
||||
}
|
||||
|
||||
46
packages/core/src/telemetry/telemetry-utils.test.ts
Normal file
46
packages/core/src/telemetry/telemetry-utils.test.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { getProgrammingLanguage } from './telemetry-utils.js';
|
||||
|
||||
describe('getProgrammingLanguage', () => {
|
||||
it('should return the programming language when file_path is present', () => {
|
||||
const args = { file_path: 'src/test.ts' };
|
||||
const language = getProgrammingLanguage(args);
|
||||
expect(language).toBe('TypeScript');
|
||||
});
|
||||
|
||||
it('should return the programming language when absolute_path is present', () => {
|
||||
const args = { absolute_path: 'src/test.py' };
|
||||
const language = getProgrammingLanguage(args);
|
||||
expect(language).toBe('Python');
|
||||
});
|
||||
|
||||
it('should return the programming language when path is present', () => {
|
||||
const args = { path: 'src/test.go' };
|
||||
const language = getProgrammingLanguage(args);
|
||||
expect(language).toBe('Go');
|
||||
});
|
||||
|
||||
it('should return undefined when no file path is present', () => {
|
||||
const args = {};
|
||||
const language = getProgrammingLanguage(args);
|
||||
expect(language).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle unknown file extensions gracefully', () => {
|
||||
const args = { file_path: 'src/test.unknown' };
|
||||
const language = getProgrammingLanguage(args);
|
||||
expect(language).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should handle files with no extension', () => {
|
||||
const args = { file_path: 'src/test' };
|
||||
const language = getProgrammingLanguage(args);
|
||||
expect(language).toBeUndefined();
|
||||
});
|
||||
});
|
||||
17
packages/core/src/telemetry/telemetry-utils.ts
Normal file
17
packages/core/src/telemetry/telemetry-utils.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2025 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { getLanguageFromFilePath } from '../utils/language-detection.js';
|
||||
|
||||
export function getProgrammingLanguage(
|
||||
args: Record<string, unknown>,
|
||||
): string | undefined {
|
||||
const filePath = args['file_path'] || args['path'] || args['absolute_path'];
|
||||
if (typeof filePath === 'string') {
|
||||
return getLanguageFromFilePath(filePath);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -8,12 +8,14 @@ import { GenerateContentResponseUsageMetadata } from '@google/genai';
|
||||
import { Config } from '../config/config.js';
|
||||
import { CompletedToolCall } from '../core/coreToolScheduler.js';
|
||||
import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
|
||||
import { FileDiff } from '../tools/tools.js';
|
||||
import { DiffStat, FileDiff } from '../tools/tools.js';
|
||||
import { AuthType } from '../core/contentGenerator.js';
|
||||
import {
|
||||
getDecisionFromOutcome,
|
||||
ToolCallDecision,
|
||||
} from './tool-call-decision.js';
|
||||
import { FileOperation } from './metrics.js';
|
||||
export { ToolCallDecision };
|
||||
import { ToolRegistry } from '../tools/tool-registry.js';
|
||||
|
||||
export interface BaseTelemetryEvent {
|
||||
@@ -399,6 +401,38 @@ export class KittySequenceOverflowEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class FileOperationEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'file_operation';
|
||||
'event.timestamp': string;
|
||||
tool_name: string;
|
||||
operation: FileOperation;
|
||||
lines?: number;
|
||||
mimetype?: string;
|
||||
extension?: string;
|
||||
diff_stat?: DiffStat;
|
||||
programming_language?: string;
|
||||
|
||||
constructor(
|
||||
tool_name: string,
|
||||
operation: FileOperation,
|
||||
lines?: number,
|
||||
mimetype?: string,
|
||||
extension?: string,
|
||||
diff_stat?: DiffStat,
|
||||
programming_language?: string,
|
||||
) {
|
||||
this['event.name'] = 'file_operation';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.tool_name = tool_name;
|
||||
this.operation = operation;
|
||||
this.lines = lines;
|
||||
this.mimetype = mimetype;
|
||||
this.extension = extension;
|
||||
this.diff_stat = diff_stat;
|
||||
this.programming_language = programming_language;
|
||||
}
|
||||
}
|
||||
|
||||
export type TelemetryEvent =
|
||||
| StartSessionEvent
|
||||
| EndSessionEvent
|
||||
@@ -413,4 +447,5 @@ export type TelemetryEvent =
|
||||
| KittySequenceOverflowEvent
|
||||
| MalformedJsonResponseEvent
|
||||
| IdeConnectionEvent
|
||||
| SlashCommandEvent;
|
||||
| SlashCommandEvent
|
||||
| FileOperationEvent;
|
||||
|
||||
Reference in New Issue
Block a user