feat(context): implement observation masking for tool outputs (#18389)

This commit is contained in:
Abhi
2026-02-05 20:53:11 -05:00
committed by GitHub
parent 27af476ed9
commit f8504ebe8a
15 changed files with 1151 additions and 7 deletions
@@ -46,6 +46,7 @@ import type {
ApprovalModeSwitchEvent,
ApprovalModeDurationEvent,
PlanExecutionEvent,
ToolOutputMaskingEvent,
} from '../types.js';
import { EventMetadataKey } from './event-metadata-key.js';
import type { Config } from '../../config/config.js';
@@ -108,6 +109,7 @@ export enum EventNames {
APPROVAL_MODE_SWITCH = 'approval_mode_switch',
APPROVAL_MODE_DURATION = 'approval_mode_duration',
PLAN_EXECUTION = 'plan_execution',
TOOL_OUTPUT_MASKING = 'tool_output_masking',
}
export interface LogResponse {
@@ -1217,8 +1219,40 @@ export class ClearcutLogger {
},
];
const logEvent = this.createLogEvent(
EventNames.TOOL_OUTPUT_TRUNCATED,
data,
);
this.enqueueLogEvent(logEvent);
this.flushIfNeeded();
}
logToolOutputMaskingEvent(event: ToolOutputMaskingEvent): void {
const data: EventValue[] = [
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_MASKING_TOKENS_BEFORE,
value: event.tokens_before.toString(),
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_MASKING_TOKENS_AFTER,
value: event.tokens_after.toString(),
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_MASKING_MASKED_COUNT,
value: event.masked_count.toString(),
},
{
gemini_cli_key:
EventMetadataKey.GEMINI_CLI_TOOL_OUTPUT_MASKING_TOTAL_PRUNABLE_TOKENS,
value: event.total_prunable_tokens.toString(),
},
];
this.enqueueLogEvent(
this.createLogEvent(EventNames.TOOL_OUTPUT_TRUNCATED, data),
this.createLogEvent(EventNames.TOOL_OUTPUT_MASKING, data),
);
this.flushIfNeeded();
}
@@ -7,7 +7,7 @@
// Defines valid event metadata keys for Clearcut logging.
export enum EventMetadataKey {
// Deleted enums: 24
// Next ID: 148
// Next ID: 152
GEMINI_CLI_KEY_UNKNOWN = 0,
@@ -561,4 +561,20 @@ export enum EventMetadataKey {
// Logs the classifier threshold used.
GEMINI_CLI_ROUTING_CLASSIFIER_THRESHOLD = 147,
// ==========================================================================
// Tool Output Masking Event Keys
// ==========================================================================
// Logs the total tokens in the prunable block before masking.
GEMINI_CLI_TOOL_OUTPUT_MASKING_TOKENS_BEFORE = 148,
// Logs the total tokens in the masked remnants after masking.
GEMINI_CLI_TOOL_OUTPUT_MASKING_TOKENS_AFTER = 149,
// Logs the number of tool outputs masked in this operation.
GEMINI_CLI_TOOL_OUTPUT_MASKING_MASKED_COUNT = 150,
// Logs the total prunable tokens identified at the trigger point.
GEMINI_CLI_TOOL_OUTPUT_MASKING_TOTAL_PRUNABLE_TOKENS = 151,
}
+16
View File
@@ -56,6 +56,7 @@ import type {
StartupStatsEvent,
LlmLoopCheckEvent,
PlanExecutionEvent,
ToolOutputMaskingEvent,
} from './types.js';
import {
recordApiErrorMetrics,
@@ -163,6 +164,21 @@ export function logToolOutputTruncated(
});
}
export function logToolOutputMasking(
config: Config,
event: ToolOutputMaskingEvent,
): void {
ClearcutLogger.getInstance(config)?.logToolOutputMaskingEvent(event);
bufferTelemetryEvent(() => {
const logger = logs.getLogger(SERVICE_NAME);
const logRecord: LogRecord = {
body: event.toLogBody(),
attributes: event.toOpenTelemetryAttributes(config),
};
logger.emit(logRecord);
});
}
export function logFileOperation(
config: Config,
event: FileOperationEvent,
+44
View File
@@ -1376,6 +1376,49 @@ export class ToolOutputTruncatedEvent implements BaseTelemetryEvent {
}
}
export const EVENT_TOOL_OUTPUT_MASKING = 'gemini_cli.tool_output_masking';
export class ToolOutputMaskingEvent implements BaseTelemetryEvent {
'event.name': 'tool_output_masking';
'event.timestamp': string;
tokens_before: number;
tokens_after: number;
masked_count: number;
total_prunable_tokens: number;
constructor(details: {
tokens_before: number;
tokens_after: number;
masked_count: number;
total_prunable_tokens: number;
}) {
this['event.name'] = 'tool_output_masking';
this['event.timestamp'] = new Date().toISOString();
this.tokens_before = details.tokens_before;
this.tokens_after = details.tokens_after;
this.masked_count = details.masked_count;
this.total_prunable_tokens = details.total_prunable_tokens;
}
toOpenTelemetryAttributes(config: Config): LogAttributes {
return {
...getCommonAttributes(config),
'event.name': EVENT_TOOL_OUTPUT_MASKING,
'event.timestamp': this['event.timestamp'],
tokens_before: this.tokens_before,
tokens_after: this.tokens_after,
masked_count: this.masked_count,
total_prunable_tokens: this.total_prunable_tokens,
};
}
toLogBody(): string {
return `Tool output masking (Masked ${this.masked_count} tool outputs. Saved ${
this.tokens_before - this.tokens_after
} tokens)`;
}
}
export const EVENT_EXTENSION_UNINSTALL = 'gemini_cli.extension_uninstall';
export class ExtensionUninstallEvent implements BaseTelemetryEvent {
'event.name': 'extension_uninstall';
@@ -1602,6 +1645,7 @@ export type TelemetryEvent =
| LlmLoopCheckEvent
| StartupStatsEvent
| WebFetchFallbackAttemptEvent
| ToolOutputMaskingEvent
| EditStrategyEvent
| PlanExecutionEvent
| RewindEvent