Log yolo mode + number of turns (#6055)

Co-authored-by: Shi Shu <shii@google.com>
This commit is contained in:
shishu314
2025-08-25 16:06:47 -04:00
committed by GitHub
parent f32a54fefc
commit cd75d94262
9 changed files with 103 additions and 2 deletions

View File

@@ -26,6 +26,7 @@ import {
TrackedCancelledToolCall, TrackedCancelledToolCall,
} from './useReactToolScheduler.js'; } from './useReactToolScheduler.js';
import { import {
ApprovalMode,
Config, Config,
EditorType, EditorType,
AuthType, AuthType,
@@ -194,6 +195,7 @@ describe('useGeminiStream', () => {
getProjectRoot: vi.fn(() => '/test/dir'), getProjectRoot: vi.fn(() => '/test/dir'),
getCheckpointingEnabled: vi.fn(() => false), getCheckpointingEnabled: vi.fn(() => false),
getGeminiClient: mockGetGeminiClient, getGeminiClient: mockGetGeminiClient,
getApprovalMode: () => ApprovalMode.DEFAULT,
getUsageStatisticsEnabled: () => true, getUsageStatisticsEnabled: () => true,
getDebugMode: () => false, getDebugMode: () => false,
addHistory: vi.fn(), addHistory: vi.fn(),

View File

@@ -25,6 +25,9 @@ import {
UnauthorizedError, UnauthorizedError,
UserPromptEvent, UserPromptEvent,
DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_FLASH_MODEL,
logConversationFinishedEvent,
ConversationFinishedEvent,
ApprovalMode,
parseAndFormatApiError, parseAndFormatApiError,
} from '@google/gemini-cli-core'; } from '@google/gemini-cli-core';
import { type Part, type PartListUnion, FinishReason } from '@google/genai'; import { type Part, type PartListUnion, FinishReason } from '@google/genai';
@@ -172,6 +175,27 @@ export const useGeminiStream = (
return StreamingState.Idle; return StreamingState.Idle;
}, [isResponding, toolCalls]); }, [isResponding, toolCalls]);
useEffect(() => {
if (
config.getApprovalMode() === ApprovalMode.YOLO &&
streamingState === StreamingState.Idle
) {
const lastUserMessageIndex = history.findLastIndex(
(item: HistoryItem) => item.type === MessageType.USER,
);
const turnCount =
lastUserMessageIndex === -1 ? 0 : history.length - lastUserMessageIndex;
if (turnCount > 0) {
logConversationFinishedEvent(
config,
new ConversationFinishedEvent(config.getApprovalMode(), turnCount),
);
}
}
}, [streamingState, config, history]);
const cancelOngoingRequest = useCallback(() => { const cancelOngoingRequest = useCallback(() => {
if (streamingState !== StreamingState.Responding) { if (streamingState !== StreamingState.Responding) {
return; return;

View File

@@ -3,7 +3,7 @@
"compilerOptions": { "compilerOptions": {
"outDir": "dist", "outDir": "dist",
"jsx": "react-jsx", "jsx": "react-jsx",
"lib": ["DOM", "DOM.Iterable", "ES2022"], "lib": ["DOM", "DOM.Iterable", "ES2023"],
"types": ["node", "vitest/globals"] "types": ["node", "vitest/globals"]
}, },
"include": [ "include": [

View File

@@ -17,6 +17,7 @@ import {
SlashCommandEvent, SlashCommandEvent,
MalformedJsonResponseEvent, MalformedJsonResponseEvent,
IdeConnectionEvent, IdeConnectionEvent,
ConversationFinishedEvent,
KittySequenceOverflowEvent, KittySequenceOverflowEvent,
ChatCompressionEvent, ChatCompressionEvent,
FileOperationEvent, FileOperationEvent,
@@ -47,6 +48,7 @@ export enum EventNames {
IDE_CONNECTION = 'ide_connection', IDE_CONNECTION = 'ide_connection',
KITTY_SEQUENCE_OVERFLOW = 'kitty_sequence_overflow', KITTY_SEQUENCE_OVERFLOW = 'kitty_sequence_overflow',
CHAT_COMPRESSION = 'chat_compression', CHAT_COMPRESSION = 'chat_compression',
CONVERSATION_FINISHED = 'conversation_finished',
} }
export interface LogResponse { export interface LogResponse {
@@ -728,6 +730,28 @@ export class ClearcutLogger {
this.flushIfNeeded(); this.flushIfNeeded();
} }
logConversationFinishedEvent(event: ConversationFinishedEvent): void {
const data: EventValue[] = [
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_SESSION_ID,
value: this.config?.getSessionId() ?? '',
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_CONVERSATION_TURN_COUNT,
value: JSON.stringify(event.turnCount),
},
{
gemini_cli_key: EventMetadataKey.GEMINI_CLI_APPROVAL_MODE,
value: event.approvalMode,
},
];
this.enqueueLogEvent(
this.createLogEvent(EventNames.CONVERSATION_FINISHED, data),
);
this.flushIfNeeded();
}
logKittySequenceOverflowEvent(event: KittySequenceOverflowEvent): void { logKittySequenceOverflowEvent(event: KittySequenceOverflowEvent): void {
const data: EventValue[] = [ const data: EventValue[] = [
{ {

View File

@@ -232,6 +232,16 @@ export enum EventMetadataKey {
// Logs the length of the kitty sequence that overflowed. // Logs the length of the kitty sequence that overflowed.
GEMINI_CLI_KITTY_SEQUENCE_LENGTH = 53, GEMINI_CLI_KITTY_SEQUENCE_LENGTH = 53,
// ==========================================================================
// Conversation Finished Event Keys
// ===========================================================================
// Logs the approval mode
GEMINI_CLI_APPROVAL_MODE = 70,
// Logs the number of turns
GEMINI_CLI_CONVERSATION_TURN_COUNT = 71,
// Logs the number of tokens before context window compression. // Logs the number of tokens before context window compression.
GEMINI_CLI_COMPRESSION_TOKENS_BEFORE = 60, GEMINI_CLI_COMPRESSION_TOKENS_BEFORE = 60,

View File

@@ -16,6 +16,7 @@ export const EVENT_FLASH_FALLBACK = 'gemini_cli.flash_fallback';
export const EVENT_NEXT_SPEAKER_CHECK = 'gemini_cli.next_speaker_check'; export const EVENT_NEXT_SPEAKER_CHECK = 'gemini_cli.next_speaker_check';
export const EVENT_SLASH_COMMAND = 'gemini_cli.slash_command'; export const EVENT_SLASH_COMMAND = 'gemini_cli.slash_command';
export const EVENT_IDE_CONNECTION = 'gemini_cli.ide_connection'; export const EVENT_IDE_CONNECTION = 'gemini_cli.ide_connection';
export const EVENT_CONVERSATION_FINISHED = 'gemini_cli.conversation_finished';
export const EVENT_CHAT_COMPRESSION = 'gemini_cli.chat_compression'; export const EVENT_CHAT_COMPRESSION = 'gemini_cli.chat_compression';
export const EVENT_MALFORMED_JSON_RESPONSE = export const EVENT_MALFORMED_JSON_RESPONSE =
'gemini_cli.malformed_json_response'; 'gemini_cli.malformed_json_response';

View File

@@ -27,6 +27,7 @@ export {
logApiResponse, logApiResponse,
logFlashFallback, logFlashFallback,
logSlashCommand, logSlashCommand,
logConversationFinishedEvent,
logKittySequenceOverflow, logKittySequenceOverflow,
logChatCompression, logChatCompression,
} from './loggers.js'; } from './loggers.js';
@@ -40,6 +41,7 @@ export {
ApiResponseEvent, ApiResponseEvent,
TelemetryEvent, TelemetryEvent,
FlashFallbackEvent, FlashFallbackEvent,
ConversationFinishedEvent,
KittySequenceOverflowEvent, KittySequenceOverflowEvent,
SlashCommandEvent, SlashCommandEvent,
makeSlashCommandEvent, makeSlashCommandEvent,

View File

@@ -19,6 +19,7 @@ import {
EVENT_NEXT_SPEAKER_CHECK, EVENT_NEXT_SPEAKER_CHECK,
SERVICE_NAME, SERVICE_NAME,
EVENT_SLASH_COMMAND, EVENT_SLASH_COMMAND,
EVENT_CONVERSATION_FINISHED,
EVENT_CHAT_COMPRESSION, EVENT_CHAT_COMPRESSION,
EVENT_MALFORMED_JSON_RESPONSE, EVENT_MALFORMED_JSON_RESPONSE,
} from './constants.js'; } from './constants.js';
@@ -35,6 +36,7 @@ import {
NextSpeakerCheckEvent, NextSpeakerCheckEvent,
LoopDetectedEvent, LoopDetectedEvent,
SlashCommandEvent, SlashCommandEvent,
ConversationFinishedEvent,
KittySequenceOverflowEvent, KittySequenceOverflowEvent,
ChatCompressionEvent, ChatCompressionEvent,
MalformedJsonResponseEvent, MalformedJsonResponseEvent,
@@ -409,6 +411,27 @@ export function logIdeConnection(
logger.emit(logRecord); logger.emit(logRecord);
} }
export function logConversationFinishedEvent(
config: Config,
event: ConversationFinishedEvent,
): void {
ClearcutLogger.getInstance(config)?.logConversationFinishedEvent(event);
if (!isTelemetrySdkInitialized()) return;
const attributes: LogAttributes = {
...getCommonAttributes(config),
...event,
'event.name': EVENT_CONVERSATION_FINISHED,
};
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: `Conversation finished.`,
attributes,
};
logger.emit(logRecord);
}
export function logChatCompression( export function logChatCompression(
config: Config, config: Config,
event: ChatCompressionEvent, event: ChatCompressionEvent,

View File

@@ -5,7 +5,7 @@
*/ */
import { GenerateContentResponseUsageMetadata } from '@google/genai'; import { GenerateContentResponseUsageMetadata } from '@google/genai';
import { Config } from '../config/config.js'; import { ApprovalMode, Config } from '../config/config.js';
import { CompletedToolCall } from '../core/coreToolScheduler.js'; import { CompletedToolCall } from '../core/coreToolScheduler.js';
import { DiscoveredMCPTool } from '../tools/mcp-tool.js'; import { DiscoveredMCPTool } from '../tools/mcp-tool.js';
import { DiffStat, FileDiff } from '../tools/tools.js'; import { DiffStat, FileDiff } from '../tools/tools.js';
@@ -387,6 +387,20 @@ export class IdeConnectionEvent {
} }
} }
export class ConversationFinishedEvent {
'event_name': 'conversation_finished';
'event.timestamp': string; // ISO 8601;
approvalMode: ApprovalMode;
turnCount: number;
constructor(approvalMode: ApprovalMode, turnCount: number) {
this['event_name'] = 'conversation_finished';
this['event.timestamp'] = new Date().toISOString();
this.approvalMode = approvalMode;
this.turnCount = turnCount;
}
}
export class KittySequenceOverflowEvent { export class KittySequenceOverflowEvent {
'event.name': 'kitty_sequence_overflow'; 'event.name': 'kitty_sequence_overflow';
'event.timestamp': string; // ISO 8601 'event.timestamp': string; // ISO 8601
@@ -447,5 +461,6 @@ export type TelemetryEvent =
| KittySequenceOverflowEvent | KittySequenceOverflowEvent
| MalformedJsonResponseEvent | MalformedJsonResponseEvent
| IdeConnectionEvent | IdeConnectionEvent
| ConversationFinishedEvent
| SlashCommandEvent | SlashCommandEvent
| FileOperationEvent; | FileOperationEvent;