@google/gemini-cli-core 0.4.0-preview.2 → 0.5.0-nightly.20250908.4693137b

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.
Files changed (88) hide show
  1. package/dist/google-gemini-cli-core-0.3.4.tgz +0 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +2 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/code_assist/codeAssist.js +1 -1
  6. package/dist/src/code_assist/codeAssist.js.map +1 -1
  7. package/dist/src/code_assist/oauth-credential-storage.d.ts +27 -0
  8. package/dist/src/code_assist/oauth-credential-storage.js +112 -0
  9. package/dist/src/code_assist/oauth-credential-storage.js.map +1 -0
  10. package/dist/src/code_assist/oauth-credential-storage.test.d.ts +6 -0
  11. package/dist/src/code_assist/oauth-credential-storage.test.js +134 -0
  12. package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -0
  13. package/dist/src/code_assist/server.d.ts +1 -1
  14. package/dist/src/code_assist/server.js +24 -1
  15. package/dist/src/code_assist/server.js.map +1 -1
  16. package/dist/src/code_assist/server.test.js +25 -0
  17. package/dist/src/code_assist/server.test.js.map +1 -1
  18. package/dist/src/code_assist/types.d.ts +17 -2
  19. package/dist/src/config/config.d.ts +13 -1
  20. package/dist/src/config/config.js +38 -22
  21. package/dist/src/config/config.js.map +1 -1
  22. package/dist/src/config/config.test.js +9 -120
  23. package/dist/src/config/config.test.js.map +1 -1
  24. package/dist/src/config/storage.d.ts +1 -0
  25. package/dist/src/config/storage.js +2 -1
  26. package/dist/src/config/storage.js.map +1 -1
  27. package/dist/src/core/client.d.ts +4 -10
  28. package/dist/src/core/client.js +18 -45
  29. package/dist/src/core/client.js.map +1 -1
  30. package/dist/src/core/client.test.js +75 -288
  31. package/dist/src/core/client.test.js.map +1 -1
  32. package/dist/src/core/contentGenerator.test.js +1 -0
  33. package/dist/src/core/contentGenerator.test.js.map +1 -1
  34. package/dist/src/core/coreToolScheduler.d.ts +4 -0
  35. package/dist/src/core/coreToolScheduler.js +63 -2
  36. package/dist/src/core/coreToolScheduler.js.map +1 -1
  37. package/dist/src/core/coreToolScheduler.test.js +172 -5
  38. package/dist/src/core/coreToolScheduler.test.js.map +1 -1
  39. package/dist/src/core/geminiChat.d.ts +2 -3
  40. package/dist/src/core/geminiChat.js +19 -5
  41. package/dist/src/core/geminiChat.js.map +1 -1
  42. package/dist/src/core/geminiChat.test.js +78 -43
  43. package/dist/src/core/geminiChat.test.js.map +1 -1
  44. package/dist/src/core/loggingContentGenerator.js +3 -6
  45. package/dist/src/core/loggingContentGenerator.js.map +1 -1
  46. package/dist/src/core/nonInteractiveToolExecutor.test.js +8 -1
  47. package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
  48. package/dist/src/core/prompts.d.ts +5 -0
  49. package/dist/src/core/prompts.js +63 -42
  50. package/dist/src/core/prompts.js.map +1 -1
  51. package/dist/src/core/prompts.test.js +130 -1
  52. package/dist/src/core/prompts.test.js.map +1 -1
  53. package/dist/src/core/subagent.js +1 -3
  54. package/dist/src/core/subagent.js.map +1 -1
  55. package/dist/src/core/subagent.test.js +3 -5
  56. package/dist/src/core/subagent.test.js.map +1 -1
  57. package/dist/src/core/turn.d.ts +1 -0
  58. package/dist/src/core/turn.js.map +1 -1
  59. package/dist/src/generated/git-commit.d.ts +2 -2
  60. package/dist/src/generated/git-commit.js +2 -2
  61. package/dist/src/generated/git-commit.js.map +1 -1
  62. package/dist/src/ide/process-utils.d.ts +0 -1
  63. package/dist/src/ide/process-utils.js +36 -31
  64. package/dist/src/ide/process-utils.js.map +1 -1
  65. package/dist/src/mcp/oauth-provider.js +3 -3
  66. package/dist/src/mcp/oauth-provider.js.map +1 -1
  67. package/dist/src/mcp/oauth-provider.test.js +12 -12
  68. package/dist/src/mcp/oauth-provider.test.js.map +1 -1
  69. package/dist/src/mcp/oauth-token-storage.d.ts +8 -6
  70. package/dist/src/mcp/oauth-token-storage.js +24 -17
  71. package/dist/src/mcp/oauth-token-storage.js.map +1 -1
  72. package/dist/src/mcp/oauth-token-storage.test.js +18 -18
  73. package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
  74. package/dist/src/telemetry/loggers.test.js +26 -2
  75. package/dist/src/telemetry/loggers.test.js.map +1 -1
  76. package/dist/src/telemetry/types.js +4 -0
  77. package/dist/src/telemetry/types.js.map +1 -1
  78. package/dist/src/tools/mcp-client.js +5 -5
  79. package/dist/src/tools/mcp-client.js.map +1 -1
  80. package/dist/src/tools/read-file.js +7 -2
  81. package/dist/src/tools/read-file.js.map +1 -1
  82. package/dist/src/tools/read-file.test.js +29 -0
  83. package/dist/src/tools/read-file.test.js.map +1 -1
  84. package/dist/src/utils/nextSpeakerChecker.test.js +13 -42
  85. package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
  86. package/dist/tsconfig.tsbuildinfo +1 -1
  87. package/package.json +2 -1
  88. package/dist/google-gemini-cli-core-0.4.0-preview.1.tgz +0 -0
@@ -3,12 +3,10 @@
3
3
  * Copyright 2025 Google LLC
4
4
  * SPDX-License-Identifier: Apache-2.0
5
5
  */
6
- import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
7
- import { GoogleGenAI } from '@google/genai';
6
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
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,22 @@ describe('isThinkingDefault', () => {
168
162
  });
169
163
  });
170
164
  describe('Gemini Client (client.ts)', () => {
165
+ let mockContentGenerator;
166
+ let mockConfig;
171
167
  let client;
172
168
  beforeEach(async () => {
173
169
  vi.resetAllMocks();
174
170
  // Disable 429 simulation for tests
175
171
  setSimulate429(false);
176
- // Set up the mock for GoogleGenAI constructor and its methods
177
- const MockedGoogleGenAI = vi.mocked(GoogleGenAI);
178
- MockedGoogleGenAI.mockImplementation(() => {
179
- const mock = {
180
- chats: { create: mockChatCreateFn },
181
- models: {
182
- generateContent: mockGenerateContentFn,
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
- });
172
+ mockContentGenerator = {
173
+ generateContent: vi.fn().mockResolvedValue({
174
+ candidates: [{ content: { parts: [{ text: '{"key": "value"}' }] } }],
175
+ }),
176
+ generateContentStream: vi.fn(),
177
+ countTokens: vi.fn(),
178
+ embedContent: vi.fn(),
179
+ batchEmbedContents: vi.fn(),
180
+ };
198
181
  // Because the GeminiClient constructor kicks off an async process (startChat)
199
182
  // that depends on a fully-formed Config object, we need to mock the
200
183
  // entire implementation of Config for these tests.
@@ -209,7 +192,7 @@ describe('Gemini Client (client.ts)', () => {
209
192
  vertexai: false,
210
193
  authType: AuthType.USE_GEMINI,
211
194
  };
212
- const mockConfigObject = {
195
+ mockConfig = {
213
196
  getContentGeneratorConfig: vi
214
197
  .fn()
215
198
  .mockReturnValue(contentGeneratorConfig),
@@ -245,36 +228,15 @@ describe('Gemini Client (client.ts)', () => {
245
228
  storage: {
246
229
  getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
247
230
  },
231
+ getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
248
232
  };
249
- const MockedConfig = vi.mocked(Config, true);
250
- MockedConfig.mockImplementation(() => mockConfigObject);
251
- // We can instantiate the client here since Config is mocked
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);
233
+ client = new GeminiClient(mockConfig);
234
+ await client.initialize();
235
+ vi.mocked(mockConfig.getGeminiClient).mockReturnValue(client);
256
236
  });
257
237
  afterEach(() => {
258
238
  vi.restoreAllMocks();
259
239
  });
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
240
  describe('generateEmbedding', () => {
279
241
  const texts = ['hello world', 'goodbye world'];
280
242
  const testEmbeddingModel = 'test-embedding-model';
@@ -283,16 +245,15 @@ describe('Gemini Client (client.ts)', () => {
283
245
  [0.1, 0.2, 0.3],
284
246
  [0.4, 0.5, 0.6],
285
247
  ];
286
- const mockResponse = {
248
+ vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
287
249
  embeddings: [
288
250
  { values: mockEmbeddings[0] },
289
251
  { values: mockEmbeddings[1] },
290
252
  ],
291
- };
292
- mockEmbedContentFn.mockResolvedValue(mockResponse);
253
+ });
293
254
  const result = await client.generateEmbedding(texts);
294
- expect(mockEmbedContentFn).toHaveBeenCalledTimes(1);
295
- expect(mockEmbedContentFn).toHaveBeenCalledWith({
255
+ expect(mockContentGenerator.embedContent).toHaveBeenCalledTimes(1);
256
+ expect(mockContentGenerator.embedContent).toHaveBeenCalledWith({
296
257
  model: testEmbeddingModel,
297
258
  contents: texts,
298
259
  });
@@ -301,43 +262,38 @@ describe('Gemini Client (client.ts)', () => {
301
262
  it('should return an empty array if an empty array is passed', async () => {
302
263
  const result = await client.generateEmbedding([]);
303
264
  expect(result).toEqual([]);
304
- expect(mockEmbedContentFn).not.toHaveBeenCalled();
265
+ expect(mockContentGenerator.embedContent).not.toHaveBeenCalled();
305
266
  });
306
267
  it('should throw an error if API response has no embeddings array', async () => {
307
- mockEmbedContentFn.mockResolvedValue({}); // No `embeddings` key
268
+ vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({});
308
269
  await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
309
270
  });
310
271
  it('should throw an error if API response has an empty embeddings array', async () => {
311
- const mockResponse = {
272
+ vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
312
273
  embeddings: [],
313
- };
314
- mockEmbedContentFn.mockResolvedValue(mockResponse);
274
+ });
315
275
  await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
316
276
  });
317
277
  it('should throw an error if API returns a mismatched number of embeddings', async () => {
318
- const mockResponse = {
278
+ vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
319
279
  embeddings: [{ values: [1, 2, 3] }], // Only one for two texts
320
- };
321
- mockEmbedContentFn.mockResolvedValue(mockResponse);
280
+ });
322
281
  await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned a mismatched number of embeddings. Expected 2, got 1.');
323
282
  });
324
283
  it('should throw an error if any embedding has nullish values', async () => {
325
- const mockResponse = {
284
+ vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
326
285
  embeddings: [{ values: [1, 2, 3] }, { values: undefined }], // Second one is bad
327
- };
328
- mockEmbedContentFn.mockResolvedValue(mockResponse);
286
+ });
329
287
  await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 1: "goodbye world"');
330
288
  });
331
289
  it('should throw an error if any embedding has an empty values array', async () => {
332
- const mockResponse = {
290
+ vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
333
291
  embeddings: [{ values: [] }, { values: [1, 2, 3] }], // First one is bad
334
- };
335
- mockEmbedContentFn.mockResolvedValue(mockResponse);
292
+ });
336
293
  await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 0: "hello world"');
337
294
  });
338
295
  it('should propagate errors from the API call', async () => {
339
- const apiError = new Error('API Failure');
340
- mockEmbedContentFn.mockRejectedValue(apiError);
296
+ vi.mocked(mockContentGenerator.embedContent).mockRejectedValue(new Error('API Failure'));
341
297
  await expect(client.generateEmbedding(texts)).rejects.toThrow('API Failure');
342
298
  });
343
299
  });
@@ -346,14 +302,11 @@ describe('Gemini Client (client.ts)', () => {
346
302
  const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
347
303
  const schema = { type: 'string' };
348
304
  const abortSignal = new AbortController().signal;
349
- // Mock countTokens
350
- const mockGenerator = {
351
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
352
- generateContent: mockGenerateContentFn,
353
- };
354
- client['contentGenerator'] = mockGenerator;
305
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
306
+ totalTokens: 1,
307
+ });
355
308
  await client.generateJson(contents, schema, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
356
- expect(mockGenerateContentFn).toHaveBeenCalledWith({
309
+ expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
357
310
  model: DEFAULT_GEMINI_FLASH_MODEL,
358
311
  config: {
359
312
  abortSignal,
@@ -374,13 +327,11 @@ describe('Gemini Client (client.ts)', () => {
374
327
  const abortSignal = new AbortController().signal;
375
328
  const customModel = 'custom-json-model';
376
329
  const customConfig = { temperature: 0.9, topK: 20 };
377
- const mockGenerator = {
378
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
379
- generateContent: mockGenerateContentFn,
380
- };
381
- client['contentGenerator'] = mockGenerator;
330
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
331
+ totalTokens: 1,
332
+ });
382
333
  await client.generateJson(contents, schema, abortSignal, customModel, customConfig);
383
- expect(mockGenerateContentFn).toHaveBeenCalledWith({
334
+ expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
384
335
  model: customModel,
385
336
  config: {
386
337
  abortSignal,
@@ -432,16 +383,12 @@ describe('Gemini Client (client.ts)', () => {
432
383
  });
433
384
  });
434
385
  describe('tryCompressChat', () => {
435
- const mockCountTokens = vi.fn();
436
386
  const mockSendMessage = vi.fn();
437
387
  const mockGetHistory = vi.fn();
438
388
  beforeEach(() => {
439
389
  vi.mock('./tokenLimits', () => ({
440
390
  tokenLimit: vi.fn(),
441
391
  }));
442
- client['contentGenerator'] = {
443
- countTokens: mockCountTokens,
444
- };
445
392
  client['chat'] = {
446
393
  getHistory: mockGetHistory,
447
394
  addHistory: vi.fn(),
@@ -458,23 +405,17 @@ describe('Gemini Client (client.ts)', () => {
458
405
  setHistory: vi.fn(),
459
406
  sendMessage: vi.fn().mockResolvedValue({ text: 'Summary' }),
460
407
  };
461
- const mockCountTokens = vi
462
- .fn()
408
+ vi.mocked(mockContentGenerator.countTokens)
463
409
  .mockResolvedValueOnce({ totalTokens: 1000 })
464
410
  .mockResolvedValueOnce({ totalTokens: 5000 });
465
- const mockGenerator = {
466
- countTokens: mockCountTokens,
467
- };
468
411
  client['chat'] = mockChat;
469
- client['contentGenerator'] = mockGenerator;
470
412
  client['startChat'] = vi.fn().mockResolvedValue({ ...mockChat });
471
- return { client, mockChat, mockGenerator };
413
+ return { client, mockChat };
472
414
  }
473
415
  describe('when compression inflates the token count', () => {
474
- it('uses the truncated history for compression');
475
416
  it('allows compression to be forced/manual after a failure', async () => {
476
- const { client, mockGenerator } = setup();
477
- mockGenerator.countTokens?.mockResolvedValue({
417
+ const { client } = setup();
418
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
478
419
  totalTokens: 1000,
479
420
  });
480
421
  await client.tryCompressChat('prompt-id-4'); // Fails
@@ -487,6 +428,9 @@ describe('Gemini Client (client.ts)', () => {
487
428
  });
488
429
  it('yields the result even if the compression inflated the tokens', async () => {
489
430
  const { client } = setup();
431
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
432
+ totalTokens: 1000,
433
+ });
490
434
  const result = await client.tryCompressChat('prompt-id-4', true);
491
435
  expect(result).toEqual({
492
436
  compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
@@ -501,7 +445,7 @@ describe('Gemini Client (client.ts)', () => {
501
445
  });
502
446
  it('restores the history back to the original', async () => {
503
447
  vi.mocked(tokenLimit).mockReturnValue(1000);
504
- mockCountTokens.mockResolvedValue({
448
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
505
449
  totalTokens: 999,
506
450
  });
507
451
  const originalHistory = [
@@ -517,11 +461,11 @@ describe('Gemini Client (client.ts)', () => {
517
461
  expect(client['chat']?.setHistory).toHaveBeenCalledWith(originalHistory);
518
462
  });
519
463
  it('will not attempt to compress context after a failure', async () => {
520
- const { client, mockGenerator } = setup();
464
+ const { client } = setup();
521
465
  await client.tryCompressChat('prompt-id-4');
522
466
  const result = await client.tryCompressChat('prompt-id-5');
523
467
  // it counts tokens for {original, compressed} and then never again
524
- expect(mockGenerator.countTokens).toHaveBeenCalledTimes(2);
468
+ expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
525
469
  expect(result).toEqual({
526
470
  compressionStatus: CompressionStatus.NOOP,
527
471
  newTokenCount: 0,
@@ -531,7 +475,7 @@ describe('Gemini Client (client.ts)', () => {
531
475
  });
532
476
  it('attempts to compress with a maxOutputTokens set to the original token count', async () => {
533
477
  vi.mocked(tokenLimit).mockReturnValue(1000);
534
- mockCountTokens.mockResolvedValue({
478
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
535
479
  totalTokens: 999,
536
480
  });
537
481
  mockGetHistory.mockReturnValue([
@@ -555,7 +499,7 @@ describe('Gemini Client (client.ts)', () => {
555
499
  mockGetHistory.mockReturnValue([
556
500
  { role: 'user', parts: [{ text: '...history...' }] },
557
501
  ]);
558
- mockCountTokens.mockResolvedValue({
502
+ vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
559
503
  totalTokens: MOCKED_TOKEN_LIMIT * 0.699, // TOKEN_THRESHOLD_FOR_SUMMARIZATION = 0.7
560
504
  });
561
505
  const initialChat = client.getChat();
@@ -582,7 +526,7 @@ describe('Gemini Client (client.ts)', () => {
582
526
  ]);
583
527
  const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
584
528
  const newTokenCount = 100;
585
- mockCountTokens
529
+ vi.mocked(mockContentGenerator.countTokens)
586
530
  .mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
587
531
  .mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
588
532
  // Mock the summary response from the chat
@@ -608,7 +552,7 @@ describe('Gemini Client (client.ts)', () => {
608
552
  ]);
609
553
  const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
610
554
  const newTokenCount = 100;
611
- mockCountTokens
555
+ vi.mocked(mockContentGenerator.countTokens)
612
556
  .mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
613
557
  .mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
614
558
  // Mock the summary response from the chat
@@ -653,7 +597,7 @@ describe('Gemini Client (client.ts)', () => {
653
597
  ]);
654
598
  const originalTokenCount = 1000 * 0.7;
655
599
  const newTokenCount = 100;
656
- mockCountTokens
600
+ vi.mocked(mockContentGenerator.countTokens)
657
601
  .mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
658
602
  .mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
659
603
  // Mock the summary response from the chat
@@ -687,7 +631,7 @@ describe('Gemini Client (client.ts)', () => {
687
631
  ]);
688
632
  const originalTokenCount = 10; // Well below threshold
689
633
  const newTokenCount = 5;
690
- mockCountTokens
634
+ vi.mocked(mockContentGenerator.countTokens)
691
635
  .mockResolvedValueOnce({ totalTokens: originalTokenCount })
692
636
  .mockResolvedValueOnce({ totalTokens: newTokenCount });
693
637
  // Mock the summary response from the chat
@@ -708,9 +652,14 @@ describe('Gemini Client (client.ts)', () => {
708
652
  expect(newChat).not.toBe(initialChat);
709
653
  });
710
654
  it('should use current model from config for token counting after sendMessage', async () => {
711
- const initialModel = client['config'].getModel();
712
- const mockCountTokens = vi
713
- .fn()
655
+ const initialModel = mockConfig.getModel();
656
+ // mock the model has been changed between calls of `countTokens`
657
+ const firstCurrentModel = initialModel + '-changed-1';
658
+ const secondCurrentModel = initialModel + '-changed-2';
659
+ vi.mocked(mockConfig.getModel)
660
+ .mockReturnValueOnce(firstCurrentModel)
661
+ .mockReturnValueOnce(secondCurrentModel);
662
+ vi.mocked(mockContentGenerator.countTokens)
714
663
  .mockResolvedValueOnce({ totalTokens: 100000 })
715
664
  .mockResolvedValueOnce({ totalTokens: 5000 });
716
665
  const mockSendMessage = vi.fn().mockResolvedValue({ text: 'Summary' });
@@ -723,25 +672,15 @@ describe('Gemini Client (client.ts)', () => {
723
672
  setHistory: vi.fn(),
724
673
  sendMessage: mockSendMessage,
725
674
  };
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
675
  client['chat'] = mockChat;
736
- client['contentGenerator'] = mockGenerator;
737
676
  client['startChat'] = vi.fn().mockResolvedValue(mockChat);
738
677
  const result = await client.tryCompressChat('prompt-id-4', true);
739
- expect(mockCountTokens).toHaveBeenCalledTimes(2);
740
- expect(mockCountTokens).toHaveBeenNthCalledWith(1, {
678
+ expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
679
+ expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(1, {
741
680
  model: firstCurrentModel,
742
681
  contents: mockChatHistory,
743
682
  });
744
- expect(mockCountTokens).toHaveBeenNthCalledWith(2, {
683
+ expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(2, {
745
684
  model: secondCurrentModel,
746
685
  contents: expect.any(Array),
747
686
  });
@@ -755,20 +694,9 @@ describe('Gemini Client (client.ts)', () => {
755
694
  describe('sendMessageStream', () => {
756
695
  it('emits a compression event when the context was automatically compressed', async () => {
757
696
  // Arrange
758
- const mockStream = (async function* () {
697
+ mockTurnRunFn.mockReturnValue((async function* () {
759
698
  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;
699
+ })());
772
700
  const compressionInfo = {
773
701
  compressionStatus: CompressionStatus.COMPRESSED,
774
702
  originalTokenCount: 1000,
@@ -798,16 +726,6 @@ describe('Gemini Client (client.ts)', () => {
798
726
  yield { type: 'content', value: 'Hello' };
799
727
  })();
800
728
  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
729
  const compressionInfo = {
812
730
  compressionStatus,
813
731
  originalTokenCount: 1000,
@@ -846,21 +764,15 @@ describe('Gemini Client (client.ts)', () => {
846
764
  ],
847
765
  },
848
766
  });
849
- vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
850
- const mockStream = (async function* () {
767
+ vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
768
+ mockTurnRunFn.mockReturnValue((async function* () {
851
769
  yield { type: 'content', value: 'Hello' };
852
- })();
853
- mockTurnRunFn.mockReturnValue(mockStream);
770
+ })());
854
771
  const mockChat = {
855
772
  addHistory: vi.fn(),
856
773
  getHistory: vi.fn().mockReturnValue([]),
857
774
  };
858
775
  client['chat'] = mockChat;
859
- const mockGenerator = {
860
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
861
- generateContent: mockGenerateContentFn,
862
- };
863
- client['contentGenerator'] = mockGenerator;
864
776
  const initialRequest = [{ text: 'Hi' }];
865
777
  // Act
866
778
  const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
@@ -908,11 +820,6 @@ ${JSON.stringify({
908
820
  getHistory: vi.fn().mockReturnValue([]),
909
821
  };
910
822
  client['chat'] = mockChat;
911
- const mockGenerator = {
912
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
913
- generateContent: mockGenerateContentFn,
914
- };
915
- client['contentGenerator'] = mockGenerator;
916
823
  const initialRequest = [{ text: 'Hi' }];
917
824
  // Act
918
825
  const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
@@ -948,11 +855,6 @@ ${JSON.stringify({
948
855
  getHistory: vi.fn().mockReturnValue([]),
949
856
  };
950
857
  client['chat'] = mockChat;
951
- const mockGenerator = {
952
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
953
- generateContent: mockGenerateContentFn,
954
- };
955
- client['contentGenerator'] = mockGenerator;
956
858
  const initialRequest = [{ text: 'Hi' }];
957
859
  // Act
958
860
  const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
@@ -1008,11 +910,6 @@ ${JSON.stringify({
1008
910
  getHistory: vi.fn().mockReturnValue([]),
1009
911
  };
1010
912
  client['chat'] = mockChat;
1011
- const mockGenerator = {
1012
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1013
- generateContent: mockGenerateContentFn,
1014
- };
1015
- client['contentGenerator'] = mockGenerator;
1016
913
  const initialRequest = [{ text: 'Hi' }];
1017
914
  // Act
1018
915
  const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
@@ -1046,11 +943,6 @@ ${JSON.stringify({
1046
943
  getHistory: vi.fn().mockReturnValue([]),
1047
944
  };
1048
945
  client['chat'] = mockChat;
1049
- const mockGenerator = {
1050
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1051
- generateContent: mockGenerateContentFn,
1052
- };
1053
- client['contentGenerator'] = mockGenerator;
1054
946
  // Act
1055
947
  const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
1056
948
  // Consume the stream manually to get the final return value.
@@ -1083,11 +975,6 @@ ${JSON.stringify({
1083
975
  getHistory: vi.fn().mockReturnValue([]),
1084
976
  };
1085
977
  client['chat'] = mockChat;
1086
- const mockGenerator = {
1087
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1088
- generateContent: mockGenerateContentFn,
1089
- };
1090
- client['contentGenerator'] = mockGenerator;
1091
978
  // Use a signal that never gets aborted
1092
979
  const abortController = new AbortController();
1093
980
  const signal = abortController.signal;
@@ -1150,11 +1037,6 @@ ${JSON.stringify({
1150
1037
  getHistory: vi.fn().mockReturnValue([]),
1151
1038
  };
1152
1039
  client['chat'] = mockChat;
1153
- const mockGenerator = {
1154
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1155
- generateContent: mockGenerateContentFn,
1156
- };
1157
- client['contentGenerator'] = mockGenerator;
1158
1040
  // Act & Assert
1159
1041
  // Run up to the limit
1160
1042
  for (let i = 0; i < MAX_SESSION_TURNS; i++) {
@@ -1193,11 +1075,6 @@ ${JSON.stringify({
1193
1075
  getHistory: vi.fn().mockReturnValue([]),
1194
1076
  };
1195
1077
  client['chat'] = mockChat;
1196
- const mockGenerator = {
1197
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1198
- generateContent: mockGenerateContentFn,
1199
- };
1200
- client['contentGenerator'] = mockGenerator;
1201
1078
  // Use a signal that never gets aborted
1202
1079
  const abortController = new AbortController();
1203
1080
  const signal = abortController.signal;
@@ -1261,11 +1138,6 @@ ${JSON.stringify({
1261
1138
  ]),
1262
1139
  };
1263
1140
  client['chat'] = mockChat;
1264
- const mockGenerator = {
1265
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1266
- generateContent: mockGenerateContentFn,
1267
- };
1268
- client['contentGenerator'] = mockGenerator;
1269
1141
  });
1270
1142
  const testCases = [
1271
1143
  {
@@ -1475,10 +1347,6 @@ ${JSON.stringify({
1475
1347
  sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
1476
1348
  };
1477
1349
  client['chat'] = mockChat;
1478
- const mockGenerator = {
1479
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1480
- };
1481
- client['contentGenerator'] = mockGenerator;
1482
1350
  vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
1483
1351
  vi.mocked(ideContext.getIdeContext).mockReturnValue({
1484
1352
  workspaceState: {
@@ -1742,11 +1610,6 @@ ${JSON.stringify({
1742
1610
  getHistory: vi.fn().mockReturnValue([]),
1743
1611
  };
1744
1612
  client['chat'] = mockChat;
1745
- const mockGenerator = {
1746
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1747
- generateContent: mockGenerateContentFn,
1748
- };
1749
- client['contentGenerator'] = mockGenerator;
1750
1613
  // Act
1751
1614
  const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
1752
1615
  for await (const _ of stream) {
@@ -1772,11 +1635,6 @@ ${JSON.stringify({
1772
1635
  getHistory: vi.fn().mockReturnValue([]),
1773
1636
  };
1774
1637
  client['chat'] = mockChat;
1775
- const mockGenerator = {
1776
- countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
1777
- generateContent: mockGenerateContentFn,
1778
- };
1779
- client['contentGenerator'] = mockGenerator;
1780
1638
  // Act
1781
1639
  const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
1782
1640
  for await (const _ of stream) {
@@ -1791,14 +1649,8 @@ ${JSON.stringify({
1791
1649
  const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
1792
1650
  const generationConfig = { temperature: 0.5 };
1793
1651
  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
1652
  await client.generateContent(contents, generationConfig, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
1801
- expect(mockGenerateContentFn).toHaveBeenCalledWith({
1653
+ expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
1802
1654
  model: DEFAULT_GEMINI_FLASH_MODEL,
1803
1655
  config: {
1804
1656
  abortSignal,
@@ -1814,18 +1666,13 @@ ${JSON.stringify({
1814
1666
  const contents = [{ role: 'user', parts: [{ text: 'test' }] }];
1815
1667
  const currentModel = initialModel + '-changed';
1816
1668
  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
1669
  await client.generateContent(contents, {}, new AbortController().signal, DEFAULT_GEMINI_FLASH_MODEL);
1823
- expect(mockGenerateContentFn).not.toHaveBeenCalledWith({
1670
+ expect(mockContentGenerator.generateContent).not.toHaveBeenCalledWith({
1824
1671
  model: initialModel,
1825
1672
  config: expect.any(Object),
1826
1673
  contents,
1827
1674
  });
1828
- expect(mockGenerateContentFn).toHaveBeenCalledWith({
1675
+ expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
1829
1676
  model: DEFAULT_GEMINI_FLASH_MODEL,
1830
1677
  config: expect.any(Object),
1831
1678
  contents,
@@ -1848,65 +1695,5 @@ ${JSON.stringify({
1848
1695
  expect(mockFallbackHandler).toHaveBeenCalledWith(currentModel, fallbackModel, undefined);
1849
1696
  });
1850
1697
  });
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);
1909
- });
1910
- });
1911
1698
  });
1912
1699
  //# sourceMappingURL=client.test.js.map