mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-23 03:24:42 -07:00
Cherrypick extensions changes to v0.6.0 (#9179)
Co-authored-by: hritan <48129645+hritan@users.noreply.github.com> Co-authored-by: Taneja Hriday <hridayt@google.com> Co-authored-by: shishu314 <shishu_1998@yahoo.com> Co-authored-by: Shi Shu <shii@google.com> Co-authored-by: Jacob MacDonald <jakemac@google.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -24,12 +24,16 @@ export {
|
||||
DEFAULT_TRUNCATE_TOOL_OUTPUT_THRESHOLD,
|
||||
} from './src/config/config.js';
|
||||
export { detectIdeFromEnv, getIdeInfo } from './src/ide/detect-ide.js';
|
||||
export { logIdeConnection } from './src/telemetry/loggers.js';
|
||||
export {
|
||||
logExtensionEnable,
|
||||
logIdeConnection,
|
||||
} from './src/telemetry/loggers.js';
|
||||
|
||||
export {
|
||||
IdeConnectionEvent,
|
||||
IdeConnectionType,
|
||||
ExtensionInstallEvent,
|
||||
ExtensionEnableEvent,
|
||||
ExtensionUninstallEvent,
|
||||
} from './src/telemetry/types.js';
|
||||
export { makeFakeConfig } from './src/test-utils/config.js';
|
||||
|
||||
@@ -117,9 +117,15 @@ export interface GeminiCLIExtension {
|
||||
version: string;
|
||||
isActive: boolean;
|
||||
path: string;
|
||||
source?: string;
|
||||
type?: 'git' | 'local' | 'link';
|
||||
installMetadata?: ExtensionInstallMetadata;
|
||||
}
|
||||
|
||||
export interface ExtensionInstallMetadata {
|
||||
source: string;
|
||||
type: 'git' | 'local' | 'link' | 'github-release';
|
||||
releaseTag?: string; // Only present for github-release installs.
|
||||
ref?: string;
|
||||
autoUpdate?: boolean;
|
||||
}
|
||||
|
||||
export interface FileFilteringOptions {
|
||||
|
||||
@@ -28,6 +28,7 @@ import type {
|
||||
ToolOutputTruncatedEvent,
|
||||
ExtensionUninstallEvent,
|
||||
ModelRoutingEvent,
|
||||
ExtensionEnableEvent,
|
||||
} from '../types.js';
|
||||
import { EventMetadataKey } from './event-metadata-key.js';
|
||||
import type { Config } from '../../config/config.js';
|
||||
@@ -61,6 +62,7 @@ export enum EventNames {
|
||||
INVALID_CHUNK = 'invalid_chunk',
|
||||
CONTENT_RETRY = 'content_retry',
|
||||
CONTENT_RETRY_FAILURE = 'content_retry_failure',
|
||||
EXTENSION_ENABLE = 'extension_enable',
|
||||
EXTENSION_INSTALL = 'extension_install',
|
||||
EXTENSION_UNINSTALL = 'extension_uninstall',
|
||||
TOOL_OUTPUT_TRUNCATED = 'tool_output_truncated',
|
||||
@@ -959,6 +961,25 @@ export class ClearcutLogger {
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
logExtensionEnableEvent(event: ExtensionEnableEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
gemini_cli_key: EventMetadataKey.GEMINI_CLI_EXTENSION_NAME,
|
||||
value: event.extension_name,
|
||||
},
|
||||
{
|
||||
gemini_cli_key:
|
||||
EventMetadataKey.GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE,
|
||||
value: event.setting_scope,
|
||||
},
|
||||
];
|
||||
|
||||
this.enqueueLogEvent(
|
||||
this.createLogEvent(EventNames.EXTENSION_ENABLE, data),
|
||||
);
|
||||
this.flushIfNeeded();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds default fields to data, and returns a new data array. This fields
|
||||
* should exist on all log events.
|
||||
|
||||
@@ -353,6 +353,9 @@ export enum EventMetadataKey {
|
||||
// Logs the status of the extension uninstall
|
||||
GEMINI_CLI_EXTENSION_UNINSTALL_STATUS = 96,
|
||||
|
||||
// Logs the setting scope for an extension enablement.
|
||||
GEMINI_CLI_EXTENSION_ENABLE_SETTING_SCOPE = 102,
|
||||
|
||||
// ==========================================================================
|
||||
// Tool Output Truncated Event Keys
|
||||
// ===========================================================================
|
||||
|
||||
@@ -12,6 +12,9 @@ export const EVENT_API_REQUEST = 'gemini_cli.api_request';
|
||||
export const EVENT_API_ERROR = 'gemini_cli.api_error';
|
||||
export const EVENT_API_RESPONSE = 'gemini_cli.api_response';
|
||||
export const EVENT_CLI_CONFIG = 'gemini_cli.config';
|
||||
export const EVENT_EXTENSION_ENABLE = 'gemini_cli.extension_enable';
|
||||
export const EVENT_EXTENSION_INSTALL = 'gemini_cli.extension_install';
|
||||
export const EVENT_EXTENSION_UNINSTALL = 'gemini_cli.extension_uninstall';
|
||||
export const EVENT_FLASH_FALLBACK = 'gemini_cli.flash_fallback';
|
||||
export const EVENT_RIPGREP_FALLBACK = 'gemini_cli.ripgrep_fallback';
|
||||
export const EVENT_NEXT_SPEAKER_CHECK = 'gemini_cli.next_speaker_check';
|
||||
|
||||
@@ -36,6 +36,9 @@ export {
|
||||
logKittySequenceOverflow,
|
||||
logChatCompression,
|
||||
logToolOutputTruncated,
|
||||
logExtensionEnable,
|
||||
logExtensionInstallEvent,
|
||||
logExtensionUninstall,
|
||||
} from './loggers.js';
|
||||
export type { SlashCommandEvent, ChatCompressionEvent } from './types.js';
|
||||
export {
|
||||
|
||||
@@ -33,6 +33,9 @@ import {
|
||||
EVENT_FILE_OPERATION,
|
||||
EVENT_RIPGREP_FALLBACK,
|
||||
EVENT_MODEL_ROUTING,
|
||||
EVENT_EXTENSION_ENABLE,
|
||||
EVENT_EXTENSION_INSTALL,
|
||||
EVENT_EXTENSION_UNINSTALL,
|
||||
} from './constants.js';
|
||||
import {
|
||||
logApiRequest,
|
||||
@@ -47,6 +50,9 @@ import {
|
||||
logRipgrepFallback,
|
||||
logToolOutputTruncated,
|
||||
logModelRouting,
|
||||
logExtensionEnable,
|
||||
logExtensionInstallEvent,
|
||||
logExtensionUninstall,
|
||||
} from './loggers.js';
|
||||
import { ToolCallDecision } from './tool-call-decision.js';
|
||||
import {
|
||||
@@ -62,11 +68,14 @@ import {
|
||||
FileOperationEvent,
|
||||
ToolOutputTruncatedEvent,
|
||||
ModelRoutingEvent,
|
||||
ExtensionEnableEvent,
|
||||
ExtensionInstallEvent,
|
||||
ExtensionUninstallEvent,
|
||||
} from './types.js';
|
||||
import * as metrics from './metrics.js';
|
||||
import { FileOperation } from './metrics.js';
|
||||
import * as sdk from './sdk.js';
|
||||
import { vi, describe, beforeEach, it, expect } from 'vitest';
|
||||
import { vi, describe, beforeEach, it, expect, afterEach } from 'vitest';
|
||||
import type { GenerateContentResponseUsageMetadata } from '@google/genai';
|
||||
import * as uiTelemetry from './uiTelemetry.js';
|
||||
import { makeFakeConfig } from '../test-utils/config.js';
|
||||
@@ -1108,4 +1117,122 @@ describe('loggers', () => {
|
||||
expect(metrics.recordModelRoutingMetrics).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('logExtensionInstall', () => {
|
||||
const mockConfig = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(ClearcutLogger.prototype, 'logExtensionInstallEvent');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should log extension install event', () => {
|
||||
const event = new ExtensionInstallEvent(
|
||||
'vscode',
|
||||
'0.1.0',
|
||||
'git',
|
||||
'success',
|
||||
);
|
||||
|
||||
logExtensionInstallEvent(mockConfig, event);
|
||||
|
||||
expect(
|
||||
ClearcutLogger.prototype.logExtensionInstallEvent,
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Installed extension vscode',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'event.name': EVENT_EXTENSION_INSTALL,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
extension_version: '0.1.0',
|
||||
extension_source: 'git',
|
||||
status: 'success',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('logExtensionUninstall', () => {
|
||||
const mockConfig = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(ClearcutLogger.prototype, 'logExtensionUninstallEvent');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should log extension uninstall event', () => {
|
||||
const event = new ExtensionUninstallEvent('vscode', 'success');
|
||||
|
||||
logExtensionUninstall(mockConfig, event);
|
||||
|
||||
expect(
|
||||
ClearcutLogger.prototype.logExtensionUninstallEvent,
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Uninstalled extension vscode',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'event.name': EVENT_EXTENSION_UNINSTALL,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
status: 'success',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('logExtensionEnable', () => {
|
||||
const mockConfig = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(ClearcutLogger.prototype, 'logExtensionEnableEvent');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.resetAllMocks();
|
||||
});
|
||||
|
||||
it('should log extension enable event', () => {
|
||||
const event = new ExtensionEnableEvent('vscode', 'user');
|
||||
|
||||
logExtensionEnable(mockConfig, event);
|
||||
|
||||
expect(
|
||||
ClearcutLogger.prototype.logExtensionEnableEvent,
|
||||
).toHaveBeenCalledWith(event);
|
||||
|
||||
expect(mockLogger.emit).toHaveBeenCalledWith({
|
||||
body: 'Enabled extension vscode',
|
||||
attributes: {
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'event.name': EVENT_EXTENSION_ENABLE,
|
||||
'event.timestamp': '2025-01-01T00:00:00.000Z',
|
||||
extension_name: 'vscode',
|
||||
setting_scope: 'user',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,8 @@ import {
|
||||
EVENT_API_REQUEST,
|
||||
EVENT_API_RESPONSE,
|
||||
EVENT_CLI_CONFIG,
|
||||
EVENT_EXTENSION_UNINSTALL,
|
||||
EVENT_EXTENSION_ENABLE,
|
||||
EVENT_IDE_CONNECTION,
|
||||
EVENT_TOOL_CALL,
|
||||
EVENT_USER_PROMPT,
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
EVENT_FILE_OPERATION,
|
||||
EVENT_RIPGREP_FALLBACK,
|
||||
EVENT_MODEL_ROUTING,
|
||||
EVENT_EXTENSION_INSTALL,
|
||||
} from './constants.js';
|
||||
import type {
|
||||
ApiErrorEvent,
|
||||
@@ -54,6 +57,9 @@ import type {
|
||||
RipgrepFallbackEvent,
|
||||
ToolOutputTruncatedEvent,
|
||||
ModelRoutingEvent,
|
||||
ExtensionEnableEvent,
|
||||
ExtensionUninstallEvent,
|
||||
ExtensionInstallEvent,
|
||||
} from './types.js';
|
||||
import {
|
||||
recordApiErrorMetrics,
|
||||
@@ -691,3 +697,73 @@ export function logModelRouting(
|
||||
logger.emit(logRecord);
|
||||
recordModelRoutingMetrics(config, event);
|
||||
}
|
||||
|
||||
export function logExtensionInstallEvent(
|
||||
config: Config,
|
||||
event: ExtensionInstallEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logExtensionInstallEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_EXTENSION_INSTALL,
|
||||
'event.timestamp': new Date().toISOString(),
|
||||
extension_name: event.extension_name,
|
||||
extension_version: event.extension_version,
|
||||
extension_source: event.extension_source,
|
||||
status: event.status,
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `Installed extension ${event.extension_name}`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
export function logExtensionUninstall(
|
||||
config: Config,
|
||||
event: ExtensionUninstallEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logExtensionUninstallEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_EXTENSION_UNINSTALL,
|
||||
'event.timestamp': new Date().toISOString(),
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `Uninstalled extension ${event.extension_name}`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
export function logExtensionEnable(
|
||||
config: Config,
|
||||
event: ExtensionEnableEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logExtensionEnableEvent(event);
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_EXTENSION_ENABLE,
|
||||
'event.timestamp': new Date().toISOString(),
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `Enabled extension ${event.extension_name}`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
@@ -198,6 +198,9 @@ export function initializeTelemetry(config: Config): void {
|
||||
process.on('SIGINT', () => {
|
||||
shutdownTelemetry(config);
|
||||
});
|
||||
process.on('exit', () => {
|
||||
shutdownTelemetry(config);
|
||||
});
|
||||
}
|
||||
|
||||
export async function shutdownTelemetry(config: Config): Promise<void> {
|
||||
|
||||
@@ -576,6 +576,7 @@ export type TelemetryEvent =
|
||||
| InvalidChunkEvent
|
||||
| ContentRetryEvent
|
||||
| ContentRetryFailureEvent
|
||||
| ExtensionEnableEvent
|
||||
| ExtensionInstallEvent
|
||||
| ExtensionUninstallEvent
|
||||
| ModelRoutingEvent
|
||||
@@ -648,3 +649,17 @@ export class ExtensionUninstallEvent implements BaseTelemetryEvent {
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExtensionEnableEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'extension_enable';
|
||||
'event.timestamp': string;
|
||||
extension_name: string;
|
||||
setting_scope: string;
|
||||
|
||||
constructor(extension_name: string, settingScope: string) {
|
||||
this['event.name'] = 'extension_enable';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
this.extension_name = extension_name;
|
||||
this.setting_scope = settingScope;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user