mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-18 18:11:02 -07:00
logs
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
|
||||
// DISCLAIMER: This is a copied version of https://github.com/googleapis/js-genai/blob/main/src/chats.ts with the intention of working around a key bug
|
||||
// where function responses are not treated as "valid" responses: https://b.corp.google.com/issues/420354090
|
||||
|
||||
@@ -167,6 +170,39 @@ export class GeminiChat {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the projected history sent to the API to a side-channel file for anomaly analysis.
|
||||
*/
|
||||
private logRequestHistory(
|
||||
requestContents: Content[],
|
||||
promptId: string,
|
||||
): void {
|
||||
try {
|
||||
const logDir = path.join(
|
||||
this.config.storage.getProjectTempDir(),
|
||||
'request_history_logs',
|
||||
this.config.getSessionId(),
|
||||
);
|
||||
if (!fs.existsSync(logDir)) {
|
||||
fs.mkdirSync(logDir, { recursive: true });
|
||||
}
|
||||
|
||||
const timestamp = Date.now();
|
||||
const logFile = path.join(
|
||||
logDir,
|
||||
`request_${timestamp}_${promptId}.json`,
|
||||
);
|
||||
fs.writeFileSync(logFile, JSON.stringify(requestContents, null, 2));
|
||||
debugLogger.debug(
|
||||
`[PROJECT CLARITY] Request history logged to ${logFile}`,
|
||||
);
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`[PROJECT CLARITY] Failed to log request history: ${error}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks a specific tool call ID for elision from the history.
|
||||
*/
|
||||
@@ -242,6 +278,9 @@ export class GeminiChat {
|
||||
const requestContents =
|
||||
this.historyManager.getHistoryForRequest(userContent);
|
||||
|
||||
// PROJECT CLARITY: Side-channel request logging.
|
||||
this.logRequestHistory(requestContents, prompt_id);
|
||||
|
||||
const stream = async function* (
|
||||
this: GeminiChat,
|
||||
): AsyncGenerator<StreamEvent, void, void> {
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
import type { Content } from '@google/genai';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
/**
|
||||
* Types of side-effects that can be triggered by tools or the system.
|
||||
@@ -74,6 +75,12 @@ export class SideEffectService {
|
||||
* Queues a side-effect for later application.
|
||||
*/
|
||||
queueSideEffect(effect: SideEffect): void {
|
||||
debugLogger.debug(`[PROJECT CLARITY] Queuing side-effect: ${effect.type}`, {
|
||||
payload:
|
||||
effect.type === SideEffectType.REPLACE_HISTORY
|
||||
? '<history>'
|
||||
: effect.payload,
|
||||
});
|
||||
this.pendingSideEffects.push(effect);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import { CHECKPOINT_STATE_DEFINITION } from './definitions/coreTools.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
interface CheckpointStateParams {
|
||||
[CHECKPOINT_STATE_PARAM_SUMMARY]: string;
|
||||
@@ -43,6 +44,7 @@ class CheckpointStateInvocation extends BaseToolInvocation<
|
||||
|
||||
override async execute(): Promise<ToolResult> {
|
||||
const summary = this.params[CHECKPOINT_STATE_PARAM_SUMMARY];
|
||||
debugLogger.debug(`[PROJECT CLARITY] Executing CheckpointStateTool with summary length: ${summary.length}`);
|
||||
const chat = this.config.getGeminiClient().getChat();
|
||||
const previousSummary = chat.getContinuityAnchor();
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import type { Config } from '../config/config.js';
|
||||
import type { GeminiChat } from '../core/geminiChat.js';
|
||||
import { CompressionStatus } from '../core/compression-status.js';
|
||||
import type { ShellExecutionConfig } from 'src/services/shellExecutionService.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
class CompressInvocation extends BaseToolInvocation<
|
||||
Record<string, never>,
|
||||
@@ -50,6 +51,7 @@ class CompressInvocation extends BaseToolInvocation<
|
||||
if (!callId) {
|
||||
throw new Error('Critical error: callId is required for context compression elision.');
|
||||
}
|
||||
debugLogger.debug(`[PROJECT CLARITY] Executing CompressTool (callId: ${callId})`);
|
||||
try {
|
||||
const continuityService = this.config.getContinuityCompressionService();
|
||||
const snapshot = await continuityService.generateSnapshot(
|
||||
|
||||
@@ -9,22 +9,19 @@ import {
|
||||
BaseToolInvocation,
|
||||
Kind,
|
||||
type ToolInvocation,
|
||||
type ToolResult,
|
||||
type ToolLiveOutput,
|
||||
type ToolResult,
|
||||
} from './tools.js';
|
||||
import {
|
||||
DISTILL_RESULT_TOOL_NAME,
|
||||
DISTILL_RESULT_PARAM_REVISED_TEXT,
|
||||
} from './tool-names.js';
|
||||
import { DISTILL_RESULT_TOOL_NAME } from './tool-names.js';
|
||||
import { DISTILL_RESULT_DEFINITION } from './definitions/coreTools.js';
|
||||
import type { MessageBus } from '../confirmation-bus/message-bus.js';
|
||||
import type { Config } from '../config/config.js';
|
||||
import type { GeminiChat } from '../core/geminiChat.js';
|
||||
import { saveTruncatedToolOutput } from '../utils/fileUtils.js';
|
||||
import type { ShellExecutionConfig } from '../index.js';
|
||||
import { debugLogger } from '../utils/debugLogger.js';
|
||||
import { saveTruncatedToolOutput } from '../utils/tool-output-helper.js';
|
||||
|
||||
interface DistillResultParams {
|
||||
[DISTILL_RESULT_PARAM_REVISED_TEXT]: string;
|
||||
revised_text: string;
|
||||
}
|
||||
|
||||
class DistillResultInvocation extends BaseToolInvocation<
|
||||
@@ -43,30 +40,24 @@ class DistillResultInvocation extends BaseToolInvocation<
|
||||
}
|
||||
|
||||
override getDescription(): string {
|
||||
return 'Distills the last tool output to reduce context entropy.';
|
||||
return 'Distills the most recent tool output in the history.';
|
||||
}
|
||||
|
||||
override async execute(
|
||||
_signal: AbortSignal,
|
||||
_updateOutput?: (output: ToolLiveOutput) => void,
|
||||
_shellExecutionConfig?: ShellExecutionConfig,
|
||||
_shellExecutionConfig?: any,
|
||||
ownCallId?: string,
|
||||
): Promise<ToolResult> {
|
||||
if (!ownCallId) {
|
||||
throw new Error('Critical error: ownCallId is required for distill_result elision.');
|
||||
}
|
||||
const revisedText = this.params[DISTILL_RESULT_PARAM_REVISED_TEXT];
|
||||
const sideEffects = this.config.getSideEffectService();
|
||||
const history = this.chat.getComprehensiveHistory();
|
||||
const revisedText = this.params.revised_text;
|
||||
|
||||
// 1. Find the last tool response (user message with functionResponse parts)
|
||||
debugLogger.debug(`[PROJECT CLARITY] Executing DistillResultTool (ownCallId: ${ownCallId})`);
|
||||
|
||||
// 1. Find the target: the last function response in history.
|
||||
const history = this.chat.getHistory();
|
||||
let lastToolResponseIndex = -1;
|
||||
for (let i = history.length - 1; i >= 0; i--) {
|
||||
const content = history[i];
|
||||
if (
|
||||
content.role === 'user' &&
|
||||
content.parts?.some((p) => p.functionResponse)
|
||||
) {
|
||||
if (history[i].parts?.some((p) => p.functionResponse)) {
|
||||
lastToolResponseIndex = i;
|
||||
break;
|
||||
}
|
||||
@@ -91,6 +82,10 @@ class DistillResultInvocation extends BaseToolInvocation<
|
||||
throw new Error('Target call ID missing from tool response.');
|
||||
}
|
||||
|
||||
debugLogger.debug(`[PROJECT CLARITY] Distill target identified: ${targetCallId} at index ${lastToolResponseIndex}`);
|
||||
|
||||
const sideEffects = this.config.getSideEffectService();
|
||||
|
||||
// 2. Elide all turns between that tool response and the current turn.
|
||||
if (ownCallId) {
|
||||
sideEffects.elideBetween(targetCallId, ownCallId);
|
||||
|
||||
Reference in New Issue
Block a user