mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-11 18:40:57 -07:00
strong owner
This commit is contained in:
@@ -14,8 +14,8 @@
|
||||
* model call so the model only ever sees the most recent snapshot in full.
|
||||
*/
|
||||
|
||||
import type { GeminiChat } from '../../core/geminiChat.js';
|
||||
import type { Content, Part } from '@google/genai';
|
||||
import type { GeminiChat, HistoryTurn } from '../../core/geminiChat.js';
|
||||
import type { Part } from '@google/genai';
|
||||
import { debugLogger } from '../../utils/debugLogger.js';
|
||||
|
||||
const TAKE_SNAPSHOT_TOOL_NAME = 'take_snapshot';
|
||||
@@ -39,7 +39,7 @@ export const SNAPSHOT_SUPERSEDED_PLACEHOLDER =
|
||||
* Uses {@link GeminiChat.setHistory} to apply the modified history.
|
||||
*/
|
||||
export function supersedeStaleSnapshots(chat: GeminiChat): void {
|
||||
const history = chat.getHistory();
|
||||
const history = chat.getHistoryTurns();
|
||||
|
||||
// Locate all (contentIndex, partIndex) tuples for take_snapshot responses.
|
||||
const snapshotLocations: Array<{
|
||||
@@ -48,7 +48,7 @@ export function supersedeStaleSnapshots(chat: GeminiChat): void {
|
||||
}> = [];
|
||||
|
||||
for (let i = 0; i < history.length; i++) {
|
||||
const parts = history[i].parts;
|
||||
const parts = history[i].content.parts;
|
||||
if (!parts) continue;
|
||||
for (let j = 0; j < parts.length; j++) {
|
||||
const part = parts[j];
|
||||
@@ -71,7 +71,7 @@ export function supersedeStaleSnapshots(chat: GeminiChat): void {
|
||||
const staleLocations = snapshotLocations.slice(0, -1);
|
||||
const needsUpdate = staleLocations.some(({ contentIdx, partIdx }) => {
|
||||
const output = getResponseOutput(
|
||||
history[contentIdx].parts![partIdx].functionResponse?.response,
|
||||
history[contentIdx].content.parts![partIdx].functionResponse?.response,
|
||||
);
|
||||
return !output.includes(SNAPSHOT_SUPERSEDED_PLACEHOLDER);
|
||||
});
|
||||
@@ -81,15 +81,18 @@ export function supersedeStaleSnapshots(chat: GeminiChat): void {
|
||||
}
|
||||
|
||||
// Shallow-copy the history and replace stale snapshots.
|
||||
const newHistory: Content[] = history.map((content) => ({
|
||||
...content,
|
||||
parts: content.parts ? [...content.parts] : undefined,
|
||||
const newHistory: HistoryTurn[] = history.map((turn) => ({
|
||||
id: turn.id,
|
||||
content: {
|
||||
...turn.content,
|
||||
parts: turn.content.parts ? [...turn.content.parts] : undefined,
|
||||
},
|
||||
}));
|
||||
|
||||
let replacedCount = 0;
|
||||
|
||||
for (const { contentIdx, partIdx } of staleLocations) {
|
||||
const originalPart = newHistory[contentIdx].parts![partIdx];
|
||||
const originalPart = newHistory[contentIdx].content.parts![partIdx];
|
||||
if (!originalPart.functionResponse) continue;
|
||||
|
||||
// Check if already superseded
|
||||
@@ -106,7 +109,7 @@ export function supersedeStaleSnapshots(chat: GeminiChat): void {
|
||||
},
|
||||
};
|
||||
|
||||
newHistory[contentIdx].parts![partIdx] = replacementPart;
|
||||
newHistory[contentIdx].content.parts![partIdx] = replacementPart;
|
||||
replacedCount++;
|
||||
}
|
||||
|
||||
|
||||
@@ -756,12 +756,19 @@ describe('LocalAgentExecutor', () => {
|
||||
|
||||
expect(startHistory).toBeDefined();
|
||||
expect(startHistory).toHaveLength(2);
|
||||
const history = startHistory!;
|
||||
|
||||
// Perform checks on defined objects to satisfy TS
|
||||
const firstPart = startHistory?.[0]?.parts?.[0];
|
||||
const firstPart =
|
||||
'content' in history[0]
|
||||
? history[0].content.parts?.[0]
|
||||
: (history[0] as Content).parts?.[0];
|
||||
expect(firstPart?.text).toBe('Goal: TestGoal');
|
||||
|
||||
const secondPart = startHistory?.[1]?.parts?.[0];
|
||||
const secondPart =
|
||||
'content' in history[1]
|
||||
? history[1].content.parts?.[0]
|
||||
: (history[1] as Content).parts?.[0];
|
||||
expect(secondPart?.text).toBe('OK, starting on TestGoal.');
|
||||
});
|
||||
|
||||
@@ -3601,7 +3608,14 @@ describe('LocalAgentExecutor', () => {
|
||||
|
||||
expect(mockCompress).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetHistory).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetHistory).toHaveBeenCalledWith(compressedHistory);
|
||||
// History turns are now wrapped with IDs
|
||||
expect(mockSetHistory).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
content: expect.objectContaining({ role: 'user' }),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should pass hasFailedCompressionAttempt=true to compression after a failure', async () => {
|
||||
@@ -3706,7 +3720,14 @@ describe('LocalAgentExecutor', () => {
|
||||
expect(mockCompress.mock.calls[2][5]).toBe(false);
|
||||
|
||||
expect(mockSetHistory).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetHistory).toHaveBeenCalledWith(compressedHistory);
|
||||
// History turns are now wrapped with IDs
|
||||
expect(mockSetHistory).toHaveBeenCalledWith(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
content: expect.objectContaining({ role: 'user' }),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
type FunctionCall,
|
||||
type FunctionDeclaration,
|
||||
} from '@google/genai';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { ToolRegistry } from '../tools/tool-registry.js';
|
||||
import { PromptRegistry } from '../prompts/prompt-registry.js';
|
||||
import { ResourceRegistry } from '../resources/resource-registry.js';
|
||||
@@ -919,12 +920,20 @@ export class LocalAgentExecutor<TOutput extends z.ZodTypeAny> {
|
||||
this.hasFailedCompressionAttempt = true;
|
||||
} else if (info.compressionStatus === CompressionStatus.COMPRESSED) {
|
||||
if (newHistory) {
|
||||
chat.setHistory(newHistory);
|
||||
const turns = newHistory.map((c) => ({
|
||||
id: randomUUID(),
|
||||
content: c,
|
||||
}));
|
||||
chat.setHistory(turns);
|
||||
this.hasFailedCompressionAttempt = false;
|
||||
}
|
||||
} else if (info.compressionStatus === CompressionStatus.CONTENT_TRUNCATED) {
|
||||
if (newHistory) {
|
||||
chat.setHistory(newHistory);
|
||||
const turns = newHistory.map((c) => ({
|
||||
id: randomUUID(),
|
||||
content: c,
|
||||
}));
|
||||
chat.setHistory(turns);
|
||||
// Do NOT reset hasFailedCompressionAttempt.
|
||||
// We only truncated content because summarization previously failed.
|
||||
// We want to keep avoiding expensive summarization calls.
|
||||
|
||||
Reference in New Issue
Block a user