@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.
- package/dist/google-gemini-cli-core-0.21.0-nightly.20251218.739c02bd6.tgz +0 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/src/agents/codebase-investigator.d.ts +2 -2
- package/dist/src/agents/codebase-investigator.js +6 -5
- package/dist/src/agents/codebase-investigator.js.map +1 -1
- package/dist/src/agents/codebase-investigator.test.js +2 -2
- package/dist/src/agents/codebase-investigator.test.js.map +1 -1
- package/dist/src/agents/delegate-to-agent-tool.js +8 -4
- package/dist/src/agents/delegate-to-agent-tool.js.map +1 -1
- package/dist/src/agents/delegate-to-agent-tool.test.js +5 -4
- package/dist/src/agents/delegate-to-agent-tool.test.js.map +1 -1
- package/dist/src/agents/{executor.d.ts → local-executor.d.ts} +5 -11
- package/dist/src/agents/{executor.js → local-executor.js} +75 -51
- package/dist/src/agents/local-executor.js.map +1 -0
- package/dist/src/agents/{executor.test.js → local-executor.test.js} +61 -45
- package/dist/src/agents/local-executor.test.js.map +1 -0
- package/dist/src/agents/{invocation.d.ts → local-invocation.d.ts} +5 -6
- package/dist/src/agents/{invocation.js → local-invocation.js} +8 -9
- package/dist/src/agents/local-invocation.js.map +1 -0
- package/dist/src/agents/{invocation.test.js → local-invocation.test.js} +18 -17
- package/dist/src/agents/local-invocation.test.js.map +1 -0
- package/dist/src/agents/registry.d.ts +1 -0
- package/dist/src/agents/registry.js +77 -28
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +130 -5
- package/dist/src/agents/registry.test.js.map +1 -1
- package/dist/src/agents/remote-invocation.d.ts +21 -0
- package/dist/src/agents/remote-invocation.js +31 -0
- package/dist/src/agents/remote-invocation.js.map +1 -0
- package/dist/src/agents/remote-invocation.test.d.ts +6 -0
- package/dist/src/agents/remote-invocation.test.js +35 -0
- package/dist/src/agents/remote-invocation.test.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.js +7 -3
- package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
- package/dist/src/agents/subagent-tool-wrapper.test.js +10 -9
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
- package/dist/src/agents/toml-loader.d.ts +65 -0
- package/dist/src/agents/toml-loader.js +176 -0
- package/dist/src/agents/toml-loader.js.map +1 -0
- package/dist/src/agents/toml-loader.test.d.ts +6 -0
- package/dist/src/agents/toml-loader.test.js +190 -0
- package/dist/src/agents/toml-loader.test.js.map +1 -0
- package/dist/src/agents/types.d.ts +12 -4
- package/dist/src/availability/modelAvailabilityService.d.ts +2 -1
- package/dist/src/availability/policyCatalog.d.ts +1 -0
- package/dist/src/availability/policyCatalog.js +6 -8
- package/dist/src/availability/policyCatalog.js.map +1 -1
- package/dist/src/availability/policyCatalog.test.js +2 -2
- package/dist/src/availability/policyCatalog.test.js.map +1 -1
- package/dist/src/availability/policyHelpers.d.ts +9 -5
- package/dist/src/availability/policyHelpers.js +43 -37
- package/dist/src/availability/policyHelpers.js.map +1 -1
- package/dist/src/availability/policyHelpers.test.js +58 -20
- package/dist/src/availability/policyHelpers.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +1 -1
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +6 -8
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/server.js +3 -9
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +18 -3
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/code_assist/telemetry.d.ts +6 -1
- package/dist/src/code_assist/telemetry.js +92 -3
- package/dist/src/code_assist/telemetry.js.map +1 -1
- package/dist/src/code_assist/telemetry.test.js +206 -18
- package/dist/src/code_assist/telemetry.test.js.map +1 -1
- package/dist/src/config/config.d.ts +9 -13
- package/dist/src/config/config.js +68 -38
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +134 -22
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.js +11 -0
- package/dist/src/config/defaultModelConfigs.js.map +1 -1
- package/dist/src/config/flashFallback.test.js +0 -37
- package/dist/src/config/flashFallback.test.js.map +1 -1
- package/dist/src/config/models.d.ts +23 -10
- package/dist/src/config/models.js +65 -23
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.js +57 -76
- package/dist/src/config/models.test.js.map +1 -1
- package/dist/src/config/storage.d.ts +2 -0
- package/dist/src/config/storage.js +6 -0
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/config/storage.test.js +8 -0
- package/dist/src/config/storage.test.js.map +1 -1
- package/dist/src/core/baseLlmClient.d.ts +3 -1
- package/dist/src/core/baseLlmClient.js +51 -43
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +12 -19
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.js +18 -33
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +14 -65
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.js +1 -1
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +0 -6
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/geminiChat.js +29 -88
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +35 -257
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/geminiChat_network_retry.test.js +6 -6
- package/dist/src/core/geminiChat_network_retry.test.js.map +1 -1
- package/dist/src/core/prompts.js +6 -7
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +10 -7
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +1 -0
- package/dist/src/core/turn.js +3 -2
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/fallback/handler.js +54 -121
- package/dist/src/fallback/handler.js.map +1 -1
- package/dist/src/fallback/handler.test.js +80 -285
- package/dist/src/fallback/handler.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/hooks/hookPlanner.js +3 -1
- package/dist/src/hooks/hookPlanner.js.map +1 -1
- package/dist/src/hooks/hookPlanner.test.js +61 -0
- package/dist/src/hooks/hookPlanner.test.js.map +1 -1
- package/dist/src/hooks/hookRegistry.d.ts +1 -1
- package/dist/src/hooks/hookRegistry.js +2 -2
- package/dist/src/hooks/hookRegistry.js.map +1 -1
- package/dist/src/hooks/hookRegistry.test.js +73 -0
- package/dist/src/hooks/hookRegistry.test.js.map +1 -1
- package/dist/src/hooks/hookRunner.js +2 -2
- package/dist/src/hooks/hookRunner.js.map +1 -1
- package/dist/src/hooks/hookRunner.test.js +23 -0
- package/dist/src/hooks/hookRunner.test.js.map +1 -1
- package/dist/src/hooks/types.d.ts +2 -0
- package/dist/src/hooks/types.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/routing/modelRouterService.js +0 -15
- package/dist/src/routing/modelRouterService.js.map +1 -1
- package/dist/src/routing/modelRouterService.test.js +0 -62
- package/dist/src/routing/modelRouterService.test.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.js +10 -21
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js +2 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.js +20 -12
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.test.js +63 -39
- package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.js +4 -3
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
- package/dist/src/services/chatCompressionService.js +3 -1
- package/dist/src/services/chatCompressionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.js +2 -1
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +14 -8
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/modelConfig.integration.test.js +1 -1
- package/dist/src/services/modelConfig.integration.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +18 -2
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases-retry.golden.json +16 -0
- package/dist/src/services/test-data/resolved-aliases.golden.json +16 -0
- package/dist/src/telemetry/sdk.js +2 -2
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/tools/ripGrep.js +2 -2
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +13 -0
- package/dist/src/tools/tool-names.js +54 -0
- package/dist/src/tools/tool-names.js.map +1 -1
- package/dist/src/tools/tool-names.test.d.ts +6 -0
- package/dist/src/tools/tool-names.test.js +43 -0
- package/dist/src/tools/tool-names.test.js.map +1 -0
- package/dist/src/tools/tool-registry.d.ts +0 -1
- package/dist/src/tools/tool-registry.js +1 -1
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +2 -1
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/utils/checkpointUtils.js +1 -1
- package/dist/src/utils/checkpointUtils.js.map +1 -1
- package/dist/src/utils/checkpointUtils.test.js +1 -1
- package/dist/src/utils/checkpointUtils.test.js.map +1 -1
- package/dist/src/utils/editCorrector.js +1 -1
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +2 -2
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/environmentContext.d.ts +1 -0
- package/dist/src/utils/environmentContext.js +1 -0
- package/dist/src/utils/environmentContext.js.map +1 -1
- package/dist/src/utils/events.d.ts +0 -16
- package/dist/src/utils/events.js +0 -9
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/flashFallback.test.js +1 -1
- package/dist/src/utils/flashFallback.test.js.map +1 -1
- package/dist/src/utils/googleQuotaErrors.js +20 -0
- package/dist/src/utils/googleQuotaErrors.js.map +1 -1
- package/dist/src/utils/googleQuotaErrors.test.js +53 -2
- package/dist/src/utils/googleQuotaErrors.test.js.map +1 -1
- package/dist/src/utils/retry.js +2 -16
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +7 -22
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -6
- package/dist/google-gemini-cli-core-0.21.0-nightly.20251216.bb0c0d8ee.tgz +0 -0
- package/dist/src/agents/executor.js.map +0 -1
- package/dist/src/agents/executor.test.js.map +0 -1
- package/dist/src/agents/invocation.js.map +0 -1
- package/dist/src/agents/invocation.test.js.map +0 -1
- /package/dist/src/agents/{executor.test.d.ts → local-executor.test.d.ts} +0 -0
- /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 {
|
|
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 {
|
|
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().
|
|
108
|
-
authType: 'oauth-personal',
|
|
109
|
-
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
|
|
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
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
.
|
|
1827
|
-
|
|
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
|