mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
Smart Edit Strategy Logging (#10345)
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -31,6 +31,7 @@ import type {
|
||||
ExtensionEnableEvent,
|
||||
ModelSlashCommandEvent,
|
||||
ExtensionDisableEvent,
|
||||
SmartEditStrategyEvent,
|
||||
} from '../types.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import type { Config } from '../../config/config.js';
|
||||
@@ -75,6 +76,7 @@ export enum EventNames {
|
||||
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
|
||||
MODEL_ROUTING = 'model_routing',
|
||||
MODEL_SLASH_COMMAND = 'model_slash_command',
|
||||
SMART_EDIT_STRATEGY = 'smart_edit_strategy',
|
||||
}
|
||||
|
||||
export interface LogResponse {
|
||||
@@ -1039,6 +1041,20 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logSmartEditStrategyEvent(event: SmartEditStrategyEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SMART_EDIT_STRATEGY,
|
||||
value: event.strategy,
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.SMART_EDIT_STRATEGY, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default fields to data, and returns a new data array. This fields
|
||||
* should exist on all log events.
|
||||
|
||||
@@ -416,4 +416,7 @@ export enum EventMetadataKey {
|
||||
|
||||
// Logs an event when the user uses the /model command.
|
||||
GEMINI_CLI_MODEL_SLASH_COMMAND = 108,
|
||||
|
||||
// Logs a smart edit tool strategy choice.
|
||||
GEMINI_CLI_SMART_EDIT_STRATEGY = 109,
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ export const EVENT_CONTENT_RETRY_FAILURE =
|
||||
'gemini_cli.chat.content_retry_failure';
|
||||
export const EVENT_FILE_OPERATION = 'gemini_cli.file_operation';
|
||||
export const EVENT_MODEL_SLASH_COMMAND = 'gemini_cli.slash_command.model';
|
||||
export const EVENT_SMART_EDIT_STRATEGY = 'gemini_cli.smart_edit.strategy';
|
||||
export const EVENT_MODEL_ROUTING = 'gemini_cli.model_routing';
|
||||
|
||||
// Performance Events
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
EVENT_EXTENSION_INSTALL,
|
||||
EVENT_MODEL_SLASH_COMMAND,
|
||||
EVENT_EXTENSION_DISABLE,
|
||||
EVENT_SMART_EDIT_STRATEGY,
|
||||
} from './constants.js';
|
||||
import type {
|
||||
ApiErrorEvent,
|
||||
@@ -64,6 +65,7 @@ import type {
|
||||
ExtensionUninstallEvent,
|
||||
ExtensionInstallEvent,
|
||||
ModelSlashCommandEvent,
|
||||
SmartEditStrategyEvent,
|
||||
} from './types.js';
|
||||
import {
|
||||
recordApiErrorMetrics,
|
||||
@@ -816,3 +818,24 @@ export function logExtensionDisable(
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
export function logSmartEditStrategy(
|
||||
config: Config,
|
||||
event: SmartEditStrategyEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logSmartEditStrategyEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_SMART_EDIT_STRATEGY,
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `Smart Edit Tool Strategy: ${event.strategy}`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
@@ -702,3 +702,15 @@ export class ExtensionDisableEvent implements BaseTelemetryEvent {
|
||||
this.setting_scope = settingScope;
|
||||
}
|
||||
}
|
||||
|
||||
export class SmartEditStrategyEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'smart_edit_strategy';
|
||||
'event.timestamp': string;
|
||||
strategy: string;
|
||||
|
||||
constructor(strategy: string) {
|
||||
this['event.name'] = 'smart_edit_strategy';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.strategy = strategy;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,12 @@ describe('SmartEditTool', () => {
|
||||
} as unknown as BaseLlmClient;
|
||||
|
||||
mockConfig = {
|
||||
getUsageStatisticsEnabled: vi.fn(() => true),
|
||||
getSessionId: vi.fn(() => 'mock-session-id'),
|
||||
getContentGeneratorConfig: vi.fn(() => ({ authType: 'mock' })),
|
||||
getUseSmartEdit: vi.fn(() => false),
|
||||
getUseModelRouter: vi.fn(() => false),
|
||||
getProxy: vi.fn(() => undefined),
|
||||
getGeminiClient: vi.fn().mockReturnValue(geminiClient),
|
||||
getBaseLlmClient: vi.fn().mockReturnValue(baseLlmClient),
|
||||
getTargetDir: () => rootDir,
|
||||
@@ -195,7 +201,7 @@ describe('SmartEditTool', () => {
|
||||
|
||||
it('should perform an exact replacement', async () => {
|
||||
const content = 'hello world';
|
||||
const result = await calculateReplacement({
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.txt',
|
||||
instruction: 'test',
|
||||
@@ -211,7 +217,7 @@ describe('SmartEditTool', () => {
|
||||
|
||||
it('should perform a flexible, whitespace-insensitive replacement', async () => {
|
||||
const content = ' hello\n world\n';
|
||||
const result = await calculateReplacement({
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.txt',
|
||||
instruction: 'test',
|
||||
@@ -227,7 +233,7 @@ describe('SmartEditTool', () => {
|
||||
|
||||
it('should return 0 occurrences if no match is found', async () => {
|
||||
const content = 'hello world';
|
||||
const result = await calculateReplacement({
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.txt',
|
||||
instruction: 'test',
|
||||
@@ -245,7 +251,7 @@ describe('SmartEditTool', () => {
|
||||
// This case would fail with the previous exact and line-trimming flexible logic
|
||||
// because the whitespace *within* the line is different.
|
||||
const content = ' function myFunc( a, b ) {\n return a + b;\n }';
|
||||
const result = await calculateReplacement({
|
||||
const result = await calculateReplacement(mockConfig, {
|
||||
params: {
|
||||
file_path: 'test.js',
|
||||
instruction: 'test',
|
||||
|
||||
@@ -32,6 +32,8 @@ import { IdeClient } from '../ide/ide-client.js';
|
||||
import { FixLLMEditWithInstruction } from '../utils/llm-edit-fixer.js';
|
||||
import { applyReplacement } from './edit.js';
|
||||
import { safeLiteralReplace } from '../utils/textUtils.js';
|
||||
import { SmartEditStrategyEvent } from '../telemetry/types.js';
|
||||
import { logSmartEditStrategy } from '../telemetry/loggers.js';
|
||||
|
||||
interface ReplacementContext {
|
||||
params: EditToolParams;
|
||||
@@ -229,6 +231,7 @@ function detectLineEnding(content: string): '\r\n' | '\n' {
|
||||
}
|
||||
|
||||
export async function calculateReplacement(
|
||||
config: Config,
|
||||
context: ReplacementContext,
|
||||
): Promise<ReplacementResult> {
|
||||
const { currentContent, params } = context;
|
||||
@@ -247,16 +250,22 @@ export async function calculateReplacement(
|
||||
|
||||
const exactResult = await calculateExactReplacement(context);
|
||||
if (exactResult) {
|
||||
const event = new SmartEditStrategyEvent('exact');
|
||||
logSmartEditStrategy(config, event);
|
||||
return exactResult;
|
||||
}
|
||||
|
||||
const flexibleResult = await calculateFlexibleReplacement(context);
|
||||
if (flexibleResult) {
|
||||
const event = new SmartEditStrategyEvent('flexible');
|
||||
logSmartEditStrategy(config, event);
|
||||
return flexibleResult;
|
||||
}
|
||||
|
||||
const regexResult = await calculateRegexReplacement(context);
|
||||
if (regexResult) {
|
||||
const event = new SmartEditStrategyEvent('regex');
|
||||
logSmartEditStrategy(config, event);
|
||||
return regexResult;
|
||||
}
|
||||
|
||||
@@ -388,7 +397,7 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
|
||||
};
|
||||
}
|
||||
|
||||
const secondAttemptResult = await calculateReplacement({
|
||||
const secondAttemptResult = await calculateReplacement(this.config, {
|
||||
params: {
|
||||
...params,
|
||||
old_string: fixedEdit.search,
|
||||
@@ -516,7 +525,7 @@ class EditToolInvocation implements ToolInvocation<EditToolParams, ToolResult> {
|
||||
};
|
||||
}
|
||||
|
||||
const replacementResult = await calculateReplacement({
|
||||
const replacementResult = await calculateReplacement(this.config, {
|
||||
params,
|
||||
currentContent,
|
||||
abortSignal,
|
||||
|
||||
Reference in New Issue
Block a user