mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-28 22:14:52 -07:00
feat(core): Download ripgrep at runtime, if enabled. (#7818)
This commit is contained in:
@@ -392,6 +392,24 @@ describe('ClearcutLogger', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('logRipgrepFallbackEvent', () => {
|
||||
it('logs an event with the proper name', () => {
|
||||
const { logger } = setup();
|
||||
// Spy on flushToClearcut to prevent it from clearing the queue
|
||||
const flushSpy = vi
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.spyOn(logger!, 'flushToClearcut' as any)
|
||||
.mockResolvedValue({ nextRequestWaitMs: 0 });
|
||||
|
||||
logger?.logRipgrepFallbackEvent();
|
||||
|
||||
const events = getEvents(logger!);
|
||||
expect(events.length).toBe(1);
|
||||
expect(events[0]).toHaveEventName(EventNames.RIPGREP_FALLBACK);
|
||||
expect(flushSpy).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
describe('enqueueLogEvent', () => {
|
||||
it('should add events to the queue', () => {
|
||||
const { logger } = setup();
|
||||
|
||||
@@ -44,6 +44,7 @@ export enum EventNames {
|
||||
API_ERROR = 'api_error',
|
||||
END_SESSION = 'end_session',
|
||||
FLASH_FALLBACK = 'flash_fallback',
|
||||
RIPGREP_FALLBACK = 'ripgrep_fallback',
|
||||
LOOP_DETECTED = 'loop_detected',
|
||||
NEXT_SPEAKER_CHECK = 'next_speaker_check',
|
||||
SLASH_COMMAND = 'slash_command',
|
||||
@@ -631,6 +632,13 @@ export class ClearcutLogger {
|
||||
});
|
||||
}
|
||||
|
||||
logRipgrepFallbackEvent(): void {
|
||||
this.enqueueLogEvent(this.createLogEvent(EventNames.RIPGREP_FALLBACK, []));
|
||||
this.flushToClearcut().catch((error) => {
|
||||
console.debug('Error flushing to Clearcut:', error);
|
||||
});
|
||||
}
|
||||
|
||||
logLoopDetectedEvent(event: LoopDetectedEvent): void {
|
||||
const data: EventValue[] = [
|
||||
{
|
||||
|
||||
@@ -13,6 +13,7 @@ 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_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';
|
||||
export const EVENT_SLASH_COMMAND = 'gemini_cli.slash_command';
|
||||
export const EVENT_IDE_CONNECTION = 'gemini_cli.ide_connection';
|
||||
|
||||
@@ -30,6 +30,7 @@ import {
|
||||
EVENT_FLASH_FALLBACK,
|
||||
EVENT_MALFORMED_JSON_RESPONSE,
|
||||
EVENT_FILE_OPERATION,
|
||||
EVENT_RIPGREP_FALLBACK,
|
||||
} from './constants.js';
|
||||
import {
|
||||
logApiRequest,
|
||||
@@ -41,6 +42,7 @@ import {
|
||||
logChatCompression,
|
||||
logMalformedJsonResponse,
|
||||
logFileOperation,
|
||||
logRipgrepFallback,
|
||||
} from './loggers.js';
|
||||
import { ToolCallDecision } from './tool-call-decision.js';
|
||||
import {
|
||||
@@ -50,6 +52,7 @@ import {
|
||||
ToolCallEvent,
|
||||
UserPromptEvent,
|
||||
FlashFallbackEvent,
|
||||
RipgrepFallbackEvent,
|
||||
MalformedJsonResponseEvent,
|
||||
makeChatCompressionEvent,
|
||||
FileOperationEvent,
|
||||
@@ -453,6 +456,59 @@ describe('loggers', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('logRipgrepFallback', () => {
|
||||
const mockConfig = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
getUsageStatisticsEnabled: () => true,
|
||||
} as unknown as Config;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.spyOn(ClearcutLogger.prototype, 'logRipgrepFallbackEvent');
|
||||
});
|
||||
|
||||
it('should log ripgrep fallback event', () => {
|
||||
const event = new RipgrepFallbackEvent();
|
||||
|
||||
logRipgrepFallback(mockConfig, event);
|
||||
|
||||
expect(
|
||||
ClearcutLogger.prototype.logRipgrepFallbackEvent,
|
||||
).toHaveBeenCalled();
|
||||
|
||||
const emittedEvent = mockLogger.emit.mock.calls[0][0];
|
||||
expect(emittedEvent.body).toBe('Switching to grep as fallback.');
|
||||
expect(emittedEvent.attributes).toEqual(
|
||||
expect.objectContaining({
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'event.name': EVENT_RIPGREP_FALLBACK,
|
||||
error: undefined,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('should log ripgrep fallback event with an error', () => {
|
||||
const event = new RipgrepFallbackEvent('rg not found');
|
||||
|
||||
logRipgrepFallback(mockConfig, event);
|
||||
|
||||
expect(
|
||||
ClearcutLogger.prototype.logRipgrepFallbackEvent,
|
||||
).toHaveBeenCalled();
|
||||
|
||||
const emittedEvent = mockLogger.emit.mock.calls[0][0];
|
||||
expect(emittedEvent.body).toBe('Switching to grep as fallback.');
|
||||
expect(emittedEvent.attributes).toEqual(
|
||||
expect.objectContaining({
|
||||
'session.id': 'test-session-id',
|
||||
'user.email': 'test-user@example.com',
|
||||
'event.name': EVENT_RIPGREP_FALLBACK,
|
||||
error: 'rg not found',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('logToolCall', () => {
|
||||
const cfg1 = {
|
||||
getSessionId: () => 'test-session-id',
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
EVENT_CONTENT_RETRY,
|
||||
EVENT_CONTENT_RETRY_FAILURE,
|
||||
EVENT_FILE_OPERATION,
|
||||
EVENT_RIPGREP_FALLBACK,
|
||||
} from './constants.js';
|
||||
import type {
|
||||
ApiErrorEvent,
|
||||
@@ -48,6 +49,7 @@ import type {
|
||||
InvalidChunkEvent,
|
||||
ContentRetryEvent,
|
||||
ContentRetryFailureEvent,
|
||||
RipgrepFallbackEvent,
|
||||
} from './types.js';
|
||||
import {
|
||||
recordApiErrorMetrics,
|
||||
@@ -268,6 +270,28 @@ export function logFlashFallback(
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
export function logRipgrepFallback(
|
||||
config: Config,
|
||||
event: RipgrepFallbackEvent,
|
||||
): void {
|
||||
ClearcutLogger.getInstance(config)?.logRipgrepFallbackEvent();
|
||||
if (!isTelemetrySdkInitialized()) return;
|
||||
|
||||
const attributes: LogAttributes = {
|
||||
...getCommonAttributes(config),
|
||||
...event,
|
||||
'event.name': EVENT_RIPGREP_FALLBACK,
|
||||
'event.timestamp': new Date().toISOString(),
|
||||
};
|
||||
|
||||
const logger = logs.getLogger(SERVICE_NAME);
|
||||
const logRecord: LogRecord = {
|
||||
body: `Switching to grep as fallback.`,
|
||||
attributes,
|
||||
};
|
||||
logger.emit(logRecord);
|
||||
}
|
||||
|
||||
export function logApiError(config: Config, event: ApiErrorEvent): void {
|
||||
const uiEvent = {
|
||||
...event,
|
||||
|
||||
@@ -281,6 +281,16 @@ export class FlashFallbackEvent implements BaseTelemetryEvent {
|
||||
}
|
||||
}
|
||||
|
||||
export class RipgrepFallbackEvent implements BaseTelemetryEvent {
|
||||
'event.name': 'ripgrep_fallback';
|
||||
'event.timestamp': string;
|
||||
|
||||
constructor(public error?: string) {
|
||||
this['event.name'] = 'ripgrep_fallback';
|
||||
this['event.timestamp'] = new Date().toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
export enum LoopType {
|
||||
CONSECUTIVE_IDENTICAL_TOOL_CALLS = 'consecutive_identical_tool_calls',
|
||||
CHANTING_IDENTICAL_SENTENCES = 'chanting_identical_sentences',
|
||||
|
||||
Reference in New Issue
Block a user