@google/gemini-cli-core 0.21.0-nightly.20251217.db643e916 → 0.21.0-nightly.20251219.70696e364

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 (212) hide show
  1. package/dist/google-gemini-cli-core-0.21.0-nightly.20251218.739c02bd6.tgz +0 -0
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/agents/codebase-investigator.d.ts +2 -2
  6. package/dist/src/agents/codebase-investigator.js +6 -5
  7. package/dist/src/agents/codebase-investigator.js.map +1 -1
  8. package/dist/src/agents/codebase-investigator.test.js +2 -2
  9. package/dist/src/agents/codebase-investigator.test.js.map +1 -1
  10. package/dist/src/agents/delegate-to-agent-tool.js +8 -4
  11. package/dist/src/agents/delegate-to-agent-tool.js.map +1 -1
  12. package/dist/src/agents/delegate-to-agent-tool.test.js +5 -4
  13. package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -1
  14. package/dist/src/agents/{executor.d.ts → local-executor.d.ts} +5 -11
  15. package/dist/src/agents/{executor.js → local-executor.js} +75 -51
  16. package/dist/src/agents/local-executor.js.map +1 -0
  17. package/dist/src/agents/{executor.test.js → local-executor.test.js} +61 -45
  18. package/dist/src/agents/local-executor.test.js.map +1 -0
  19. package/dist/src/agents/{invocation.d.ts → local-invocation.d.ts} +5 -6
  20. package/dist/src/agents/{invocation.js → local-invocation.js} +8 -9
  21. package/dist/src/agents/local-invocation.js.map +1 -0
  22. package/dist/src/agents/{invocation.test.js → local-invocation.test.js} +18 -17
  23. package/dist/src/agents/local-invocation.test.js.map +1 -0
  24. package/dist/src/agents/registry.d.ts +1 -0
  25. package/dist/src/agents/registry.js +77 -28
  26. package/dist/src/agents/registry.js.map +1 -1
  27. package/dist/src/agents/registry.test.js +130 -5
  28. package/dist/src/agents/registry.test.js.map +1 -1
  29. package/dist/src/agents/remote-invocation.d.ts +21 -0
  30. package/dist/src/agents/remote-invocation.js +31 -0
  31. package/dist/src/agents/remote-invocation.js.map +1 -0
  32. package/dist/src/agents/remote-invocation.test.d.ts +6 -0
  33. package/dist/src/agents/remote-invocation.test.js +35 -0
  34. package/dist/src/agents/remote-invocation.test.js.map +1 -0
  35. package/dist/src/agents/subagent-tool-wrapper.js +7 -3
  36. package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
  37. package/dist/src/agents/subagent-tool-wrapper.test.js +10 -9
  38. package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
  39. package/dist/src/agents/toml-loader.d.ts +65 -0
  40. package/dist/src/agents/toml-loader.js +176 -0
  41. package/dist/src/agents/toml-loader.js.map +1 -0
  42. package/dist/src/agents/toml-loader.test.d.ts +6 -0
  43. package/dist/src/agents/toml-loader.test.js +190 -0
  44. package/dist/src/agents/toml-loader.test.js.map +1 -0
  45. package/dist/src/agents/types.d.ts +12 -4
  46. package/dist/src/availability/modelAvailabilityService.d.ts +2 -1
  47. package/dist/src/availability/policyCatalog.d.ts +1 -0
  48. package/dist/src/availability/policyCatalog.js +6 -8
  49. package/dist/src/availability/policyCatalog.js.map +1 -1
  50. package/dist/src/availability/policyCatalog.test.js +2 -2
  51. package/dist/src/availability/policyCatalog.test.js.map +1 -1
  52. package/dist/src/availability/policyHelpers.d.ts +9 -5
  53. package/dist/src/availability/policyHelpers.js +43 -37
  54. package/dist/src/availability/policyHelpers.js.map +1 -1
  55. package/dist/src/availability/policyHelpers.test.js +58 -20
  56. package/dist/src/availability/policyHelpers.test.js.map +1 -1
  57. package/dist/src/code_assist/oauth2.js +1 -1
  58. package/dist/src/code_assist/oauth2.js.map +1 -1
  59. package/dist/src/code_assist/oauth2.test.js +6 -8
  60. package/dist/src/code_assist/oauth2.test.js.map +1 -1
  61. package/dist/src/code_assist/server.js +3 -9
  62. package/dist/src/code_assist/server.js.map +1 -1
  63. package/dist/src/code_assist/server.test.js +18 -3
  64. package/dist/src/code_assist/server.test.js.map +1 -1
  65. package/dist/src/code_assist/telemetry.d.ts +6 -1
  66. package/dist/src/code_assist/telemetry.js +92 -3
  67. package/dist/src/code_assist/telemetry.js.map +1 -1
  68. package/dist/src/code_assist/telemetry.test.js +206 -18
  69. package/dist/src/code_assist/telemetry.test.js.map +1 -1
  70. package/dist/src/config/config.d.ts +9 -13
  71. package/dist/src/config/config.js +68 -38
  72. package/dist/src/config/config.js.map +1 -1
  73. package/dist/src/config/config.test.js +134 -22
  74. package/dist/src/config/config.test.js.map +1 -1
  75. package/dist/src/config/defaultModelConfigs.js +11 -0
  76. package/dist/src/config/defaultModelConfigs.js.map +1 -1
  77. package/dist/src/config/flashFallback.test.js +0 -37
  78. package/dist/src/config/flashFallback.test.js.map +1 -1
  79. package/dist/src/config/models.d.ts +23 -10
  80. package/dist/src/config/models.js +65 -23
  81. package/dist/src/config/models.js.map +1 -1
  82. package/dist/src/config/models.test.js +57 -76
  83. package/dist/src/config/models.test.js.map +1 -1
  84. package/dist/src/config/storage.d.ts +2 -0
  85. package/dist/src/config/storage.js +6 -0
  86. package/dist/src/config/storage.js.map +1 -1
  87. package/dist/src/config/storage.test.js +8 -0
  88. package/dist/src/config/storage.test.js.map +1 -1
  89. package/dist/src/core/baseLlmClient.d.ts +3 -1
  90. package/dist/src/core/baseLlmClient.js +51 -43
  91. package/dist/src/core/baseLlmClient.js.map +1 -1
  92. package/dist/src/core/baseLlmClient.test.js +12 -19
  93. package/dist/src/core/baseLlmClient.test.js.map +1 -1
  94. package/dist/src/core/client.js +18 -33
  95. package/dist/src/core/client.js.map +1 -1
  96. package/dist/src/core/client.test.js +14 -65
  97. package/dist/src/core/client.test.js.map +1 -1
  98. package/dist/src/core/contentGenerator.js +1 -1
  99. package/dist/src/core/contentGenerator.js.map +1 -1
  100. package/dist/src/core/contentGenerator.test.js +0 -6
  101. package/dist/src/core/contentGenerator.test.js.map +1 -1
  102. package/dist/src/core/geminiChat.js +29 -88
  103. package/dist/src/core/geminiChat.js.map +1 -1
  104. package/dist/src/core/geminiChat.test.js +35 -257
  105. package/dist/src/core/geminiChat.test.js.map +1 -1
  106. package/dist/src/core/geminiChat_network_retry.test.js +6 -6
  107. package/dist/src/core/geminiChat_network_retry.test.js.map +1 -1
  108. package/dist/src/core/prompts.js +6 -7
  109. package/dist/src/core/prompts.js.map +1 -1
  110. package/dist/src/core/prompts.test.js +10 -7
  111. package/dist/src/core/prompts.test.js.map +1 -1
  112. package/dist/src/core/turn.d.ts +1 -0
  113. package/dist/src/core/turn.js +3 -2
  114. package/dist/src/core/turn.js.map +1 -1
  115. package/dist/src/fallback/handler.js +54 -121
  116. package/dist/src/fallback/handler.js.map +1 -1
  117. package/dist/src/fallback/handler.test.js +80 -285
  118. package/dist/src/fallback/handler.test.js.map +1 -1
  119. package/dist/src/generated/git-commit.d.ts +2 -2
  120. package/dist/src/generated/git-commit.js +2 -2
  121. package/dist/src/hooks/hookPlanner.js +3 -1
  122. package/dist/src/hooks/hookPlanner.js.map +1 -1
  123. package/dist/src/hooks/hookPlanner.test.js +61 -0
  124. package/dist/src/hooks/hookPlanner.test.js.map +1 -1
  125. package/dist/src/hooks/hookRegistry.d.ts +1 -1
  126. package/dist/src/hooks/hookRegistry.js +2 -2
  127. package/dist/src/hooks/hookRegistry.js.map +1 -1
  128. package/dist/src/hooks/hookRegistry.test.js +73 -0
  129. package/dist/src/hooks/hookRegistry.test.js.map +1 -1
  130. package/dist/src/hooks/hookRunner.js +2 -2
  131. package/dist/src/hooks/hookRunner.js.map +1 -1
  132. package/dist/src/hooks/hookRunner.test.js +23 -0
  133. package/dist/src/hooks/hookRunner.test.js.map +1 -1
  134. package/dist/src/hooks/types.d.ts +2 -0
  135. package/dist/src/hooks/types.js.map +1 -1
  136. package/dist/src/index.d.ts +2 -0
  137. package/dist/src/index.js +2 -0
  138. package/dist/src/index.js.map +1 -1
  139. package/dist/src/routing/modelRouterService.js +0 -15
  140. package/dist/src/routing/modelRouterService.js.map +1 -1
  141. package/dist/src/routing/modelRouterService.test.js +0 -62
  142. package/dist/src/routing/modelRouterService.test.js.map +1 -1
  143. package/dist/src/routing/strategies/classifierStrategy.js +10 -21
  144. package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
  145. package/dist/src/routing/strategies/classifierStrategy.test.js +2 -1
  146. package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
  147. package/dist/src/routing/strategies/fallbackStrategy.js +20 -12
  148. package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
  149. package/dist/src/routing/strategies/fallbackStrategy.test.js +63 -39
  150. package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
  151. package/dist/src/routing/strategies/overrideStrategy.js +4 -3
  152. package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
  153. package/dist/src/services/chatCompressionService.js +3 -1
  154. package/dist/src/services/chatCompressionService.js.map +1 -1
  155. package/dist/src/services/loopDetectionService.js +2 -1
  156. package/dist/src/services/loopDetectionService.js.map +1 -1
  157. package/dist/src/services/loopDetectionService.test.js +14 -8
  158. package/dist/src/services/loopDetectionService.test.js.map +1 -1
  159. package/dist/src/services/modelConfig.integration.test.js +1 -1
  160. package/dist/src/services/modelConfig.integration.test.js.map +1 -1
  161. package/dist/src/services/shellExecutionService.js +18 -2
  162. package/dist/src/services/shellExecutionService.js.map +1 -1
  163. package/dist/src/services/test-data/resolved-aliases-retry.golden.json +16 -0
  164. package/dist/src/services/test-data/resolved-aliases.golden.json +16 -0
  165. package/dist/src/telemetry/sdk.js +2 -2
  166. package/dist/src/telemetry/sdk.js.map +1 -1
  167. package/dist/src/tools/ripGrep.js +2 -2
  168. package/dist/src/tools/ripGrep.js.map +1 -1
  169. package/dist/src/tools/tool-names.d.ts +13 -0
  170. package/dist/src/tools/tool-names.js +54 -0
  171. package/dist/src/tools/tool-names.js.map +1 -1
  172. package/dist/src/tools/tool-names.test.d.ts +6 -0
  173. package/dist/src/tools/tool-names.test.js +43 -0
  174. package/dist/src/tools/tool-names.test.js.map +1 -0
  175. package/dist/src/tools/tool-registry.d.ts +0 -1
  176. package/dist/src/tools/tool-registry.js +1 -1
  177. package/dist/src/tools/tool-registry.js.map +1 -1
  178. package/dist/src/tools/tool-registry.test.js +2 -1
  179. package/dist/src/tools/tool-registry.test.js.map +1 -1
  180. package/dist/src/utils/checkpointUtils.js +1 -1
  181. package/dist/src/utils/checkpointUtils.js.map +1 -1
  182. package/dist/src/utils/checkpointUtils.test.js +1 -1
  183. package/dist/src/utils/checkpointUtils.test.js.map +1 -1
  184. package/dist/src/utils/editCorrector.js +1 -1
  185. package/dist/src/utils/editCorrector.js.map +1 -1
  186. package/dist/src/utils/editCorrector.test.js +2 -2
  187. package/dist/src/utils/editCorrector.test.js.map +1 -1
  188. package/dist/src/utils/environmentContext.d.ts +1 -0
  189. package/dist/src/utils/environmentContext.js +1 -0
  190. package/dist/src/utils/environmentContext.js.map +1 -1
  191. package/dist/src/utils/events.d.ts +0 -16
  192. package/dist/src/utils/events.js +0 -9
  193. package/dist/src/utils/events.js.map +1 -1
  194. package/dist/src/utils/flashFallback.test.js +1 -1
  195. package/dist/src/utils/flashFallback.test.js.map +1 -1
  196. package/dist/src/utils/googleQuotaErrors.js +20 -0
  197. package/dist/src/utils/googleQuotaErrors.js.map +1 -1
  198. package/dist/src/utils/googleQuotaErrors.test.js +53 -2
  199. package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
  200. package/dist/src/utils/retry.js +2 -16
  201. package/dist/src/utils/retry.js.map +1 -1
  202. package/dist/src/utils/retry.test.js +7 -22
  203. package/dist/src/utils/retry.test.js.map +1 -1
  204. package/dist/tsconfig.tsbuildinfo +1 -1
  205. package/package.json +1 -6
  206. package/dist/google-gemini-cli-core-0.21.0-nightly.20251216.bb0c0d8ee.tgz +0 -0
  207. package/dist/src/agents/executor.js.map +0 -1
  208. package/dist/src/agents/executor.test.js.map +0 -1
  209. package/dist/src/agents/invocation.js.map +0 -1
  210. package/dist/src/agents/invocation.test.js.map +0 -1
  211. /package/dist/src/agents/{executor.test.d.ts → local-executor.test.d.ts} +0 -0
  212. /package/dist/src/agents/{invocation.test.d.ts → local-invocation.test.d.ts} +0 -0
@@ -7,10 +7,10 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
7
7
  import { ApiError, ThinkingLevel } from '@google/genai';
8
8
  import { GeminiChat, InvalidStreamError, StreamEventType, SYNTHETIC_THOUGHT_SIGNATURE, } from './geminiChat.js';
9
9
  import { setSimulate429 } from '../utils/testUtils.js';
10
- import { DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_THINKING_MODE, PREVIEW_GEMINI_MODEL, } from '../config/models.js';
10
+ import { DEFAULT_THINKING_MODE } from '../config/models.js';
11
11
  import { AuthType } from './contentGenerator.js';
12
12
  import { TerminalQuotaError } from '../utils/googleQuotaErrors.js';
13
- import { retryWithBackoff } from '../utils/retry.js';
13
+ import {} from '../utils/retry.js';
14
14
  import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
15
15
  import { HookSystem } from '../hooks/hookSystem.js';
16
16
  import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
@@ -98,19 +98,24 @@ describe('GeminiChat', () => {
98
98
  }
99
99
  return result;
100
100
  });
101
+ let currentModel = 'gemini-pro';
102
+ let currentActiveModel = 'gemini-pro';
101
103
  mockConfig = {
102
104
  getSessionId: () => 'test-session-id',
103
105
  getTelemetryLogPromptsEnabled: () => true,
104
106
  getUsageStatisticsEnabled: () => true,
105
107
  getDebugMode: () => false,
106
108
  getPreviewFeatures: () => false,
107
- getContentGeneratorConfig: vi.fn().mockReturnValue({
108
- authType: 'oauth-personal', // Ensure this is set for fallback tests
109
- model: 'test-model',
109
+ getContentGeneratorConfig: vi.fn().mockImplementation(() => ({
110
+ authType: 'oauth-personal',
111
+ model: currentModel,
112
+ })),
113
+ getModel: vi.fn().mockImplementation(() => currentModel),
114
+ setModel: vi.fn().mockImplementation((m) => {
115
+ currentModel = m;
116
+ // When model is explicitly set, active model usually resets or updates to it
117
+ currentActiveModel = m;
110
118
  }),
111
- getModel: vi.fn().mockReturnValue('gemini-pro'),
112
- setModel: vi.fn(),
113
- isInFallbackMode: vi.fn().mockReturnValue(false),
114
119
  getQuotaErrorOccurred: vi.fn().mockReturnValue(false),
115
120
  setQuotaErrorOccurred: vi.fn(),
116
121
  flashFallbackHandler: undefined,
@@ -126,7 +131,8 @@ describe('GeminiChat', () => {
126
131
  getUserTier: vi.fn().mockReturnValue(undefined),
127
132
  modelConfigService: {
128
133
  getResolvedConfig: vi.fn().mockImplementation((modelConfigKey) => {
129
- const thinkingConfig = modelConfigKey.model.startsWith('gemini-3')
134
+ const model = modelConfigKey.model ?? mockConfig.getModel();
135
+ const thinkingConfig = model.startsWith('gemini-3')
130
136
  ? {
131
137
  thinkingLevel: ThinkingLevel.HIGH,
132
138
  }
@@ -134,24 +140,23 @@ describe('GeminiChat', () => {
134
140
  thinkingBudget: DEFAULT_THINKING_MODE,
135
141
  };
136
142
  return {
137
- model: modelConfigKey.model,
143
+ model,
138
144
  generateContentConfig: {
139
- temperature: 0,
145
+ temperature: modelConfigKey.isRetry ? 1 : 0,
140
146
  thinkingConfig,
141
147
  },
142
148
  };
143
149
  }),
144
150
  },
145
- isPreviewModelBypassMode: vi.fn().mockReturnValue(false),
146
- setPreviewModelBypassMode: vi.fn(),
147
- isPreviewModelFallbackMode: vi.fn().mockReturnValue(false),
148
- setPreviewModelFallbackMode: vi.fn(),
149
151
  isInteractive: vi.fn().mockReturnValue(false),
150
152
  getEnableHooks: vi.fn().mockReturnValue(false),
151
- isModelAvailabilityServiceEnabled: vi.fn().mockReturnValue(false),
152
- getActiveModel: vi.fn().mockReturnValue('gemini-pro'),
153
- setActiveModel: vi.fn(),
154
- getModelAvailabilityService: vi.fn(),
153
+ getActiveModel: vi.fn().mockImplementation(() => currentActiveModel),
154
+ setActiveModel: vi
155
+ .fn()
156
+ .mockImplementation((m) => (currentActiveModel = m)),
157
+ getModelAvailabilityService: vi
158
+ .fn()
159
+ .mockReturnValue(createAvailabilityServiceMock()),
155
160
  };
156
161
  // Use proper MessageBus mocking for Phase 3 preparation
157
162
  const mockMessageBus = createMockMessageBus();
@@ -424,84 +429,6 @@ describe('GeminiChat', () => {
424
429
  expect(modelTurn?.parts?.length).toBe(1);
425
430
  expect(modelTurn?.parts[0].text).toBe('This is the visible text that should not be lost.');
426
431
  });
427
- it('should use maxAttempts=1 for retryWithBackoff when in Preview Model Fallback Mode', async () => {
428
- vi.mocked(mockConfig.isPreviewModelFallbackMode).mockReturnValue(true);
429
- vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue((async function* () {
430
- yield {
431
- candidates: [
432
- {
433
- content: { parts: [{ text: 'Success' }] },
434
- finishReason: 'STOP',
435
- },
436
- ],
437
- };
438
- })());
439
- const stream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-fast-retry', new AbortController().signal);
440
- for await (const _ of stream) {
441
- // consume stream
442
- }
443
- expect(mockRetryWithBackoff).toHaveBeenCalledWith(expect.any(Function), expect.objectContaining({
444
- maxAttempts: 1,
445
- }));
446
- });
447
- it('should NOT use maxAttempts=1 for other models even in Preview Model Fallback Mode', async () => {
448
- vi.mocked(mockConfig.isPreviewModelFallbackMode).mockReturnValue(true);
449
- vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue((async function* () {
450
- yield {
451
- candidates: [
452
- {
453
- content: { parts: [{ text: 'Success' }] },
454
- finishReason: 'STOP',
455
- },
456
- ],
457
- };
458
- })());
459
- const stream = await chat.sendMessageStream({ model: DEFAULT_GEMINI_FLASH_MODEL }, 'test', 'prompt-id-normal-retry', new AbortController().signal);
460
- for await (const _ of stream) {
461
- // consume stream
462
- }
463
- expect(mockRetryWithBackoff).toHaveBeenCalledWith(expect.any(Function), expect.objectContaining({
464
- maxAttempts: undefined, // Should use default
465
- }));
466
- });
467
- it('should pass DEFAULT_GEMINI_MODEL to handleFallback when Preview Model is bypassed (downgraded)', async () => {
468
- // ARRANGE
469
- vi.mocked(mockConfig.isPreviewModelBypassMode).mockReturnValue(true);
470
- // Mock retryWithBackoff to simulate catching the error and calling onPersistent429
471
- vi.mocked(retryWithBackoff).mockImplementation(async (apiCall, options) => {
472
- const onPersistent429 = options?.onPersistent429;
473
- try {
474
- await apiCall();
475
- }
476
- catch (error) {
477
- if (onPersistent429) {
478
- await onPersistent429(AuthType.LOGIN_WITH_GOOGLE, error);
479
- }
480
- throw error;
481
- }
482
- });
483
- // We need the API call to fail so retryWithBackoff calls the callback.
484
- vi.mocked(mockContentGenerator.generateContentStream).mockRejectedValue(new TerminalQuotaError('Simulated Quota Error', {
485
- code: 429,
486
- message: 'Simulated Quota Error',
487
- details: [],
488
- }));
489
- // ACT
490
- const consumeStream = async () => {
491
- const stream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-bypass', new AbortController().signal);
492
- // Consume the stream to trigger execution
493
- for await (const _ of stream) {
494
- // do nothing
495
- }
496
- };
497
- await expect(consumeStream()).rejects.toThrow('Simulated Quota Error');
498
- expect(retryWithBackoff).toHaveBeenCalled();
499
- // ASSERT
500
- // handleFallback is called via onPersistent429Callback
501
- // We verify it was called with DEFAULT_GEMINI_MODEL
502
- expect(mockHandleFallback).toHaveBeenCalledWith(expect.anything(), DEFAULT_GEMINI_MODEL, // This is the key assertion
503
- expect.anything(), expect.anything());
504
- });
505
432
  it('should throw an error when a tool call is followed by an empty stream response', async () => {
506
433
  // 1. Setup: A history where the model has just made a function call.
507
434
  const initialHistory = [
@@ -1318,30 +1245,6 @@ describe('GeminiChat', () => {
1318
1245
  }
1319
1246
  expect(turn4.parts[0].text).toBe('second response');
1320
1247
  });
1321
- describe('Model Resolution', () => {
1322
- const mockResponse = {
1323
- candidates: [
1324
- {
1325
- content: { parts: [{ text: 'response' }], role: 'model' },
1326
- finishReason: 'STOP',
1327
- },
1328
- ],
1329
- };
1330
- it('should use the FLASH model when in fallback mode (sendMessageStream)', async () => {
1331
- vi.mocked(mockConfig.getModel).mockReturnValue('gemini-pro');
1332
- vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(true);
1333
- vi.mocked(mockContentGenerator.generateContentStream).mockImplementation(async () => (async function* () {
1334
- yield mockResponse;
1335
- })());
1336
- const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-res3', new AbortController().signal);
1337
- for await (const _ of stream) {
1338
- // consume stream
1339
- }
1340
- expect(mockContentGenerator.generateContentStream).toHaveBeenCalledWith(expect.objectContaining({
1341
- model: DEFAULT_GEMINI_FLASH_MODEL,
1342
- }), 'prompt-id-res3');
1343
- });
1344
- });
1345
1248
  describe('Fallback Integration (Retries)', () => {
1346
1249
  const error429 = new ApiError({
1347
1250
  message: 'API Error 429: Quota exceeded',
@@ -1376,8 +1279,6 @@ describe('GeminiChat', () => {
1376
1279
  vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
1377
1280
  authType,
1378
1281
  });
1379
- const isInFallbackModeSpy = vi.spyOn(mockConfig, 'isInFallbackMode');
1380
- isInFallbackModeSpy.mockReturnValue(false);
1381
1282
  vi.mocked(mockContentGenerator.generateContentStream)
1382
1283
  .mockRejectedValueOnce(error429) // Attempt 1 fails
1383
1284
  .mockResolvedValueOnce(
@@ -1392,10 +1293,7 @@ describe('GeminiChat', () => {
1392
1293
  ],
1393
1294
  };
1394
1295
  })());
1395
- mockHandleFallback.mockImplementation(async () => {
1396
- isInFallbackModeSpy.mockReturnValue(true);
1397
- return true; // Signal retry
1398
- });
1296
+ mockHandleFallback.mockImplementation(async () => true);
1399
1297
  const stream = await chat.sendMessageStream({ model: 'test-model' }, 'trigger 429', 'prompt-id-fb1', new AbortController().signal);
1400
1298
  // Consume stream to trigger logic
1401
1299
  for await (const _ of stream) {
@@ -1408,77 +1306,6 @@ describe('GeminiChat', () => {
1408
1306
  const modelTurn = history[1];
1409
1307
  expect(modelTurn.parts[0].text).toBe('Success on retry');
1410
1308
  });
1411
- it('should switch to DEFAULT_GEMINI_FLASH_MODEL and use thinkingBudget when falling back from a gemini-3 model', async () => {
1412
- // ARRANGE
1413
- const authType = AuthType.LOGIN_WITH_GOOGLE;
1414
- vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
1415
- authType,
1416
- });
1417
- // Initial state: Not in fallback mode
1418
- const isInFallbackModeSpy = vi.spyOn(mockConfig, 'isInFallbackMode');
1419
- isInFallbackModeSpy.mockReturnValue(false);
1420
- // Mock API calls:
1421
- // 1. Fails with 429 (simulating gemini-3 failure)
1422
- // 2. Succeeds (simulating fallback success)
1423
- vi.mocked(mockContentGenerator.generateContentStream)
1424
- .mockRejectedValueOnce(error429)
1425
- .mockResolvedValueOnce((async function* () {
1426
- yield {
1427
- candidates: [
1428
- {
1429
- content: { parts: [{ text: 'Fallback success' }] },
1430
- finishReason: 'STOP',
1431
- },
1432
- ],
1433
- };
1434
- })());
1435
- // Mock handleFallback to enable fallback mode and signal retry
1436
- mockHandleFallback.mockImplementation(async () => {
1437
- isInFallbackModeSpy.mockReturnValue(true); // Next call will see fallback mode = true
1438
- return true;
1439
- });
1440
- // ACT
1441
- const stream = await chat.sendMessageStream({ model: 'gemini-3-test-model' }, // Start with a gemini-3 model
1442
- 'test fallback thinking', 'prompt-id-fb3', new AbortController().signal);
1443
- for await (const _ of stream) {
1444
- // consume stream
1445
- }
1446
- // ASSERT
1447
- expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
1448
- // First call: gemini-3 model, thinkingLevel set
1449
- expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(1, expect.objectContaining({
1450
- model: 'gemini-3-test-model',
1451
- config: expect.objectContaining({
1452
- thinkingConfig: {
1453
- thinkingBudget: undefined,
1454
- thinkingLevel: ThinkingLevel.HIGH,
1455
- },
1456
- }),
1457
- }), 'prompt-id-fb3');
1458
- // Second call: DEFAULT_GEMINI_FLASH_MODEL (due to fallback), thinkingBudget set (due to fix)
1459
- expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(2, expect.objectContaining({
1460
- model: DEFAULT_GEMINI_FLASH_MODEL,
1461
- config: expect.objectContaining({
1462
- thinkingConfig: {
1463
- thinkingBudget: DEFAULT_THINKING_MODE,
1464
- thinkingLevel: undefined,
1465
- },
1466
- }),
1467
- }), 'prompt-id-fb3');
1468
- });
1469
- it('should stop retrying if handleFallback returns false (e.g., auth intent)', async () => {
1470
- vi.mocked(mockConfig.getModel).mockReturnValue('gemini-pro');
1471
- vi.mocked(mockContentGenerator.generateContentStream).mockRejectedValue(error429);
1472
- mockHandleFallback.mockResolvedValue(false);
1473
- const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test stop', 'prompt-id-fb2', new AbortController().signal);
1474
- await expect((async () => {
1475
- for await (const _ of stream) {
1476
- /* consume stream */
1477
- }
1478
- })()).rejects.toThrow(error429);
1479
- expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(1);
1480
- expect(mockHandleFallback).toHaveBeenCalledTimes(1);
1481
- });
1482
1309
  });
1483
1310
  it('should discard valid partial content from a failed attempt upon retry', async () => {
1484
1311
  // Mock the stream to fail on the first attempt after yielding some valid content.
@@ -1565,61 +1392,6 @@ describe('GeminiChat', () => {
1565
1392
  ]);
1566
1393
  });
1567
1394
  });
1568
- describe('Preview Model Fallback Logic', () => {
1569
- it('should reset previewModelBypassMode to false at the start of sendMessageStream', async () => {
1570
- const stream = (async function* () {
1571
- yield {
1572
- candidates: [
1573
- {
1574
- content: { role: 'model', parts: [{ text: 'Success' }] },
1575
- finishReason: 'STOP',
1576
- },
1577
- ],
1578
- };
1579
- })();
1580
- vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
1581
- await chat.sendMessageStream({ model: 'test-model' }, 'test', 'prompt-id-preview-model-reset', new AbortController().signal);
1582
- expect(mockConfig.setPreviewModelBypassMode).toHaveBeenCalledWith(false);
1583
- });
1584
- it('should reset previewModelFallbackMode to false upon successful Preview Model usage', async () => {
1585
- const stream = (async function* () {
1586
- yield {
1587
- candidates: [
1588
- {
1589
- content: { role: 'model', parts: [{ text: 'Success' }] },
1590
- finishReason: 'STOP',
1591
- },
1592
- ],
1593
- };
1594
- })();
1595
- vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
1596
- const resultStream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-preview-model-healing', new AbortController().signal);
1597
- for await (const _ of resultStream) {
1598
- // consume stream
1599
- }
1600
- expect(mockConfig.setPreviewModelFallbackMode).toHaveBeenCalledWith(false);
1601
- });
1602
- it('should NOT reset previewModelFallbackMode if Preview Model was bypassed (downgraded)', async () => {
1603
- const stream = (async function* () {
1604
- yield {
1605
- candidates: [
1606
- {
1607
- content: { role: 'model', parts: [{ text: 'Success' }] },
1608
- finishReason: 'STOP',
1609
- },
1610
- ],
1611
- };
1612
- })();
1613
- vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
1614
- // Simulate bypass mode being active (downgrade happened)
1615
- vi.mocked(mockConfig.isPreviewModelBypassMode).mockReturnValue(true);
1616
- const resultStream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-bypass-no-healing', new AbortController().signal);
1617
- for await (const _ of resultStream) {
1618
- // consume stream
1619
- }
1620
- expect(mockConfig.setPreviewModelFallbackMode).not.toHaveBeenCalled();
1621
- });
1622
- });
1623
1395
  describe('ensureActiveLoopHasThoughtSignatures', () => {
1624
1396
  it('should add thoughtSignature to the first functionCall in each model turn of the active loop', () => {
1625
1397
  const chat = new GeminiChat(mockConfig, '', [], []);
@@ -1704,7 +1476,6 @@ describe('GeminiChat', () => {
1704
1476
  beforeEach(async () => {
1705
1477
  mockAvailabilityService = createAvailabilityServiceMock();
1706
1478
  vi.mocked(mockConfig.getModelAvailabilityService).mockReturnValue(mockAvailabilityService);
1707
- vi.mocked(mockConfig.isModelAvailabilityServiceEnabled).mockReturnValue(true);
1708
1479
  // Stateful mock for activeModel
1709
1480
  let activeModel = 'model-a';
1710
1481
  vi.mocked(mockConfig.getActiveModel).mockImplementation(() => activeModel);
@@ -1822,9 +1593,16 @@ describe('GeminiChat', () => {
1822
1593
  activeModel = model;
1823
1594
  });
1824
1595
  // Different configs per model
1825
- vi.mocked(mockConfig.modelConfigService.getResolvedConfig)
1826
- .mockReturnValueOnce(makeResolvedModelConfig('model-a', { temperature: 0.1 }))
1827
- .mockReturnValueOnce(makeResolvedModelConfig('model-b', { temperature: 0.9 }));
1596
+ vi.mocked(mockConfig.modelConfigService.getResolvedConfig).mockImplementation((key) => {
1597
+ if (key.model === 'model-a') {
1598
+ return makeResolvedModelConfig('model-a', { temperature: 0.1 });
1599
+ }
1600
+ if (key.model === 'model-b') {
1601
+ return makeResolvedModelConfig('model-b', { temperature: 0.9 });
1602
+ }
1603
+ // Default for the initial requested model in this test
1604
+ return makeResolvedModelConfig('model-a', { temperature: 0.1 });
1605
+ });
1828
1606
  // First attempt uses model-a, then simulate availability switching to model-b
1829
1607
  mockRetryWithBackoff.mockImplementation(async (apiCall) => {
1830
1608
  await apiCall(); // first attempt