@google/gemini-cli-core 0.9.0-nightly.20250926.1487841d → 0.9.0-nightly.20251001.33269bdb
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/agents/codebase-investigator.d.ts +11 -0
- package/dist/src/agents/codebase-investigator.js +164 -0
- package/dist/src/agents/codebase-investigator.js.map +1 -0
- package/dist/src/agents/executor.d.ts +96 -0
- package/dist/src/agents/executor.js +438 -0
- package/dist/src/agents/executor.js.map +1 -0
- package/dist/src/agents/executor.test.d.ts +6 -0
- package/dist/src/agents/executor.test.js +492 -0
- package/dist/src/agents/executor.test.js.map +1 -0
- package/dist/src/agents/invocation.d.ts +45 -0
- package/dist/src/agents/invocation.js +101 -0
- package/dist/src/agents/invocation.js.map +1 -0
- package/dist/src/agents/invocation.test.d.ts +6 -0
- package/dist/src/agents/invocation.test.js +214 -0
- package/dist/src/agents/invocation.test.js.map +1 -0
- package/dist/src/agents/registry.d.ts +35 -0
- package/dist/src/agents/registry.js +58 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/agents/registry.test.d.ts +6 -0
- package/dist/src/agents/registry.test.js +146 -0
- package/dist/src/agents/registry.test.js.map +1 -0
- package/dist/src/agents/schema-utils.d.ts +39 -0
- package/dist/src/agents/schema-utils.js +57 -0
- package/dist/src/agents/schema-utils.js.map +1 -0
- package/dist/src/agents/schema-utils.test.d.ts +6 -0
- package/dist/src/agents/schema-utils.test.js +144 -0
- package/dist/src/agents/schema-utils.test.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.d.ts +38 -0
- package/dist/src/agents/subagent-tool-wrapper.js +48 -0
- package/dist/src/agents/subagent-tool-wrapper.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.test.d.ts +6 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js +112 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -0
- package/dist/src/agents/types.d.ts +123 -0
- package/dist/src/agents/types.js +17 -0
- package/dist/src/agents/types.js.map +1 -0
- package/dist/src/agents/utils.d.ts +15 -0
- package/dist/src/agents/utils.js +29 -0
- package/dist/src/agents/utils.js.map +1 -0
- package/dist/src/agents/utils.test.d.ts +6 -0
- package/dist/src/agents/utils.test.js +87 -0
- package/dist/src/agents/utils.test.js.map +1 -0
- package/dist/src/config/config.d.ts +11 -2
- package/dist/src/config/config.js +49 -3
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +63 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/core/baseLlmClient.js +19 -21
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +57 -17
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.js +8 -29
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +223 -94
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +33 -23
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.js +1 -1
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +2 -1
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +11 -11
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/ide/detect-ide.d.ts +1 -0
- package/dist/src/ide/detect-ide.js +4 -1
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/mcp/sa-impersonation-provider.d.ts +33 -0
- package/dist/src/mcp/sa-impersonation-provider.js +130 -0
- package/dist/src/mcp/sa-impersonation-provider.js.map +1 -0
- package/dist/src/mcp/sa-impersonation-provider.test.d.ts +6 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js +117 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js.map +1 -0
- package/dist/src/services/fileSystemService.d.ts +9 -0
- package/dist/src/services/fileSystemService.js +11 -0
- package/dist/src/services/fileSystemService.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +2 -0
- package/dist/src/services/shellExecutionService.js +48 -7
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +13 -4
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +4 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +15 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +18 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +2 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +2 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +1 -24
- package/dist/src/telemetry/constants.js +1 -25
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +1 -1
- package/dist/src/telemetry/index.js +7 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +2 -1
- package/dist/src/telemetry/loggers.js +66 -11
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +3 -3
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +76 -9
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +363 -19
- package/dist/src/telemetry/metrics.js +415 -235
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +352 -54
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +6 -0
- package/dist/src/telemetry/types.js +10 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +2 -2
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/mock-tool.d.ts +28 -3
- package/dist/src/test-utils/mock-tool.js +71 -1
- package/dist/src/test-utils/mock-tool.js.map +1 -1
- package/dist/src/tools/glob.js +2 -1
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/mcp-client.js +16 -0
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/shell.js +1 -54
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +0 -1
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +20 -1
- package/dist/src/tools/smart-edit.js +114 -4
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +90 -6
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +6 -0
- package/dist/src/tools/tool-names.js +15 -0
- package/dist/src/tools/tool-names.js.map +1 -0
- package/dist/src/tools/tool-registry.test.js +10 -10
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/utils/flashFallback.test.js +2 -2
- package/dist/src/utils/flashFallback.test.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.js +10 -1
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.js +81 -0
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +13 -20
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.test.js +14 -0
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
- package/dist/src/utils/retry.d.ts +3 -1
- package/dist/src/utils/retry.js +13 -4
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +2 -2
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +0 -1
- package/dist/src/utils/shell-utils.js +1 -1
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/terminalSerializer.d.ts +1 -4
- package/dist/src/utils/terminalSerializer.js +3 -3
- package/dist/src/utils/terminalSerializer.js.map +1 -1
- package/dist/src/utils/tool-utils.js +2 -2
- package/dist/src/utils/tool-utils.js.map +1 -1
- package/dist/src/utils/tool-utils.test.js +0 -8
- package/dist/src/utils/tool-utils.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/dist/src/test-utils/tools.d.ts +0 -45
- package/dist/src/test-utils/tools.js +0 -105
- package/dist/src/test-utils/tools.js.map +0 -1
|
@@ -80,6 +80,7 @@ vi.mock('../ide/ideContext.js');
|
|
|
80
80
|
vi.mock('../telemetry/uiTelemetry.js', () => ({
|
|
81
81
|
uiTelemetryService: {
|
|
82
82
|
setLastPromptTokenCount: vi.fn(),
|
|
83
|
+
getLastPromptTokenCount: vi.fn(),
|
|
83
84
|
},
|
|
84
85
|
}));
|
|
85
86
|
/**
|
|
@@ -94,7 +95,7 @@ async function fromAsync(promise) {
|
|
|
94
95
|
}
|
|
95
96
|
return results;
|
|
96
97
|
}
|
|
97
|
-
describe('
|
|
98
|
+
describe('findCompressSplitPoint', () => {
|
|
98
99
|
it('should throw an error for non-positive numbers', () => {
|
|
99
100
|
expect(() => findCompressSplitPoint([], 0)).toThrow('Fraction must be between 0 and 1');
|
|
100
101
|
});
|
|
@@ -112,7 +113,7 @@ describe('findIndexAfterFraction', () => {
|
|
|
112
113
|
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
113
114
|
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
114
115
|
];
|
|
115
|
-
expect(findCompressSplitPoint(history, 0.5)).toBe(
|
|
116
|
+
expect(findCompressSplitPoint(history, 0.5)).toBe(4);
|
|
116
117
|
});
|
|
117
118
|
it('should handle a fraction of last index', () => {
|
|
118
119
|
const history = [
|
|
@@ -201,7 +202,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
201
202
|
mockContentGenerator = {
|
|
202
203
|
generateContent: mockGenerateContentFn,
|
|
203
204
|
generateContentStream: vi.fn(),
|
|
204
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 100 }),
|
|
205
205
|
batchEmbedContents: vi.fn(),
|
|
206
206
|
};
|
|
207
207
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
@@ -324,74 +324,128 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
324
324
|
function setup({ chatHistory = [
|
|
325
325
|
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
326
326
|
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
327
|
-
], } = {}) {
|
|
328
|
-
const
|
|
329
|
-
getHistory: vi.fn()
|
|
327
|
+
], originalTokenCount = 1000, summaryText = 'This is a summary.', } = {}) {
|
|
328
|
+
const mockOriginalChat = {
|
|
329
|
+
getHistory: vi.fn((_curated) => chatHistory),
|
|
330
330
|
setHistory: vi.fn(),
|
|
331
331
|
};
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
332
|
+
client['chat'] = mockOriginalChat;
|
|
333
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
334
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
335
|
+
candidates: [
|
|
336
|
+
{
|
|
337
|
+
content: {
|
|
338
|
+
role: 'model',
|
|
339
|
+
parts: [{ text: summaryText }],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
// Calculate what the new history will be
|
|
345
|
+
const splitPoint = findCompressSplitPoint(chatHistory, 0.7); // 1 - 0.3
|
|
346
|
+
const historyToKeep = chatHistory.slice(splitPoint);
|
|
347
|
+
// This is the history that the new chat will have.
|
|
348
|
+
// It includes the default startChat history + the extra history from tryCompressChat
|
|
349
|
+
const newCompressedHistory = [
|
|
350
|
+
// Mocked envParts + canned response from startChat
|
|
351
|
+
{
|
|
352
|
+
role: 'user',
|
|
353
|
+
parts: [{ text: 'Mocked env context' }],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
role: 'model',
|
|
357
|
+
parts: [{ text: 'Got it. Thanks for the context!' }],
|
|
358
|
+
},
|
|
359
|
+
// extraHistory from tryCompressChat
|
|
360
|
+
{
|
|
361
|
+
role: 'user',
|
|
362
|
+
parts: [{ text: summaryText }],
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
role: 'model',
|
|
366
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
367
|
+
},
|
|
368
|
+
...historyToKeep,
|
|
369
|
+
];
|
|
370
|
+
const mockNewChat = {
|
|
371
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
372
|
+
setHistory: vi.fn(),
|
|
373
|
+
};
|
|
374
|
+
client['startChat'] = vi
|
|
375
|
+
.fn()
|
|
376
|
+
.mockResolvedValue(mockNewChat);
|
|
377
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
378
|
+
const estimatedNewTokenCount = Math.floor(totalChars / 4);
|
|
379
|
+
return {
|
|
380
|
+
client,
|
|
381
|
+
mockOriginalChat,
|
|
382
|
+
mockNewChat,
|
|
383
|
+
estimatedNewTokenCount,
|
|
384
|
+
};
|
|
338
385
|
}
|
|
339
386
|
describe('when compression inflates the token count', () => {
|
|
340
387
|
it('allows compression to be forced/manual after a failure', async () => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
388
|
+
// Call 1 (Fails): Setup with a long summary to inflate tokens
|
|
389
|
+
const longSummary = 'long summary '.repeat(100);
|
|
390
|
+
const { client, estimatedNewTokenCount: inflatedTokenCount } = setup({
|
|
391
|
+
originalTokenCount: 100,
|
|
392
|
+
summaryText: longSummary,
|
|
344
393
|
});
|
|
394
|
+
expect(inflatedTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
345
395
|
await client.tryCompressChat('prompt-id-4', false); // Fails
|
|
346
|
-
|
|
396
|
+
// Call 2 (Forced): Re-setup with a short summary
|
|
397
|
+
const shortSummary = 'short';
|
|
398
|
+
const { estimatedNewTokenCount: compressedTokenCount } = setup({
|
|
399
|
+
originalTokenCount: 100,
|
|
400
|
+
summaryText: shortSummary,
|
|
401
|
+
});
|
|
402
|
+
expect(compressedTokenCount).toBeLessThanOrEqual(100); // Ensure setup is correct
|
|
403
|
+
const result = await client.tryCompressChat('prompt-id-4', true); // Forced
|
|
347
404
|
expect(result).toEqual({
|
|
348
405
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
349
|
-
newTokenCount:
|
|
350
|
-
originalTokenCount:
|
|
406
|
+
newTokenCount: compressedTokenCount,
|
|
407
|
+
originalTokenCount: 100,
|
|
351
408
|
});
|
|
352
409
|
});
|
|
353
410
|
it('yields the result even if the compression inflated the tokens', async () => {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
411
|
+
const longSummary = 'long summary '.repeat(100);
|
|
412
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
413
|
+
originalTokenCount: 100,
|
|
414
|
+
summaryText: longSummary,
|
|
357
415
|
});
|
|
416
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
358
417
|
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
359
418
|
expect(result).toEqual({
|
|
360
419
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
361
|
-
newTokenCount:
|
|
362
|
-
originalTokenCount:
|
|
420
|
+
newTokenCount: estimatedNewTokenCount,
|
|
421
|
+
originalTokenCount: 100,
|
|
363
422
|
});
|
|
364
|
-
|
|
365
|
-
expect(uiTelemetryService.setLastPromptTokenCount).
|
|
423
|
+
// IMPORTANT: The change in client.ts means setLastPromptTokenCount is NOT called on failure
|
|
424
|
+
expect(uiTelemetryService.setLastPromptTokenCount).not.toHaveBeenCalled();
|
|
366
425
|
});
|
|
367
426
|
it('does not manipulate the source chat', async () => {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
it('restores the history back to the original', async () => {
|
|
373
|
-
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
374
|
-
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
375
|
-
totalTokens: 999,
|
|
427
|
+
const longSummary = 'long summary '.repeat(100);
|
|
428
|
+
const { client, mockOriginalChat, estimatedNewTokenCount } = setup({
|
|
429
|
+
originalTokenCount: 100,
|
|
430
|
+
summaryText: longSummary,
|
|
376
431
|
});
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
];
|
|
382
|
-
const { client } = setup({
|
|
383
|
-
chatHistory: originalHistory,
|
|
384
|
-
});
|
|
385
|
-
const { compressionStatus } = await client.tryCompressChat('prompt-id-4', false);
|
|
386
|
-
expect(compressionStatus).toBe(CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT);
|
|
387
|
-
expect(client['chat']?.setHistory).toHaveBeenCalledWith(originalHistory);
|
|
432
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
433
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
434
|
+
// On failure, the chat should NOT be replaced
|
|
435
|
+
expect(client['chat']).toBe(mockOriginalChat);
|
|
388
436
|
});
|
|
389
437
|
it('will not attempt to compress context after a failure', async () => {
|
|
390
|
-
const
|
|
391
|
-
|
|
438
|
+
const longSummary = 'long summary '.repeat(100);
|
|
439
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
440
|
+
originalTokenCount: 100,
|
|
441
|
+
summaryText: longSummary,
|
|
442
|
+
});
|
|
443
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
444
|
+
await client.tryCompressChat('prompt-id-4', false); // This fails and sets hasFailedCompressionAttempt = true
|
|
445
|
+
// This call should now be a NOOP
|
|
392
446
|
const result = await client.tryCompressChat('prompt-id-5', false);
|
|
393
|
-
//
|
|
394
|
-
expect(
|
|
447
|
+
// generateContent (for summary) should only have been called once
|
|
448
|
+
expect(mockGenerateContentFn).toHaveBeenCalledTimes(1);
|
|
395
449
|
expect(result).toEqual({
|
|
396
450
|
compressionStatus: CompressionStatus.NOOP,
|
|
397
451
|
newTokenCount: 0,
|
|
@@ -405,17 +459,16 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
405
459
|
mockGetHistory.mockReturnValue([
|
|
406
460
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
407
461
|
]);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
462
|
+
const originalTokenCount = MOCKED_TOKEN_LIMIT * 0.699;
|
|
463
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
411
464
|
const initialChat = client.getChat();
|
|
412
465
|
const result = await client.tryCompressChat('prompt-id-2', false);
|
|
413
466
|
const newChat = client.getChat();
|
|
414
467
|
expect(tokenLimit).toHaveBeenCalled();
|
|
415
468
|
expect(result).toEqual({
|
|
416
469
|
compressionStatus: CompressionStatus.NOOP,
|
|
417
|
-
newTokenCount:
|
|
418
|
-
originalTokenCount
|
|
470
|
+
newTokenCount: originalTokenCount,
|
|
471
|
+
originalTokenCount,
|
|
419
472
|
});
|
|
420
473
|
expect(newChat).toBe(initialChat);
|
|
421
474
|
});
|
|
@@ -427,21 +480,40 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
427
480
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
428
481
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
429
482
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
]);
|
|
483
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
484
|
+
mockGetHistory.mockReturnValue(history);
|
|
433
485
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
486
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
487
|
+
// We need to control the estimated new token count.
|
|
488
|
+
// We mock startChat to return a chat with a known history.
|
|
489
|
+
const summaryText = 'This is a summary.';
|
|
490
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
491
|
+
const historyToKeep = history.slice(splitPoint);
|
|
492
|
+
const newCompressedHistory = [
|
|
493
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
494
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
495
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
496
|
+
{
|
|
497
|
+
role: 'model',
|
|
498
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
499
|
+
},
|
|
500
|
+
...historyToKeep,
|
|
501
|
+
];
|
|
502
|
+
const mockNewChat = {
|
|
503
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
504
|
+
};
|
|
505
|
+
client['startChat'] = vi
|
|
506
|
+
.fn()
|
|
507
|
+
.mockResolvedValue(mockNewChat);
|
|
508
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
509
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
438
510
|
// Mock the summary response from the chat
|
|
439
511
|
mockGenerateContentFn.mockResolvedValue({
|
|
440
512
|
candidates: [
|
|
441
513
|
{
|
|
442
514
|
content: {
|
|
443
515
|
role: 'model',
|
|
444
|
-
parts: [{ text:
|
|
516
|
+
parts: [{ text: summaryText }],
|
|
445
517
|
},
|
|
446
518
|
},
|
|
447
519
|
],
|
|
@@ -461,21 +533,39 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
461
533
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
462
534
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
463
535
|
});
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
]);
|
|
536
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
537
|
+
mockGetHistory.mockReturnValue(history);
|
|
467
538
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
539
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
540
|
+
// Mock summary and new chat
|
|
541
|
+
const summaryText = 'This is a summary.';
|
|
542
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
543
|
+
const historyToKeep = history.slice(splitPoint);
|
|
544
|
+
const newCompressedHistory = [
|
|
545
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
546
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
547
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
548
|
+
{
|
|
549
|
+
role: 'model',
|
|
550
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
551
|
+
},
|
|
552
|
+
...historyToKeep,
|
|
553
|
+
];
|
|
554
|
+
const mockNewChat = {
|
|
555
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
556
|
+
};
|
|
557
|
+
client['startChat'] = vi
|
|
558
|
+
.fn()
|
|
559
|
+
.mockResolvedValue(mockNewChat);
|
|
560
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
561
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
472
562
|
// Mock the summary response from the chat
|
|
473
563
|
mockGenerateContentFn.mockResolvedValue({
|
|
474
564
|
candidates: [
|
|
475
565
|
{
|
|
476
566
|
content: {
|
|
477
567
|
role: 'model',
|
|
478
|
-
parts: [{ text:
|
|
568
|
+
parts: [{ text: summaryText }],
|
|
479
569
|
},
|
|
480
570
|
},
|
|
481
571
|
],
|
|
@@ -497,7 +587,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
497
587
|
it('should not compress across a function call response', async () => {
|
|
498
588
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
499
589
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
500
|
-
|
|
590
|
+
const history = [
|
|
501
591
|
{ role: 'user', parts: [{ text: '...history 1...' }] },
|
|
502
592
|
{ role: 'model', parts: [{ text: '...history 2...' }] },
|
|
503
593
|
{ role: 'user', parts: [{ text: '...history 3...' }] },
|
|
@@ -514,19 +604,43 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
514
604
|
{ role: 'model', parts: [{ text: '...history 10...' }] },
|
|
515
605
|
// Instead we will break here.
|
|
516
606
|
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
517
|
-
]
|
|
607
|
+
];
|
|
608
|
+
mockGetHistory.mockReturnValue(history);
|
|
518
609
|
const originalTokenCount = 1000 * 0.7;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
610
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
611
|
+
// Mock summary and new chat
|
|
612
|
+
const summaryText = 'This is a summary.';
|
|
613
|
+
const splitPoint = findCompressSplitPoint(history, 0.7); // This should be 10
|
|
614
|
+
expect(splitPoint).toBe(10); // Verify split point logic
|
|
615
|
+
const historyToKeep = history.slice(splitPoint); // Should keep last user message
|
|
616
|
+
expect(historyToKeep).toEqual([
|
|
617
|
+
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
618
|
+
]);
|
|
619
|
+
const newCompressedHistory = [
|
|
620
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
621
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
622
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
623
|
+
{
|
|
624
|
+
role: 'model',
|
|
625
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
626
|
+
},
|
|
627
|
+
...historyToKeep,
|
|
628
|
+
];
|
|
629
|
+
const mockNewChat = {
|
|
630
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
631
|
+
};
|
|
632
|
+
client['startChat'] = vi
|
|
633
|
+
.fn()
|
|
634
|
+
.mockResolvedValue(mockNewChat);
|
|
635
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
636
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
523
637
|
// Mock the summary response from the chat
|
|
524
638
|
mockGenerateContentFn.mockResolvedValue({
|
|
525
639
|
candidates: [
|
|
526
640
|
{
|
|
527
641
|
content: {
|
|
528
642
|
role: 'model',
|
|
529
|
-
parts: [{ text:
|
|
643
|
+
parts: [{ text: summaryText }],
|
|
530
644
|
},
|
|
531
645
|
},
|
|
532
646
|
],
|
|
@@ -544,35 +658,53 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
544
658
|
});
|
|
545
659
|
// Assert that the chat was reset
|
|
546
660
|
expect(newChat).not.toBe(initialChat);
|
|
547
|
-
// 1. standard start context message
|
|
548
|
-
// 2. standard canned
|
|
549
|
-
// 3. compressed summary message
|
|
550
|
-
// 4. standard canned
|
|
551
|
-
// 5. The last user message (
|
|
661
|
+
// 1. standard start context message (env)
|
|
662
|
+
// 2. standard canned model response
|
|
663
|
+
// 3. compressed summary message (user)
|
|
664
|
+
// 4. standard canned model response
|
|
665
|
+
// 5. The last user message (historyToKeep)
|
|
552
666
|
expect(newChat.getHistory().length).toEqual(5);
|
|
553
667
|
});
|
|
554
668
|
it('should always trigger summarization when force is true, regardless of token count', async () => {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
669
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
670
|
+
mockGetHistory.mockReturnValue(history);
|
|
671
|
+
const originalTokenCount = 100; // Well below threshold, but > estimated new count
|
|
672
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
673
|
+
// Mock summary and new chat
|
|
674
|
+
const summaryText = 'This is a summary.';
|
|
675
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
676
|
+
const historyToKeep = history.slice(splitPoint);
|
|
677
|
+
const newCompressedHistory = [
|
|
678
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
679
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
680
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
681
|
+
{
|
|
682
|
+
role: 'model',
|
|
683
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
684
|
+
},
|
|
685
|
+
...historyToKeep,
|
|
686
|
+
];
|
|
687
|
+
const mockNewChat = {
|
|
688
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
689
|
+
};
|
|
690
|
+
client['startChat'] = vi
|
|
691
|
+
.fn()
|
|
692
|
+
.mockResolvedValue(mockNewChat);
|
|
693
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
694
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
563
695
|
// Mock the summary response from the chat
|
|
564
696
|
mockGenerateContentFn.mockResolvedValue({
|
|
565
697
|
candidates: [
|
|
566
698
|
{
|
|
567
699
|
content: {
|
|
568
700
|
role: 'model',
|
|
569
|
-
parts: [{ text:
|
|
701
|
+
parts: [{ text: summaryText }],
|
|
570
702
|
},
|
|
571
703
|
},
|
|
572
704
|
],
|
|
573
705
|
});
|
|
574
706
|
const initialChat = client.getChat();
|
|
575
|
-
const result = await client.tryCompressChat('prompt-id-1',
|
|
707
|
+
const result = await client.tryCompressChat('prompt-id-1', true); // force = true
|
|
576
708
|
const newChat = client.getChat();
|
|
577
709
|
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
578
710
|
expect(result).toEqual({
|
|
@@ -610,9 +742,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
610
742
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
611
743
|
},
|
|
612
744
|
{ compressionStatus: CompressionStatus.NOOP },
|
|
613
|
-
{
|
|
614
|
-
compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
|
|
615
|
-
},
|
|
616
745
|
])('does not emit a compression event when the status is $compressionStatus', async ({ compressionStatus }) => {
|
|
617
746
|
// Arrange
|
|
618
747
|
const mockStream = (async function* () {
|