@google/gemini-cli-core 0.5.0-nightly.20250906.968e9389 → 0.5.0-nightly.20250909.2b05cf3b
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/google-gemini-cli-core-0.3.4.tgz +0 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/src/code_assist/codeAssist.js +1 -1
- package/dist/src/code_assist/codeAssist.js.map +1 -1
- package/dist/src/code_assist/server.d.ts +1 -1
- package/dist/src/code_assist/server.js +24 -1
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +25 -0
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/code_assist/types.d.ts +17 -2
- package/dist/src/config/config.d.ts +8 -4
- package/dist/src/config/config.js +42 -27
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +81 -121
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/storage.d.ts +1 -0
- package/dist/src/config/storage.js +4 -0
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/config/storage.test.js +4 -0
- package/dist/src/config/storage.test.js.map +1 -1
- package/dist/src/core/client.d.ts +4 -15
- package/dist/src/core/client.js +56 -100
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +101 -321
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +2 -8
- package/dist/src/core/geminiChat.js +50 -52
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +247 -47
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/subagent.js +1 -3
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagent.test.js +3 -5
- package/dist/src/core/subagent.test.js.map +1 -1
- package/dist/src/fallback/handler.d.ts +7 -0
- package/dist/src/fallback/handler.js +51 -0
- package/dist/src/fallback/handler.js.map +1 -0
- package/dist/src/fallback/handler.test.js +130 -0
- package/dist/src/fallback/handler.test.js.map +1 -0
- package/dist/src/fallback/types.d.ts +14 -0
- package/dist/src/fallback/types.js +7 -0
- package/dist/src/fallback/types.js.map +1 -0
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/ide/process-utils.js +8 -1
- package/dist/src/ide/process-utils.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +2 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +7 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +15 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +1 -0
- package/dist/src/telemetry/constants.js +1 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +2 -1
- package/dist/src/telemetry/loggers.js +18 -1
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +64 -5
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +6 -0
- package/dist/src/telemetry/types.js +14 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.js +5 -21
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +4 -0
- package/dist/src/tools/ripGrep.js +17 -2
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +57 -5
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/utils/fileUtils.d.ts +1 -0
- package/dist/src/utils/fileUtils.js +10 -0
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +17 -1
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/flashFallback.test.d.ts +6 -0
- package/dist/src/utils/{flashFallback.integration.test.js → flashFallback.test.js} +31 -27
- package/dist/src/utils/flashFallback.test.js.map +1 -0
- package/dist/src/utils/nextSpeakerChecker.test.js +13 -42
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
- package/dist/google-gemini-cli-core-0.3.1.tgz +0 -0
- package/dist/src/utils/flashFallback.integration.test.js.map +0 -1
- /package/dist/src/{utils/flashFallback.integration.test.d.ts → fallback/handler.test.d.ts} +0 -0
|
@@ -4,11 +4,9 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
-
import { GoogleGenAI } from '@google/genai';
|
|
8
7
|
import { findIndexAfterFraction, isThinkingDefault, isThinkingSupported, GeminiClient, } from './client.js';
|
|
9
8
|
import { AuthType, } from './contentGenerator.js';
|
|
10
9
|
import {} from './geminiChat.js';
|
|
11
|
-
import { Config } from '../config/config.js';
|
|
12
10
|
import { CompressionStatus, GeminiEventType, Turn, } from './turn.js';
|
|
13
11
|
import { getCoreSystemPrompt } from './prompts.js';
|
|
14
12
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
|
@@ -41,11 +39,7 @@ vi.mock('node:fs', () => {
|
|
|
41
39
|
};
|
|
42
40
|
});
|
|
43
41
|
// --- Mocks ---
|
|
44
|
-
const mockChatCreateFn = vi.fn();
|
|
45
|
-
const mockGenerateContentFn = vi.fn();
|
|
46
|
-
const mockEmbedContentFn = vi.fn();
|
|
47
42
|
const mockTurnRunFn = vi.fn();
|
|
48
|
-
vi.mock('@google/genai');
|
|
49
43
|
vi.mock('./turn', async (importOriginal) => {
|
|
50
44
|
const actual = await importOriginal();
|
|
51
45
|
// Define a mock class that has the same shape as the real Turn
|
|
@@ -168,33 +162,24 @@ describe('isThinkingDefault', () => {
|
|
|
168
162
|
});
|
|
169
163
|
});
|
|
170
164
|
describe('Gemini Client (client.ts)', () => {
|
|
165
|
+
let mockContentGenerator;
|
|
166
|
+
let mockConfig;
|
|
171
167
|
let client;
|
|
168
|
+
let mockGenerateContentFn;
|
|
172
169
|
beforeEach(async () => {
|
|
173
170
|
vi.resetAllMocks();
|
|
171
|
+
mockGenerateContentFn = vi.fn().mockResolvedValue({
|
|
172
|
+
candidates: [{ content: { parts: [{ text: '{"key": "value"}' }] } }],
|
|
173
|
+
});
|
|
174
174
|
// Disable 429 simulation for tests
|
|
175
175
|
setSimulate429(false);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
embedContent: mockEmbedContentFn,
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
return mock;
|
|
187
|
-
});
|
|
188
|
-
mockChatCreateFn.mockResolvedValue({});
|
|
189
|
-
mockGenerateContentFn.mockResolvedValue({
|
|
190
|
-
candidates: [
|
|
191
|
-
{
|
|
192
|
-
content: {
|
|
193
|
-
parts: [{ text: '{"key": "value"}' }],
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
});
|
|
176
|
+
mockContentGenerator = {
|
|
177
|
+
generateContent: mockGenerateContentFn,
|
|
178
|
+
generateContentStream: vi.fn(),
|
|
179
|
+
countTokens: vi.fn(),
|
|
180
|
+
embedContent: vi.fn(),
|
|
181
|
+
batchEmbedContents: vi.fn(),
|
|
182
|
+
};
|
|
198
183
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
199
184
|
// that depends on a fully-formed Config object, we need to mock the
|
|
200
185
|
// entire implementation of Config for these tests.
|
|
@@ -209,7 +194,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
209
194
|
vertexai: false,
|
|
210
195
|
authType: AuthType.USE_GEMINI,
|
|
211
196
|
};
|
|
212
|
-
|
|
197
|
+
mockConfig = {
|
|
213
198
|
getContentGeneratorConfig: vi
|
|
214
199
|
.fn()
|
|
215
200
|
.mockReturnValue(contentGeneratorConfig),
|
|
@@ -237,6 +222,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
237
222
|
getDirectories: vi.fn().mockReturnValue(['/test/dir']),
|
|
238
223
|
}),
|
|
239
224
|
getGeminiClient: vi.fn(),
|
|
225
|
+
isInFallbackMode: vi.fn().mockReturnValue(false),
|
|
240
226
|
setFallbackMode: vi.fn(),
|
|
241
227
|
getChatCompression: vi.fn().mockReturnValue(undefined),
|
|
242
228
|
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
|
@@ -245,36 +231,15 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
245
231
|
storage: {
|
|
246
232
|
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
|
|
247
233
|
},
|
|
234
|
+
getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
|
|
248
235
|
};
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// and the constructor will use the mocked GoogleGenAI
|
|
253
|
-
client = new GeminiClient(new Config({ sessionId: 'test-session-id' }));
|
|
254
|
-
mockConfigObject.getGeminiClient.mockReturnValue(client);
|
|
255
|
-
await client.initialize(contentGeneratorConfig);
|
|
236
|
+
client = new GeminiClient(mockConfig);
|
|
237
|
+
await client.initialize();
|
|
238
|
+
vi.mocked(mockConfig.getGeminiClient).mockReturnValue(client);
|
|
256
239
|
});
|
|
257
240
|
afterEach(() => {
|
|
258
241
|
vi.restoreAllMocks();
|
|
259
242
|
});
|
|
260
|
-
// NOTE: The following tests for startChat were removed due to persistent issues with
|
|
261
|
-
// the @google/genai mock. Specifically, the mockChatCreateFn (representing instance.chats.create)
|
|
262
|
-
// was not being detected as called by the GeminiClient instance.
|
|
263
|
-
// This likely points to a subtle issue in how the GoogleGenerativeAI class constructor
|
|
264
|
-
// and its instance methods are mocked and then used by the class under test.
|
|
265
|
-
// For future debugging, ensure that the `this.client` in `GeminiClient` (which is an
|
|
266
|
-
// instance of the mocked GoogleGenerativeAI) correctly has its `chats.create` method
|
|
267
|
-
// pointing to `mockChatCreateFn`.
|
|
268
|
-
// it('startChat should call getCoreSystemPrompt with userMemory and pass to chats.create', async () => { ... });
|
|
269
|
-
// it('startChat should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
270
|
-
// NOTE: The following tests for generateJson were removed due to persistent issues with
|
|
271
|
-
// the @google/genai mock, similar to the startChat tests. The mockGenerateContentFn
|
|
272
|
-
// (representing instance.models.generateContent) was not being detected as called, or the mock
|
|
273
|
-
// was not preventing an actual API call (leading to API key errors).
|
|
274
|
-
// For future debugging, ensure `this.client.models.generateContent` in `GeminiClient` correctly
|
|
275
|
-
// uses the `mockGenerateContentFn`.
|
|
276
|
-
// it('generateJson should call getCoreSystemPrompt with userMemory and pass to generateContent', async () => { ... });
|
|
277
|
-
// it('generateJson should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
278
243
|
describe('generateEmbedding', () => {
|
|
279
244
|
const texts = ['hello world', 'goodbye world'];
|
|
280
245
|
const testEmbeddingModel = 'test-embedding-model';
|
|
@@ -283,16 +248,15 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
283
248
|
[0.1, 0.2, 0.3],
|
|
284
249
|
[0.4, 0.5, 0.6],
|
|
285
250
|
];
|
|
286
|
-
|
|
251
|
+
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
287
252
|
embeddings: [
|
|
288
253
|
{ values: mockEmbeddings[0] },
|
|
289
254
|
{ values: mockEmbeddings[1] },
|
|
290
255
|
],
|
|
291
|
-
};
|
|
292
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
256
|
+
});
|
|
293
257
|
const result = await client.generateEmbedding(texts);
|
|
294
|
-
expect(
|
|
295
|
-
expect(
|
|
258
|
+
expect(mockContentGenerator.embedContent).toHaveBeenCalledTimes(1);
|
|
259
|
+
expect(mockContentGenerator.embedContent).toHaveBeenCalledWith({
|
|
296
260
|
model: testEmbeddingModel,
|
|
297
261
|
contents: texts,
|
|
298
262
|
});
|
|
@@ -301,43 +265,38 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
301
265
|
it('should return an empty array if an empty array is passed', async () => {
|
|
302
266
|
const result = await client.generateEmbedding([]);
|
|
303
267
|
expect(result).toEqual([]);
|
|
304
|
-
expect(
|
|
268
|
+
expect(mockContentGenerator.embedContent).not.toHaveBeenCalled();
|
|
305
269
|
});
|
|
306
270
|
it('should throw an error if API response has no embeddings array', async () => {
|
|
307
|
-
|
|
271
|
+
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({});
|
|
308
272
|
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
309
273
|
});
|
|
310
274
|
it('should throw an error if API response has an empty embeddings array', async () => {
|
|
311
|
-
|
|
275
|
+
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
312
276
|
embeddings: [],
|
|
313
|
-
};
|
|
314
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
277
|
+
});
|
|
315
278
|
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
316
279
|
});
|
|
317
280
|
it('should throw an error if API returns a mismatched number of embeddings', async () => {
|
|
318
|
-
|
|
281
|
+
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
319
282
|
embeddings: [{ values: [1, 2, 3] }], // Only one for two texts
|
|
320
|
-
};
|
|
321
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
283
|
+
});
|
|
322
284
|
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned a mismatched number of embeddings. Expected 2, got 1.');
|
|
323
285
|
});
|
|
324
286
|
it('should throw an error if any embedding has nullish values', async () => {
|
|
325
|
-
|
|
287
|
+
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
326
288
|
embeddings: [{ values: [1, 2, 3] }, { values: undefined }], // Second one is bad
|
|
327
|
-
};
|
|
328
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
289
|
+
});
|
|
329
290
|
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 1: "goodbye world"');
|
|
330
291
|
});
|
|
331
292
|
it('should throw an error if any embedding has an empty values array', async () => {
|
|
332
|
-
|
|
293
|
+
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
333
294
|
embeddings: [{ values: [] }, { values: [1, 2, 3] }], // First one is bad
|
|
334
|
-
};
|
|
335
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
295
|
+
});
|
|
336
296
|
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 0: "hello world"');
|
|
337
297
|
});
|
|
338
298
|
it('should propagate errors from the API call', async () => {
|
|
339
|
-
|
|
340
|
-
mockEmbedContentFn.mockRejectedValue(apiError);
|
|
299
|
+
vi.mocked(mockContentGenerator.embedContent).mockRejectedValue(new Error('API Failure'));
|
|
341
300
|
await expect(client.generateEmbedding(texts)).rejects.toThrow('API Failure');
|
|
342
301
|
});
|
|
343
302
|
});
|
|
@@ -346,14 +305,11 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
346
305
|
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
347
306
|
const schema = { type: 'string' };
|
|
348
307
|
const abortSignal = new AbortController().signal;
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
generateContent: mockGenerateContentFn,
|
|
353
|
-
};
|
|
354
|
-
client['contentGenerator'] = mockGenerator;
|
|
308
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
309
|
+
totalTokens: 1,
|
|
310
|
+
});
|
|
355
311
|
await client.generateJson(contents, schema, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
356
|
-
expect(
|
|
312
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
357
313
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
358
314
|
config: {
|
|
359
315
|
abortSignal,
|
|
@@ -374,13 +330,11 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
374
330
|
const abortSignal = new AbortController().signal;
|
|
375
331
|
const customModel = 'custom-json-model';
|
|
376
332
|
const customConfig = { temperature: 0.9, topK: 20 };
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
};
|
|
381
|
-
client['contentGenerator'] = mockGenerator;
|
|
333
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
334
|
+
totalTokens: 1,
|
|
335
|
+
});
|
|
382
336
|
await client.generateJson(contents, schema, abortSignal, customModel, customConfig);
|
|
383
|
-
expect(
|
|
337
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
384
338
|
model: customModel,
|
|
385
339
|
config: {
|
|
386
340
|
abortSignal,
|
|
@@ -394,6 +348,20 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
394
348
|
contents,
|
|
395
349
|
}, 'test-session-id');
|
|
396
350
|
});
|
|
351
|
+
it('should use the Flash model when fallback mode is active', async () => {
|
|
352
|
+
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
353
|
+
const schema = { type: 'string' };
|
|
354
|
+
const abortSignal = new AbortController().signal;
|
|
355
|
+
const requestedModel = 'gemini-2.5-pro'; // A non-flash model
|
|
356
|
+
// Mock config to be in fallback mode
|
|
357
|
+
// We access the mock via the client instance which holds the mocked config
|
|
358
|
+
vi.spyOn(client['config'], 'isInFallbackMode').mockReturnValue(true);
|
|
359
|
+
await client.generateJson(contents, schema, abortSignal, requestedModel);
|
|
360
|
+
// Assert that the Flash model was used, not the requested model
|
|
361
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith(expect.objectContaining({
|
|
362
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
363
|
+
}), 'test-session-id');
|
|
364
|
+
});
|
|
397
365
|
});
|
|
398
366
|
describe('addHistory', () => {
|
|
399
367
|
it('should call chat.addHistory with the provided content', async () => {
|
|
@@ -432,16 +400,12 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
432
400
|
});
|
|
433
401
|
});
|
|
434
402
|
describe('tryCompressChat', () => {
|
|
435
|
-
const mockCountTokens = vi.fn();
|
|
436
403
|
const mockSendMessage = vi.fn();
|
|
437
404
|
const mockGetHistory = vi.fn();
|
|
438
405
|
beforeEach(() => {
|
|
439
406
|
vi.mock('./tokenLimits', () => ({
|
|
440
407
|
tokenLimit: vi.fn(),
|
|
441
408
|
}));
|
|
442
|
-
client['contentGenerator'] = {
|
|
443
|
-
countTokens: mockCountTokens,
|
|
444
|
-
};
|
|
445
409
|
client['chat'] = {
|
|
446
410
|
getHistory: mockGetHistory,
|
|
447
411
|
addHistory: vi.fn(),
|
|
@@ -458,23 +422,17 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
458
422
|
setHistory: vi.fn(),
|
|
459
423
|
sendMessage: vi.fn().mockResolvedValue({ text: 'Summary' }),
|
|
460
424
|
};
|
|
461
|
-
|
|
462
|
-
.fn()
|
|
425
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
463
426
|
.mockResolvedValueOnce({ totalTokens: 1000 })
|
|
464
427
|
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
465
|
-
const mockGenerator = {
|
|
466
|
-
countTokens: mockCountTokens,
|
|
467
|
-
};
|
|
468
428
|
client['chat'] = mockChat;
|
|
469
|
-
client['contentGenerator'] = mockGenerator;
|
|
470
429
|
client['startChat'] = vi.fn().mockResolvedValue({ ...mockChat });
|
|
471
|
-
return { client, mockChat
|
|
430
|
+
return { client, mockChat };
|
|
472
431
|
}
|
|
473
432
|
describe('when compression inflates the token count', () => {
|
|
474
|
-
it('uses the truncated history for compression');
|
|
475
433
|
it('allows compression to be forced/manual after a failure', async () => {
|
|
476
|
-
const { client
|
|
477
|
-
|
|
434
|
+
const { client } = setup();
|
|
435
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
478
436
|
totalTokens: 1000,
|
|
479
437
|
});
|
|
480
438
|
await client.tryCompressChat('prompt-id-4'); // Fails
|
|
@@ -487,6 +445,9 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
487
445
|
});
|
|
488
446
|
it('yields the result even if the compression inflated the tokens', async () => {
|
|
489
447
|
const { client } = setup();
|
|
448
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
449
|
+
totalTokens: 1000,
|
|
450
|
+
});
|
|
490
451
|
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
491
452
|
expect(result).toEqual({
|
|
492
453
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
@@ -501,7 +462,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
501
462
|
});
|
|
502
463
|
it('restores the history back to the original', async () => {
|
|
503
464
|
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
504
|
-
|
|
465
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
505
466
|
totalTokens: 999,
|
|
506
467
|
});
|
|
507
468
|
const originalHistory = [
|
|
@@ -517,11 +478,11 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
517
478
|
expect(client['chat']?.setHistory).toHaveBeenCalledWith(originalHistory);
|
|
518
479
|
});
|
|
519
480
|
it('will not attempt to compress context after a failure', async () => {
|
|
520
|
-
const { client
|
|
481
|
+
const { client } = setup();
|
|
521
482
|
await client.tryCompressChat('prompt-id-4');
|
|
522
483
|
const result = await client.tryCompressChat('prompt-id-5');
|
|
523
484
|
// it counts tokens for {original, compressed} and then never again
|
|
524
|
-
expect(
|
|
485
|
+
expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
|
|
525
486
|
expect(result).toEqual({
|
|
526
487
|
compressionStatus: CompressionStatus.NOOP,
|
|
527
488
|
newTokenCount: 0,
|
|
@@ -529,33 +490,13 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
529
490
|
});
|
|
530
491
|
});
|
|
531
492
|
});
|
|
532
|
-
it('attempts to compress with a maxOutputTokens set to the original token count', async () => {
|
|
533
|
-
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
534
|
-
mockCountTokens.mockResolvedValue({
|
|
535
|
-
totalTokens: 999,
|
|
536
|
-
});
|
|
537
|
-
mockGetHistory.mockReturnValue([
|
|
538
|
-
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
539
|
-
]);
|
|
540
|
-
// Mock the summary response from the chat
|
|
541
|
-
mockSendMessage.mockResolvedValue({
|
|
542
|
-
role: 'model',
|
|
543
|
-
parts: [{ text: 'This is a summary.' }],
|
|
544
|
-
});
|
|
545
|
-
await client.tryCompressChat('prompt-id-2', true);
|
|
546
|
-
expect(mockSendMessage).toHaveBeenCalledWith(expect.objectContaining({
|
|
547
|
-
config: expect.objectContaining({
|
|
548
|
-
maxOutputTokens: 999,
|
|
549
|
-
}),
|
|
550
|
-
}), 'prompt-id-2');
|
|
551
|
-
});
|
|
552
493
|
it('should not trigger summarization if token count is below threshold', async () => {
|
|
553
494
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
554
495
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
555
496
|
mockGetHistory.mockReturnValue([
|
|
556
497
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
557
498
|
]);
|
|
558
|
-
|
|
499
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
559
500
|
totalTokens: MOCKED_TOKEN_LIMIT * 0.699, // TOKEN_THRESHOLD_FOR_SUMMARIZATION = 0.7
|
|
560
501
|
});
|
|
561
502
|
const initialChat = client.getChat();
|
|
@@ -582,7 +523,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
582
523
|
]);
|
|
583
524
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
584
525
|
const newTokenCount = 100;
|
|
585
|
-
|
|
526
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
586
527
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
587
528
|
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
588
529
|
// Mock the summary response from the chat
|
|
@@ -608,7 +549,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
608
549
|
]);
|
|
609
550
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
610
551
|
const newTokenCount = 100;
|
|
611
|
-
|
|
552
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
612
553
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
613
554
|
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
614
555
|
// Mock the summary response from the chat
|
|
@@ -653,7 +594,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
653
594
|
]);
|
|
654
595
|
const originalTokenCount = 1000 * 0.7;
|
|
655
596
|
const newTokenCount = 100;
|
|
656
|
-
|
|
597
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
657
598
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
658
599
|
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
659
600
|
// Mock the summary response from the chat
|
|
@@ -687,7 +628,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
687
628
|
]);
|
|
688
629
|
const originalTokenCount = 10; // Well below threshold
|
|
689
630
|
const newTokenCount = 5;
|
|
690
|
-
|
|
631
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
691
632
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount })
|
|
692
633
|
.mockResolvedValueOnce({ totalTokens: newTokenCount });
|
|
693
634
|
// Mock the summary response from the chat
|
|
@@ -708,9 +649,14 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
708
649
|
expect(newChat).not.toBe(initialChat);
|
|
709
650
|
});
|
|
710
651
|
it('should use current model from config for token counting after sendMessage', async () => {
|
|
711
|
-
const initialModel =
|
|
712
|
-
|
|
713
|
-
|
|
652
|
+
const initialModel = mockConfig.getModel();
|
|
653
|
+
// mock the model has been changed between calls of `countTokens`
|
|
654
|
+
const firstCurrentModel = initialModel + '-changed-1';
|
|
655
|
+
const secondCurrentModel = initialModel + '-changed-2';
|
|
656
|
+
vi.mocked(mockConfig.getModel)
|
|
657
|
+
.mockReturnValueOnce(firstCurrentModel)
|
|
658
|
+
.mockReturnValueOnce(secondCurrentModel);
|
|
659
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
714
660
|
.mockResolvedValueOnce({ totalTokens: 100000 })
|
|
715
661
|
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
716
662
|
const mockSendMessage = vi.fn().mockResolvedValue({ text: 'Summary' });
|
|
@@ -723,25 +669,15 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
723
669
|
setHistory: vi.fn(),
|
|
724
670
|
sendMessage: mockSendMessage,
|
|
725
671
|
};
|
|
726
|
-
const mockGenerator = {
|
|
727
|
-
countTokens: mockCountTokens,
|
|
728
|
-
};
|
|
729
|
-
// mock the model has been changed between calls of `countTokens`
|
|
730
|
-
const firstCurrentModel = initialModel + '-changed-1';
|
|
731
|
-
const secondCurrentModel = initialModel + '-changed-2';
|
|
732
|
-
vi.spyOn(client['config'], 'getModel')
|
|
733
|
-
.mockReturnValueOnce(firstCurrentModel)
|
|
734
|
-
.mockReturnValueOnce(secondCurrentModel);
|
|
735
672
|
client['chat'] = mockChat;
|
|
736
|
-
client['contentGenerator'] = mockGenerator;
|
|
737
673
|
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
|
738
674
|
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
739
|
-
expect(
|
|
740
|
-
expect(
|
|
675
|
+
expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
|
|
676
|
+
expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(1, {
|
|
741
677
|
model: firstCurrentModel,
|
|
742
678
|
contents: mockChatHistory,
|
|
743
679
|
});
|
|
744
|
-
expect(
|
|
680
|
+
expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(2, {
|
|
745
681
|
model: secondCurrentModel,
|
|
746
682
|
contents: expect.any(Array),
|
|
747
683
|
});
|
|
@@ -755,20 +691,9 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
755
691
|
describe('sendMessageStream', () => {
|
|
756
692
|
it('emits a compression event when the context was automatically compressed', async () => {
|
|
757
693
|
// Arrange
|
|
758
|
-
|
|
694
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
759
695
|
yield { type: 'content', value: 'Hello' };
|
|
760
|
-
})();
|
|
761
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
762
|
-
const mockChat = {
|
|
763
|
-
addHistory: vi.fn(),
|
|
764
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
765
|
-
};
|
|
766
|
-
client['chat'] = mockChat;
|
|
767
|
-
const mockGenerator = {
|
|
768
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
769
|
-
generateContent: mockGenerateContentFn,
|
|
770
|
-
};
|
|
771
|
-
client['contentGenerator'] = mockGenerator;
|
|
696
|
+
})());
|
|
772
697
|
const compressionInfo = {
|
|
773
698
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
774
699
|
originalTokenCount: 1000,
|
|
@@ -798,16 +723,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
798
723
|
yield { type: 'content', value: 'Hello' };
|
|
799
724
|
})();
|
|
800
725
|
mockTurnRunFn.mockReturnValue(mockStream);
|
|
801
|
-
const mockChat = {
|
|
802
|
-
addHistory: vi.fn(),
|
|
803
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
804
|
-
};
|
|
805
|
-
client['chat'] = mockChat;
|
|
806
|
-
const mockGenerator = {
|
|
807
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
808
|
-
generateContent: mockGenerateContentFn,
|
|
809
|
-
};
|
|
810
|
-
client['contentGenerator'] = mockGenerator;
|
|
811
726
|
const compressionInfo = {
|
|
812
727
|
compressionStatus,
|
|
813
728
|
originalTokenCount: 1000,
|
|
@@ -846,21 +761,15 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
846
761
|
],
|
|
847
762
|
},
|
|
848
763
|
});
|
|
849
|
-
vi.
|
|
850
|
-
|
|
764
|
+
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
|
765
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
851
766
|
yield { type: 'content', value: 'Hello' };
|
|
852
|
-
})();
|
|
853
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
767
|
+
})());
|
|
854
768
|
const mockChat = {
|
|
855
769
|
addHistory: vi.fn(),
|
|
856
770
|
getHistory: vi.fn().mockReturnValue([]),
|
|
857
771
|
};
|
|
858
772
|
client['chat'] = mockChat;
|
|
859
|
-
const mockGenerator = {
|
|
860
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
861
|
-
generateContent: mockGenerateContentFn,
|
|
862
|
-
};
|
|
863
|
-
client['contentGenerator'] = mockGenerator;
|
|
864
773
|
const initialRequest = [{ text: 'Hi' }];
|
|
865
774
|
// Act
|
|
866
775
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -908,11 +817,6 @@ ${JSON.stringify({
|
|
|
908
817
|
getHistory: vi.fn().mockReturnValue([]),
|
|
909
818
|
};
|
|
910
819
|
client['chat'] = mockChat;
|
|
911
|
-
const mockGenerator = {
|
|
912
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
913
|
-
generateContent: mockGenerateContentFn,
|
|
914
|
-
};
|
|
915
|
-
client['contentGenerator'] = mockGenerator;
|
|
916
820
|
const initialRequest = [{ text: 'Hi' }];
|
|
917
821
|
// Act
|
|
918
822
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -948,11 +852,6 @@ ${JSON.stringify({
|
|
|
948
852
|
getHistory: vi.fn().mockReturnValue([]),
|
|
949
853
|
};
|
|
950
854
|
client['chat'] = mockChat;
|
|
951
|
-
const mockGenerator = {
|
|
952
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
953
|
-
generateContent: mockGenerateContentFn,
|
|
954
|
-
};
|
|
955
|
-
client['contentGenerator'] = mockGenerator;
|
|
956
855
|
const initialRequest = [{ text: 'Hi' }];
|
|
957
856
|
// Act
|
|
958
857
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -1008,11 +907,6 @@ ${JSON.stringify({
|
|
|
1008
907
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1009
908
|
};
|
|
1010
909
|
client['chat'] = mockChat;
|
|
1011
|
-
const mockGenerator = {
|
|
1012
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1013
|
-
generateContent: mockGenerateContentFn,
|
|
1014
|
-
};
|
|
1015
|
-
client['contentGenerator'] = mockGenerator;
|
|
1016
910
|
const initialRequest = [{ text: 'Hi' }];
|
|
1017
911
|
// Act
|
|
1018
912
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -1046,11 +940,6 @@ ${JSON.stringify({
|
|
|
1046
940
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1047
941
|
};
|
|
1048
942
|
client['chat'] = mockChat;
|
|
1049
|
-
const mockGenerator = {
|
|
1050
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1051
|
-
generateContent: mockGenerateContentFn,
|
|
1052
|
-
};
|
|
1053
|
-
client['contentGenerator'] = mockGenerator;
|
|
1054
943
|
// Act
|
|
1055
944
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
|
|
1056
945
|
// Consume the stream manually to get the final return value.
|
|
@@ -1083,11 +972,6 @@ ${JSON.stringify({
|
|
|
1083
972
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1084
973
|
};
|
|
1085
974
|
client['chat'] = mockChat;
|
|
1086
|
-
const mockGenerator = {
|
|
1087
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1088
|
-
generateContent: mockGenerateContentFn,
|
|
1089
|
-
};
|
|
1090
|
-
client['contentGenerator'] = mockGenerator;
|
|
1091
975
|
// Use a signal that never gets aborted
|
|
1092
976
|
const abortController = new AbortController();
|
|
1093
977
|
const signal = abortController.signal;
|
|
@@ -1150,11 +1034,6 @@ ${JSON.stringify({
|
|
|
1150
1034
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1151
1035
|
};
|
|
1152
1036
|
client['chat'] = mockChat;
|
|
1153
|
-
const mockGenerator = {
|
|
1154
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1155
|
-
generateContent: mockGenerateContentFn,
|
|
1156
|
-
};
|
|
1157
|
-
client['contentGenerator'] = mockGenerator;
|
|
1158
1037
|
// Act & Assert
|
|
1159
1038
|
// Run up to the limit
|
|
1160
1039
|
for (let i = 0; i < MAX_SESSION_TURNS; i++) {
|
|
@@ -1193,11 +1072,6 @@ ${JSON.stringify({
|
|
|
1193
1072
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1194
1073
|
};
|
|
1195
1074
|
client['chat'] = mockChat;
|
|
1196
|
-
const mockGenerator = {
|
|
1197
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1198
|
-
generateContent: mockGenerateContentFn,
|
|
1199
|
-
};
|
|
1200
|
-
client['contentGenerator'] = mockGenerator;
|
|
1201
1075
|
// Use a signal that never gets aborted
|
|
1202
1076
|
const abortController = new AbortController();
|
|
1203
1077
|
const signal = abortController.signal;
|
|
@@ -1261,11 +1135,6 @@ ${JSON.stringify({
|
|
|
1261
1135
|
]),
|
|
1262
1136
|
};
|
|
1263
1137
|
client['chat'] = mockChat;
|
|
1264
|
-
const mockGenerator = {
|
|
1265
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1266
|
-
generateContent: mockGenerateContentFn,
|
|
1267
|
-
};
|
|
1268
|
-
client['contentGenerator'] = mockGenerator;
|
|
1269
1138
|
});
|
|
1270
1139
|
const testCases = [
|
|
1271
1140
|
{
|
|
@@ -1475,10 +1344,6 @@ ${JSON.stringify({
|
|
|
1475
1344
|
sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
|
|
1476
1345
|
};
|
|
1477
1346
|
client['chat'] = mockChat;
|
|
1478
|
-
const mockGenerator = {
|
|
1479
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1480
|
-
};
|
|
1481
|
-
client['contentGenerator'] = mockGenerator;
|
|
1482
1347
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
1483
1348
|
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
|
1484
1349
|
workspaceState: {
|
|
@@ -1742,11 +1607,6 @@ ${JSON.stringify({
|
|
|
1742
1607
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1743
1608
|
};
|
|
1744
1609
|
client['chat'] = mockChat;
|
|
1745
|
-
const mockGenerator = {
|
|
1746
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1747
|
-
generateContent: mockGenerateContentFn,
|
|
1748
|
-
};
|
|
1749
|
-
client['contentGenerator'] = mockGenerator;
|
|
1750
1610
|
// Act
|
|
1751
1611
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
|
|
1752
1612
|
for await (const _ of stream) {
|
|
@@ -1772,11 +1632,6 @@ ${JSON.stringify({
|
|
|
1772
1632
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1773
1633
|
};
|
|
1774
1634
|
client['chat'] = mockChat;
|
|
1775
|
-
const mockGenerator = {
|
|
1776
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1777
|
-
generateContent: mockGenerateContentFn,
|
|
1778
|
-
};
|
|
1779
|
-
client['contentGenerator'] = mockGenerator;
|
|
1780
1635
|
// Act
|
|
1781
1636
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
|
|
1782
1637
|
for await (const _ of stream) {
|
|
@@ -1791,14 +1646,8 @@ ${JSON.stringify({
|
|
|
1791
1646
|
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
1792
1647
|
const generationConfig = { temperature: 0.5 };
|
|
1793
1648
|
const abortSignal = new AbortController().signal;
|
|
1794
|
-
// Mock countTokens
|
|
1795
|
-
const mockGenerator = {
|
|
1796
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
1797
|
-
generateContent: mockGenerateContentFn,
|
|
1798
|
-
};
|
|
1799
|
-
client['contentGenerator'] = mockGenerator;
|
|
1800
1649
|
await client.generateContent(contents, generationConfig, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
1801
|
-
expect(
|
|
1650
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
1802
1651
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1803
1652
|
config: {
|
|
1804
1653
|
abortSignal,
|
|
@@ -1814,98 +1663,29 @@ ${JSON.stringify({
|
|
|
1814
1663
|
const contents = [{ role: 'user', parts: [{ text: 'test' }] }];
|
|
1815
1664
|
const currentModel = initialModel + '-changed';
|
|
1816
1665
|
vi.spyOn(client['config'], 'getModel').mockReturnValueOnce(currentModel);
|
|
1817
|
-
const mockGenerator = {
|
|
1818
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
1819
|
-
generateContent: mockGenerateContentFn,
|
|
1820
|
-
};
|
|
1821
|
-
client['contentGenerator'] = mockGenerator;
|
|
1822
1666
|
await client.generateContent(contents, {}, new AbortController().signal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
1823
|
-
expect(
|
|
1667
|
+
expect(mockContentGenerator.generateContent).not.toHaveBeenCalledWith({
|
|
1824
1668
|
model: initialModel,
|
|
1825
1669
|
config: expect.any(Object),
|
|
1826
1670
|
contents,
|
|
1827
1671
|
});
|
|
1828
|
-
expect(
|
|
1672
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
1829
1673
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1830
1674
|
config: expect.any(Object),
|
|
1831
1675
|
contents,
|
|
1832
1676
|
}, 'test-session-id');
|
|
1833
1677
|
});
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
const
|
|
1838
|
-
const
|
|
1839
|
-
//
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
client['config'].setModel = vi.fn();
|
|
1846
|
-
const result = await client['handleFlashFallback'](AuthType.LOGIN_WITH_GOOGLE);
|
|
1847
|
-
expect(result).toBe(fallbackModel);
|
|
1848
|
-
expect(mockFallbackHandler).toHaveBeenCalledWith(currentModel, fallbackModel, undefined);
|
|
1849
|
-
});
|
|
1850
|
-
});
|
|
1851
|
-
describe('setHistory', () => {
|
|
1852
|
-
it('should strip thought signatures when stripThoughts is true', () => {
|
|
1853
|
-
const mockChat = {
|
|
1854
|
-
setHistory: vi.fn(),
|
|
1855
|
-
};
|
|
1856
|
-
client['chat'] = mockChat;
|
|
1857
|
-
const historyWithThoughts = [
|
|
1858
|
-
{
|
|
1859
|
-
role: 'user',
|
|
1860
|
-
parts: [{ text: 'hello' }],
|
|
1861
|
-
},
|
|
1862
|
-
{
|
|
1863
|
-
role: 'model',
|
|
1864
|
-
parts: [
|
|
1865
|
-
{ text: 'thinking...', thoughtSignature: 'thought-123' },
|
|
1866
|
-
{
|
|
1867
|
-
functionCall: { name: 'test', args: {} },
|
|
1868
|
-
thoughtSignature: 'thought-456',
|
|
1869
|
-
},
|
|
1870
|
-
],
|
|
1871
|
-
},
|
|
1872
|
-
];
|
|
1873
|
-
client.setHistory(historyWithThoughts, { stripThoughts: true });
|
|
1874
|
-
const expectedHistory = [
|
|
1875
|
-
{
|
|
1876
|
-
role: 'user',
|
|
1877
|
-
parts: [{ text: 'hello' }],
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
role: 'model',
|
|
1881
|
-
parts: [
|
|
1882
|
-
{ text: 'thinking...' },
|
|
1883
|
-
{ functionCall: { name: 'test', args: {} } },
|
|
1884
|
-
],
|
|
1885
|
-
},
|
|
1886
|
-
];
|
|
1887
|
-
expect(mockChat.setHistory).toHaveBeenCalledWith(expectedHistory);
|
|
1888
|
-
});
|
|
1889
|
-
it('should not strip thought signatures when stripThoughts is false', () => {
|
|
1890
|
-
const mockChat = {
|
|
1891
|
-
setHistory: vi.fn(),
|
|
1892
|
-
};
|
|
1893
|
-
client['chat'] = mockChat;
|
|
1894
|
-
const historyWithThoughts = [
|
|
1895
|
-
{
|
|
1896
|
-
role: 'user',
|
|
1897
|
-
parts: [{ text: 'hello' }],
|
|
1898
|
-
},
|
|
1899
|
-
{
|
|
1900
|
-
role: 'model',
|
|
1901
|
-
parts: [
|
|
1902
|
-
{ text: 'thinking...', thoughtSignature: 'thought-123' },
|
|
1903
|
-
{ text: 'ok', thoughtSignature: 'thought-456' },
|
|
1904
|
-
],
|
|
1905
|
-
},
|
|
1906
|
-
];
|
|
1907
|
-
client.setHistory(historyWithThoughts, { stripThoughts: false });
|
|
1908
|
-
expect(mockChat.setHistory).toHaveBeenCalledWith(historyWithThoughts);
|
|
1678
|
+
it('should use the Flash model when fallback mode is active', async () => {
|
|
1679
|
+
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
1680
|
+
const generationConfig = { temperature: 0.5 };
|
|
1681
|
+
const abortSignal = new AbortController().signal;
|
|
1682
|
+
const requestedModel = 'gemini-2.5-pro'; // A non-flash model
|
|
1683
|
+
// Mock config to be in fallback mode
|
|
1684
|
+
vi.spyOn(client['config'], 'isInFallbackMode').mockReturnValue(true);
|
|
1685
|
+
await client.generateContent(contents, generationConfig, abortSignal, requestedModel);
|
|
1686
|
+
expect(mockGenerateContentFn).toHaveBeenCalledWith(expect.objectContaining({
|
|
1687
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1688
|
+
}), 'test-session-id');
|
|
1909
1689
|
});
|
|
1910
1690
|
});
|
|
1911
1691
|
});
|