@machina.ai/cell-cli-core 1.16.0-rc2 → 1.19.4-rc1
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/package.json +6 -4
- package/dist/src/agents/codebase-investigator.js +1 -1
- package/dist/src/agents/executor.js +12 -21
- package/dist/src/agents/executor.js.map +1 -1
- package/dist/src/agents/executor.test.js +53 -57
- package/dist/src/agents/executor.test.js.map +1 -1
- package/dist/src/agents/registry.d.ts +4 -0
- package/dist/src/agents/registry.js +23 -0
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +30 -16
- package/dist/src/agents/registry.test.js.map +1 -1
- package/dist/src/availability/modelAvailabilityService.d.ts +34 -0
- package/dist/src/availability/modelAvailabilityService.js +84 -0
- package/dist/src/availability/modelAvailabilityService.js.map +1 -0
- package/dist/src/availability/modelAvailabilityService.test.d.ts +6 -0
- package/dist/src/availability/modelAvailabilityService.test.js +140 -0
- package/dist/src/availability/modelAvailabilityService.test.js.map +1 -0
- package/dist/src/availability/modelPolicy.d.ts +42 -0
- package/dist/src/availability/modelPolicy.js +7 -0
- package/dist/src/availability/modelPolicy.js.map +1 -0
- package/dist/src/availability/policyCatalog.d.ts +20 -0
- package/dist/src/availability/policyCatalog.js +83 -0
- package/dist/src/availability/policyCatalog.js.map +1 -0
- package/dist/src/availability/policyCatalog.test.d.ts +6 -0
- package/dist/src/availability/policyCatalog.test.js +70 -0
- package/dist/src/availability/policyCatalog.test.js.map +1 -0
- package/dist/src/code_assist/codeAssist.js +1 -1
- package/dist/src/code_assist/codeAssist.test.js +3 -3
- package/dist/src/code_assist/oauth-credential-storage.test.js +1 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +106 -53
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +25 -12
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/config/config.d.ts +18 -5
- package/dist/src/config/config.js +51 -19
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +14 -79
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.js +56 -4
- package/dist/src/config/defaultModelConfigs.js.map +1 -1
- package/dist/src/confirmation-bus/message-bus.d.ts +6 -0
- package/dist/src/confirmation-bus/message-bus.js +63 -2
- package/dist/src/confirmation-bus/message-bus.js.map +1 -1
- package/dist/src/confirmation-bus/types.d.ts +25 -2
- package/dist/src/confirmation-bus/types.js +3 -0
- package/dist/src/confirmation-bus/types.js.map +1 -1
- package/dist/src/core/AuthenticatedContentGenerator.d.ts +20 -0
- package/dist/src/core/AuthenticatedContentGenerator.js +115 -0
- package/dist/src/core/AuthenticatedContentGenerator.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +27 -1
- package/dist/src/core/baseLlmClient.js +66 -45
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +94 -6
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.d.ts +1 -2
- package/dist/src/core/client.js +89 -29
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +82 -41
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/clientHookTriggers.d.ts +36 -0
- package/dist/src/core/clientHookTriggers.js +76 -0
- package/dist/src/core/clientHookTriggers.js.map +1 -0
- package/dist/src/core/contentGenerator.d.ts +2 -1
- package/dist/src/core/contentGenerator.js +12 -5
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +119 -1
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +0 -2
- package/dist/src/core/coreToolScheduler.js +1 -3
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +3 -18
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +10 -5
- package/dist/src/core/geminiChat.js +57 -22
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +188 -55
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/logger.test.js +12 -11
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.js +6 -2
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +10 -2
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.js +21 -6
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +79 -93
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +14 -3
- package/dist/src/core/turn.js +13 -7
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +73 -139
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/fallback/handler.js +10 -5
- package/dist/src/fallback/handler.js.map +1 -1
- package/dist/src/fallback/handler.test.js +79 -17
- 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/hookEventHandler.d.ts +109 -0
- package/dist/src/hooks/hookEventHandler.js +486 -0
- package/dist/src/hooks/hookEventHandler.js.map +1 -0
- package/dist/src/hooks/hookEventHandler.test.d.ts +6 -0
- package/dist/src/hooks/hookEventHandler.test.js +384 -0
- package/dist/src/hooks/hookEventHandler.test.js.map +1 -0
- package/dist/src/hooks/hookSystem.d.ts +48 -0
- package/dist/src/hooks/hookSystem.js +83 -0
- package/dist/src/hooks/hookSystem.js.map +1 -0
- package/dist/src/hooks/hookSystem.test.d.ts +6 -0
- package/dist/src/hooks/hookSystem.test.js +213 -0
- package/dist/src/hooks/hookSystem.test.js.map +1 -0
- package/dist/src/hooks/index.d.ts +15 -0
- package/dist/src/hooks/index.js +15 -0
- package/dist/src/hooks/index.js.map +1 -0
- package/dist/src/index.d.ts +4 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +1 -0
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/policy/policy-engine.d.ts +17 -1
- package/dist/src/policy/policy-engine.js +90 -1
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +222 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/policy/types.d.ts +51 -1
- package/dist/src/policy/types.js +21 -0
- package/dist/src/policy/types.js.map +1 -1
- package/dist/src/prompts/mcp-prompts.test.js +0 -1
- package/dist/src/prompts/mcp-prompts.test.js.map +1 -1
- package/dist/src/prompts/prompt-registry.test.js +0 -15
- package/dist/src/prompts/prompt-registry.test.js.map +1 -1
- package/dist/src/routing/modelRouterService.js +1 -1
- package/dist/src/routing/modelRouterService.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js +4 -3
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
- package/dist/src/services/chatCompressionService.d.ts +2 -1
- package/dist/src/services/chatCompressionService.js +23 -7
- package/dist/src/services/chatCompressionService.js.map +1 -1
- package/dist/src/services/chatCompressionService.test.js +27 -32
- package/dist/src/services/chatCompressionService.test.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +1 -1
- package/dist/src/services/gitService.js +3 -1
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +10 -0
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/modelConfig.integration.test.js +34 -0
- package/dist/src/services/modelConfig.integration.test.js.map +1 -1
- package/dist/src/services/modelConfigService.d.ts +3 -0
- package/dist/src/services/modelConfigService.js +12 -3
- package/dist/src/services/modelConfigService.js.map +1 -1
- package/dist/src/services/modelConfigService.test.js +133 -0
- package/dist/src/services/modelConfigService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +2 -0
- package/dist/src/services/shellExecutionService.js +35 -6
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +78 -1
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases.golden.json +57 -4
- package/dist/src/telemetry/activity-monitor.test.js +4 -1
- package/dist/src/telemetry/activity-monitor.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +2 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +97 -51
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +24 -16
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +2 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +5 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +2 -1
- package/dist/src/telemetry/loggers.js +14 -3
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +2 -5
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +26 -8
- package/dist/src/telemetry/metrics.js +37 -11
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +61 -39
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sanitize.d.ts +25 -0
- package/dist/src/telemetry/sanitize.js +48 -0
- package/dist/src/telemetry/sanitize.js.map +1 -0
- package/dist/src/telemetry/sanitize.test.d.ts +6 -0
- package/dist/src/telemetry/sanitize.test.js +279 -0
- package/dist/src/telemetry/sanitize.test.js.map +1 -0
- package/dist/src/telemetry/semantic.js +1 -1
- package/dist/src/telemetry/semantic.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +19 -0
- package/dist/src/telemetry/types.js +65 -0
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/test-utils/config.d.ts +1 -1
- package/dist/src/test-utils/config.js +1 -1
- package/dist/src/test-utils/mock-message-bus.d.ts +60 -0
- package/dist/src/test-utils/mock-message-bus.js +131 -0
- package/dist/src/test-utils/mock-message-bus.js.map +1 -0
- package/dist/src/test-utils/mock-tool.js +1 -1
- package/dist/src/test-utils/mock-tool.js.map +1 -1
- package/dist/src/tools/edit.test.js +203 -200
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +4 -2
- package/dist/src/tools/mcp-client.js +226 -114
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +128 -54
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +186 -273
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/modifiable-tool.d.ts +1 -1
- package/dist/src/tools/modifiable-tool.js +2 -2
- package/dist/src/tools/modifiable-tool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.test.js +12 -11
- package/dist/src/tools/modifiable-tool.test.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +84 -126
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +93 -112
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +59 -90
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +121 -179
- package/dist/src/tools/web-fetch.test.js.map +1 -1
- package/dist/src/tools/write-file.test.js +105 -116
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +1 -1
- package/dist/src/tools/write-todos.js +1 -1
- package/dist/src/utils/customHeaderUtils.d.ts +9 -0
- package/dist/src/utils/customHeaderUtils.js +34 -0
- package/dist/src/utils/customHeaderUtils.js.map +1 -0
- package/dist/src/utils/customHeaderUtils.test.d.ts +6 -0
- package/dist/src/utils/customHeaderUtils.test.js +77 -0
- package/dist/src/utils/customHeaderUtils.test.js.map +1 -0
- package/dist/src/utils/debugLogger.test.js +27 -25
- package/dist/src/utils/debugLogger.test.js.map +1 -1
- package/dist/src/utils/editor.d.ts +1 -1
- package/dist/src/utils/editor.js +3 -2
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +7 -46
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/events.d.ts +33 -3
- package/dist/src/utils/events.js +35 -15
- package/dist/src/utils/events.js.map +1 -1
- package/dist/src/utils/events.test.js +86 -5
- package/dist/src/utils/events.test.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.js +1 -1
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
- package/dist/src/utils/installationManager.test.js +2 -1
- package/dist/src/utils/installationManager.test.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.js +2 -1
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +3 -2
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +1 -1
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.test.js +8 -14
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +3 -1
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/package.d.ts +14 -0
- package/dist/src/utils/package.js +15 -2
- package/dist/src/utils/package.js.map +1 -1
- package/dist/src/utils/schemaValidator.d.ts +1 -1
- package/dist/src/utils/schemaValidator.js +1 -1
- package/dist/src/utils/shell-utils.js +1 -1
- package/dist/src/utils/stdio.d.ts +32 -0
- package/dist/src/utils/stdio.js +85 -0
- package/dist/src/utils/stdio.js.map +1 -0
- package/dist/src/utils/stdio.test.d.ts +6 -0
- package/dist/src/utils/stdio.test.js +47 -0
- package/dist/src/utils/stdio.test.js.map +1 -0
- package/dist/src/utils/systemEncoding.test.js +2 -1
- package/dist/src/utils/systemEncoding.test.js.map +1 -1
- package/dist/src/utils/terminal.d.ts +14 -0
- package/dist/src/utils/terminal.js +38 -0
- package/dist/src/utils/terminal.js.map +1 -0
- package/dist/src/utils/userAccountManager.test.js +7 -6
- package/dist/src/utils/userAccountManager.test.js.map +1 -1
- package/dist/src/utils/workspaceContext.test.js +5 -4
- package/dist/src/utils/workspaceContext.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +6 -4
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import { ApiError } from '@google/genai';
|
|
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, PREVIEW_GEMINI_MODEL, } from '../config/models.js';
|
|
10
|
+
import { DEFAULT_GEMINI_FLASH_MODEL, DEFAULT_GEMINI_MODEL, DEFAULT_THINKING_MODE, PREVIEW_GEMINI_MODEL, } from '../config/models.js';
|
|
11
11
|
import { AuthType } from './contentGenerator.js';
|
|
12
12
|
import { TerminalQuotaError } from '../utils/googleQuotaErrors.js';
|
|
13
13
|
import { retryWithBackoff } from '../utils/retry.js';
|
|
@@ -65,7 +65,6 @@ describe('GeminiChat', () => {
|
|
|
65
65
|
let mockContentGenerator;
|
|
66
66
|
let chat;
|
|
67
67
|
let mockConfig;
|
|
68
|
-
const config = {};
|
|
69
68
|
beforeEach(() => {
|
|
70
69
|
vi.clearAllMocks();
|
|
71
70
|
vi.mocked(uiTelemetryService.setLastPromptTokenCount).mockClear();
|
|
@@ -104,6 +103,24 @@ describe('GeminiChat', () => {
|
|
|
104
103
|
}),
|
|
105
104
|
getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
|
|
106
105
|
getRetryFetchErrors: vi.fn().mockReturnValue(false),
|
|
106
|
+
modelConfigService: {
|
|
107
|
+
getResolvedConfig: vi.fn().mockImplementation((modelConfigKey) => {
|
|
108
|
+
const thinkingConfig = modelConfigKey.model.startsWith('gemini-3')
|
|
109
|
+
? {
|
|
110
|
+
thinkingLevel: ThinkingLevel.HIGH,
|
|
111
|
+
}
|
|
112
|
+
: {
|
|
113
|
+
thinkingBudget: DEFAULT_THINKING_MODE,
|
|
114
|
+
};
|
|
115
|
+
return {
|
|
116
|
+
model: modelConfigKey.model,
|
|
117
|
+
generateContentConfig: {
|
|
118
|
+
temperature: 0,
|
|
119
|
+
thinkingConfig,
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}),
|
|
123
|
+
},
|
|
107
124
|
isPreviewModelBypassMode: vi.fn().mockReturnValue(false),
|
|
108
125
|
setPreviewModelBypassMode: vi.fn(),
|
|
109
126
|
isPreviewModelFallbackMode: vi.fn().mockReturnValue(false),
|
|
@@ -113,7 +130,7 @@ describe('GeminiChat', () => {
|
|
|
113
130
|
// Disable 429 simulation for tests
|
|
114
131
|
setSimulate429(false);
|
|
115
132
|
// Reset history for each test by creating a new instance
|
|
116
|
-
chat = new GeminiChat(mockConfig
|
|
133
|
+
chat = new GeminiChat(mockConfig);
|
|
117
134
|
});
|
|
118
135
|
afterEach(() => {
|
|
119
136
|
vi.restoreAllMocks();
|
|
@@ -125,12 +142,12 @@ describe('GeminiChat', () => {
|
|
|
125
142
|
{ role: 'user', parts: [{ text: 'Hello' }] },
|
|
126
143
|
{ role: 'model', parts: [{ text: 'Hi there' }] },
|
|
127
144
|
];
|
|
128
|
-
const chatWithHistory = new GeminiChat(mockConfig,
|
|
145
|
+
const chatWithHistory = new GeminiChat(mockConfig, '', [], history);
|
|
129
146
|
const estimatedTokens = Math.ceil(JSON.stringify(history).length / 4);
|
|
130
147
|
expect(chatWithHistory.getLastPromptTokenCount()).toBe(estimatedTokens);
|
|
131
148
|
});
|
|
132
149
|
it('should initialize lastPromptTokenCount for empty history', () => {
|
|
133
|
-
const chatEmpty = new GeminiChat(mockConfig
|
|
150
|
+
const chatEmpty = new GeminiChat(mockConfig);
|
|
134
151
|
expect(chatEmpty.getLastPromptTokenCount()).toBe(Math.ceil(JSON.stringify([]).length / 4));
|
|
135
152
|
});
|
|
136
153
|
});
|
|
@@ -163,7 +180,7 @@ describe('GeminiChat', () => {
|
|
|
163
180
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithToolCall);
|
|
164
181
|
// 2. Action & Assert: The stream processing should complete without throwing an error
|
|
165
182
|
// because the presence of a tool call makes the empty final chunk acceptable.
|
|
166
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
183
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-tool-call-empty-end', new AbortController().signal);
|
|
167
184
|
await expect((async () => {
|
|
168
185
|
for await (const _ of stream) {
|
|
169
186
|
/* consume stream */
|
|
@@ -203,7 +220,7 @@ describe('GeminiChat', () => {
|
|
|
203
220
|
})();
|
|
204
221
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithNoFinish);
|
|
205
222
|
// 2. Action & Assert: The stream should fail because there's no finish reason.
|
|
206
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
223
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-no-finish-empty-end', new AbortController().signal);
|
|
207
224
|
await expect((async () => {
|
|
208
225
|
for await (const _ of stream) {
|
|
209
226
|
/* consume stream */
|
|
@@ -238,7 +255,7 @@ describe('GeminiChat', () => {
|
|
|
238
255
|
})();
|
|
239
256
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithInvalidEnd);
|
|
240
257
|
// 2. Action & Assert: The stream should complete without throwing an error.
|
|
241
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
258
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-valid-then-invalid-end', new AbortController().signal);
|
|
242
259
|
await expect((async () => {
|
|
243
260
|
for await (const _ of stream) {
|
|
244
261
|
/* consume stream */
|
|
@@ -273,7 +290,7 @@ describe('GeminiChat', () => {
|
|
|
273
290
|
})();
|
|
274
291
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(multiChunkStream);
|
|
275
292
|
// 2. Action: Send a message and consume the stream.
|
|
276
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
293
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-empty-chunk-consolidation', new AbortController().signal);
|
|
277
294
|
for await (const _ of stream) {
|
|
278
295
|
// Consume the stream
|
|
279
296
|
}
|
|
@@ -321,7 +338,7 @@ describe('GeminiChat', () => {
|
|
|
321
338
|
})();
|
|
322
339
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(multiChunkStream);
|
|
323
340
|
// 2. Action: Send a message and consume the stream.
|
|
324
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
341
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-multi-chunk', new AbortController().signal);
|
|
325
342
|
for await (const _ of stream) {
|
|
326
343
|
// Consume the stream to trigger history recording.
|
|
327
344
|
}
|
|
@@ -357,7 +374,7 @@ describe('GeminiChat', () => {
|
|
|
357
374
|
})();
|
|
358
375
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(mixedContentStream);
|
|
359
376
|
// 2. Action: Send a message and fully consume the stream to trigger history recording.
|
|
360
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
377
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-mixed-chunk', new AbortController().signal);
|
|
361
378
|
for await (const _ of stream) {
|
|
362
379
|
// This loop consumes the stream.
|
|
363
380
|
}
|
|
@@ -385,7 +402,7 @@ describe('GeminiChat', () => {
|
|
|
385
402
|
],
|
|
386
403
|
};
|
|
387
404
|
})());
|
|
388
|
-
const stream = await chat.sendMessageStream(
|
|
405
|
+
const stream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-fast-retry', new AbortController().signal);
|
|
389
406
|
for await (const _ of stream) {
|
|
390
407
|
// consume stream
|
|
391
408
|
}
|
|
@@ -405,7 +422,7 @@ describe('GeminiChat', () => {
|
|
|
405
422
|
],
|
|
406
423
|
};
|
|
407
424
|
})());
|
|
408
|
-
const stream = await chat.sendMessageStream(
|
|
425
|
+
const stream = await chat.sendMessageStream({ model: DEFAULT_GEMINI_FLASH_MODEL }, 'test', 'prompt-id-normal-retry', new AbortController().signal);
|
|
409
426
|
for await (const _ of stream) {
|
|
410
427
|
// consume stream
|
|
411
428
|
}
|
|
@@ -437,7 +454,7 @@ describe('GeminiChat', () => {
|
|
|
437
454
|
}));
|
|
438
455
|
// ACT
|
|
439
456
|
const consumeStream = async () => {
|
|
440
|
-
const stream = await chat.sendMessageStream(
|
|
457
|
+
const stream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-bypass', new AbortController().signal);
|
|
441
458
|
// Consume the stream to trigger execution
|
|
442
459
|
for await (const _ of stream) {
|
|
443
460
|
// do nothing
|
|
@@ -484,14 +501,12 @@ describe('GeminiChat', () => {
|
|
|
484
501
|
})();
|
|
485
502
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(emptyStreamResponse);
|
|
486
503
|
// 3. Action: Send the function response back to the model and consume the stream.
|
|
487
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash', {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
response: { name: 'Vesuvio' },
|
|
492
|
-
},
|
|
504
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, {
|
|
505
|
+
functionResponse: {
|
|
506
|
+
name: 'find_restaurant',
|
|
507
|
+
response: { name: 'Vesuvio' },
|
|
493
508
|
},
|
|
494
|
-
}, 'prompt-id-stream-1');
|
|
509
|
+
}, 'prompt-id-stream-1', new AbortController().signal);
|
|
495
510
|
// 4. Assert: The stream processing should throw an InvalidStreamError.
|
|
496
511
|
await expect((async () => {
|
|
497
512
|
for await (const _ of stream) {
|
|
@@ -522,7 +537,7 @@ describe('GeminiChat', () => {
|
|
|
522
537
|
};
|
|
523
538
|
})();
|
|
524
539
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithToolCall);
|
|
525
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
540
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-1', new AbortController().signal);
|
|
526
541
|
// Should not throw an error
|
|
527
542
|
await expect((async () => {
|
|
528
543
|
for await (const _ of stream) {
|
|
@@ -546,7 +561,7 @@ describe('GeminiChat', () => {
|
|
|
546
561
|
};
|
|
547
562
|
})();
|
|
548
563
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithoutFinishReason);
|
|
549
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
564
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-1', new AbortController().signal);
|
|
550
565
|
await expect((async () => {
|
|
551
566
|
for await (const _ of stream) {
|
|
552
567
|
// consume stream
|
|
@@ -569,7 +584,7 @@ describe('GeminiChat', () => {
|
|
|
569
584
|
};
|
|
570
585
|
})();
|
|
571
586
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithEmptyResponse);
|
|
572
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
587
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-1', new AbortController().signal);
|
|
573
588
|
await expect((async () => {
|
|
574
589
|
for await (const _ of stream) {
|
|
575
590
|
// consume stream
|
|
@@ -592,7 +607,7 @@ describe('GeminiChat', () => {
|
|
|
592
607
|
};
|
|
593
608
|
})();
|
|
594
609
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(validStream);
|
|
595
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
610
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-1', new AbortController().signal);
|
|
596
611
|
// Should not throw an error
|
|
597
612
|
await expect((async () => {
|
|
598
613
|
for await (const _ of stream) {
|
|
@@ -616,7 +631,7 @@ describe('GeminiChat', () => {
|
|
|
616
631
|
};
|
|
617
632
|
})();
|
|
618
633
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithMalformedFunctionCall);
|
|
619
|
-
const stream = await chat.sendMessageStream('gemini-2.5-pro',
|
|
634
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.5-pro' }, 'test', 'prompt-id-malformed', new AbortController().signal);
|
|
620
635
|
// Should throw an error
|
|
621
636
|
await expect((async () => {
|
|
622
637
|
for await (const _ of stream) {
|
|
@@ -650,7 +665,7 @@ describe('GeminiChat', () => {
|
|
|
650
665
|
};
|
|
651
666
|
})());
|
|
652
667
|
// 2. Send a message
|
|
653
|
-
const stream = await chat.sendMessageStream('gemini-2.5-pro',
|
|
668
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.5-pro' }, 'test retry', 'prompt-id-retry-malformed', new AbortController().signal);
|
|
654
669
|
const events = [];
|
|
655
670
|
for await (const event of stream) {
|
|
656
671
|
events.push(event);
|
|
@@ -688,7 +703,7 @@ describe('GeminiChat', () => {
|
|
|
688
703
|
};
|
|
689
704
|
})();
|
|
690
705
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(response);
|
|
691
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
706
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'hello', 'prompt-id-1', new AbortController().signal);
|
|
692
707
|
for await (const _ of stream) {
|
|
693
708
|
// consume stream
|
|
694
709
|
}
|
|
@@ -700,9 +715,69 @@ describe('GeminiChat', () => {
|
|
|
700
715
|
parts: [{ text: 'hello' }],
|
|
701
716
|
},
|
|
702
717
|
],
|
|
703
|
-
config: {
|
|
718
|
+
config: {
|
|
719
|
+
systemInstruction: '',
|
|
720
|
+
tools: [],
|
|
721
|
+
temperature: 0,
|
|
722
|
+
thinkingConfig: {
|
|
723
|
+
thinkingBudget: DEFAULT_THINKING_MODE,
|
|
724
|
+
},
|
|
725
|
+
abortSignal: expect.any(AbortSignal),
|
|
726
|
+
},
|
|
704
727
|
}, 'prompt-id-1');
|
|
705
728
|
});
|
|
729
|
+
it('should use thinkingLevel and remove thinkingBudget for gemini-3 models', async () => {
|
|
730
|
+
const response = (async function* () {
|
|
731
|
+
yield {
|
|
732
|
+
candidates: [
|
|
733
|
+
{
|
|
734
|
+
content: { parts: [{ text: 'response' }], role: 'model' },
|
|
735
|
+
finishReason: 'STOP',
|
|
736
|
+
},
|
|
737
|
+
],
|
|
738
|
+
};
|
|
739
|
+
})();
|
|
740
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(response);
|
|
741
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-3-test-only-model-string-for-testing' }, 'hello', 'prompt-id-thinking-level', new AbortController().signal);
|
|
742
|
+
for await (const _ of stream) {
|
|
743
|
+
// consume stream
|
|
744
|
+
}
|
|
745
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledWith(expect.objectContaining({
|
|
746
|
+
model: 'gemini-3-test-only-model-string-for-testing',
|
|
747
|
+
config: expect.objectContaining({
|
|
748
|
+
thinkingConfig: {
|
|
749
|
+
thinkingBudget: undefined,
|
|
750
|
+
thinkingLevel: ThinkingLevel.HIGH,
|
|
751
|
+
},
|
|
752
|
+
}),
|
|
753
|
+
}), 'prompt-id-thinking-level');
|
|
754
|
+
});
|
|
755
|
+
it('should use thinkingBudget and remove thinkingLevel for non-gemini-3 models', async () => {
|
|
756
|
+
const response = (async function* () {
|
|
757
|
+
yield {
|
|
758
|
+
candidates: [
|
|
759
|
+
{
|
|
760
|
+
content: { parts: [{ text: 'response' }], role: 'model' },
|
|
761
|
+
finishReason: 'STOP',
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
};
|
|
765
|
+
})();
|
|
766
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(response);
|
|
767
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'hello', 'prompt-id-thinking-budget', new AbortController().signal);
|
|
768
|
+
for await (const _ of stream) {
|
|
769
|
+
// consume stream
|
|
770
|
+
}
|
|
771
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledWith(expect.objectContaining({
|
|
772
|
+
model: 'gemini-2.0-flash',
|
|
773
|
+
config: expect.objectContaining({
|
|
774
|
+
thinkingConfig: {
|
|
775
|
+
thinkingBudget: DEFAULT_THINKING_MODE,
|
|
776
|
+
thinkingLevel: undefined,
|
|
777
|
+
},
|
|
778
|
+
}),
|
|
779
|
+
}), 'prompt-id-thinking-budget');
|
|
780
|
+
});
|
|
706
781
|
});
|
|
707
782
|
describe('addHistory', () => {
|
|
708
783
|
it('should add a new content item to the history', () => {
|
|
@@ -740,7 +815,7 @@ describe('GeminiChat', () => {
|
|
|
740
815
|
candidates: [{ content: { parts: [{ text: '' }] } }],
|
|
741
816
|
};
|
|
742
817
|
})());
|
|
743
|
-
const stream = await chat.sendMessageStream('gemini-1.5-pro',
|
|
818
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-1.5-pro' }, 'test', 'prompt-id-no-retry', new AbortController().signal);
|
|
744
819
|
await expect((async () => {
|
|
745
820
|
for await (const _ of stream) {
|
|
746
821
|
// Must loop to trigger the internal logic that throws.
|
|
@@ -773,7 +848,7 @@ describe('GeminiChat', () => {
|
|
|
773
848
|
};
|
|
774
849
|
})());
|
|
775
850
|
// ACT: Send a message and collect all events from the stream.
|
|
776
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
851
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-yield-retry', new AbortController().signal);
|
|
777
852
|
const events = [];
|
|
778
853
|
for await (const event of stream) {
|
|
779
854
|
events.push(event);
|
|
@@ -805,7 +880,7 @@ describe('GeminiChat', () => {
|
|
|
805
880
|
],
|
|
806
881
|
};
|
|
807
882
|
})());
|
|
808
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
883
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test', 'prompt-id-retry-success', new AbortController().signal);
|
|
809
884
|
const chunks = [];
|
|
810
885
|
for await (const chunk of stream) {
|
|
811
886
|
chunks.push(chunk);
|
|
@@ -856,7 +931,7 @@ describe('GeminiChat', () => {
|
|
|
856
931
|
],
|
|
857
932
|
};
|
|
858
933
|
})());
|
|
859
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
934
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-retry-temperature', new AbortController().signal);
|
|
860
935
|
for await (const _ of stream) {
|
|
861
936
|
// consume stream
|
|
862
937
|
}
|
|
@@ -864,7 +939,7 @@ describe('GeminiChat', () => {
|
|
|
864
939
|
// First call should have original temperature
|
|
865
940
|
expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
866
941
|
config: expect.objectContaining({
|
|
867
|
-
temperature: 0
|
|
942
|
+
temperature: 0,
|
|
868
943
|
}),
|
|
869
944
|
}), 'prompt-id-retry-temperature');
|
|
870
945
|
// Second call (retry) should have temperature 1
|
|
@@ -887,7 +962,7 @@ describe('GeminiChat', () => {
|
|
|
887
962
|
],
|
|
888
963
|
};
|
|
889
964
|
})());
|
|
890
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
965
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test', 'prompt-id-retry-fail', new AbortController().signal);
|
|
891
966
|
await expect(async () => {
|
|
892
967
|
for await (const _ of stream) {
|
|
893
968
|
// Must loop to trigger the internal logic that throws.
|
|
@@ -936,7 +1011,7 @@ describe('GeminiChat', () => {
|
|
|
936
1011
|
it('should not retry on 400 Bad Request errors', async () => {
|
|
937
1012
|
const error400 = new ApiError({ message: 'Bad Request', status: 400 });
|
|
938
1013
|
vi.mocked(mockContentGenerator.generateContentStream).mockRejectedValue(error400);
|
|
939
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
1014
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-400', new AbortController().signal);
|
|
940
1015
|
await expect((async () => {
|
|
941
1016
|
for await (const _ of stream) {
|
|
942
1017
|
/* consume stream */
|
|
@@ -959,7 +1034,7 @@ describe('GeminiChat', () => {
|
|
|
959
1034
|
],
|
|
960
1035
|
};
|
|
961
1036
|
})());
|
|
962
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
1037
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-429-retry', new AbortController().signal);
|
|
963
1038
|
const events = [];
|
|
964
1039
|
for await (const event of stream) {
|
|
965
1040
|
events.push(event);
|
|
@@ -988,7 +1063,7 @@ describe('GeminiChat', () => {
|
|
|
988
1063
|
],
|
|
989
1064
|
};
|
|
990
1065
|
})());
|
|
991
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
1066
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-500-retry', new AbortController().signal);
|
|
992
1067
|
const events = [];
|
|
993
1068
|
for await (const event of stream) {
|
|
994
1069
|
events.push(event);
|
|
@@ -1024,7 +1099,7 @@ describe('GeminiChat', () => {
|
|
|
1024
1099
|
throw error;
|
|
1025
1100
|
}
|
|
1026
1101
|
});
|
|
1027
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
1102
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-fetch-error-retry', new AbortController().signal);
|
|
1028
1103
|
const events = [];
|
|
1029
1104
|
for await (const event of stream) {
|
|
1030
1105
|
events.push(event);
|
|
@@ -1067,7 +1142,7 @@ describe('GeminiChat', () => {
|
|
|
1067
1142
|
};
|
|
1068
1143
|
})());
|
|
1069
1144
|
// 3. Send a new message
|
|
1070
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
1145
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'Second question', 'prompt-id-retry-existing', new AbortController().signal);
|
|
1071
1146
|
for await (const _ of stream) {
|
|
1072
1147
|
// consume stream
|
|
1073
1148
|
}
|
|
@@ -1119,7 +1194,7 @@ describe('GeminiChat', () => {
|
|
|
1119
1194
|
};
|
|
1120
1195
|
})());
|
|
1121
1196
|
// 2. Call the method and consume the stream.
|
|
1122
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
1197
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test empty stream', 'prompt-id-empty-stream', new AbortController().signal);
|
|
1123
1198
|
const chunks = [];
|
|
1124
1199
|
for await (const chunk of stream) {
|
|
1125
1200
|
chunks.push(chunk);
|
|
@@ -1180,11 +1255,11 @@ describe('GeminiChat', () => {
|
|
|
1180
1255
|
.mockResolvedValueOnce(firstStreamGenerator)
|
|
1181
1256
|
.mockResolvedValueOnce(secondStreamGenerator);
|
|
1182
1257
|
// 3. Start the first stream and consume only the first chunk to pause it
|
|
1183
|
-
const firstStream = await chat.sendMessageStream('test-model',
|
|
1258
|
+
const firstStream = await chat.sendMessageStream({ model: 'test-model' }, 'first', 'prompt-1', new AbortController().signal);
|
|
1184
1259
|
const firstStreamIterator = firstStream[Symbol.asyncIterator]();
|
|
1185
1260
|
await firstStreamIterator.next();
|
|
1186
1261
|
// 4. While the first stream is paused, start the second call. It will block.
|
|
1187
|
-
const secondStreamPromise = chat.sendMessageStream('test-model',
|
|
1262
|
+
const secondStreamPromise = chat.sendMessageStream({ model: 'test-model' }, 'second', 'prompt-2', new AbortController().signal);
|
|
1188
1263
|
// 5. Assert that only one API call has been made so far.
|
|
1189
1264
|
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(1);
|
|
1190
1265
|
// 6. Unblock and fully consume the first stream to completion.
|
|
@@ -1224,7 +1299,7 @@ describe('GeminiChat', () => {
|
|
|
1224
1299
|
vi.mocked(mockContentGenerator.generateContentStream).mockImplementation(async () => (async function* () {
|
|
1225
1300
|
yield mockResponse;
|
|
1226
1301
|
})());
|
|
1227
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
1302
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'test message', 'prompt-id-res3', new AbortController().signal);
|
|
1228
1303
|
for await (const _ of stream) {
|
|
1229
1304
|
// consume stream
|
|
1230
1305
|
}
|
|
@@ -1287,7 +1362,7 @@ describe('GeminiChat', () => {
|
|
|
1287
1362
|
isInFallbackModeSpy.mockReturnValue(true);
|
|
1288
1363
|
return true; // Signal retry
|
|
1289
1364
|
});
|
|
1290
|
-
const stream = await chat.sendMessageStream('test-model',
|
|
1365
|
+
const stream = await chat.sendMessageStream({ model: 'test-model' }, 'trigger 429', 'prompt-id-fb1', new AbortController().signal);
|
|
1291
1366
|
// Consume stream to trigger logic
|
|
1292
1367
|
for await (const _ of stream) {
|
|
1293
1368
|
// no-op
|
|
@@ -1299,11 +1374,69 @@ describe('GeminiChat', () => {
|
|
|
1299
1374
|
const modelTurn = history[1];
|
|
1300
1375
|
expect(modelTurn.parts[0].text).toBe('Success on retry');
|
|
1301
1376
|
});
|
|
1377
|
+
it('should switch to DEFAULT_GEMINI_FLASH_MODEL and use thinkingBudget when falling back from a gemini-3 model', async () => {
|
|
1378
|
+
// ARRANGE
|
|
1379
|
+
const authType = AuthType.LOGIN_WITH_GOOGLE;
|
|
1380
|
+
vi.mocked(mockConfig.getContentGeneratorConfig).mockReturnValue({
|
|
1381
|
+
authType,
|
|
1382
|
+
});
|
|
1383
|
+
// Initial state: Not in fallback mode
|
|
1384
|
+
const isInFallbackModeSpy = vi.spyOn(mockConfig, 'isInFallbackMode');
|
|
1385
|
+
isInFallbackModeSpy.mockReturnValue(false);
|
|
1386
|
+
// Mock API calls:
|
|
1387
|
+
// 1. Fails with 429 (simulating gemini-3 failure)
|
|
1388
|
+
// 2. Succeeds (simulating fallback success)
|
|
1389
|
+
vi.mocked(mockContentGenerator.generateContentStream)
|
|
1390
|
+
.mockRejectedValueOnce(error429)
|
|
1391
|
+
.mockResolvedValueOnce((async function* () {
|
|
1392
|
+
yield {
|
|
1393
|
+
candidates: [
|
|
1394
|
+
{
|
|
1395
|
+
content: { parts: [{ text: 'Fallback success' }] },
|
|
1396
|
+
finishReason: 'STOP',
|
|
1397
|
+
},
|
|
1398
|
+
],
|
|
1399
|
+
};
|
|
1400
|
+
})());
|
|
1401
|
+
// Mock handleFallback to enable fallback mode and signal retry
|
|
1402
|
+
mockHandleFallback.mockImplementation(async () => {
|
|
1403
|
+
isInFallbackModeSpy.mockReturnValue(true); // Next call will see fallback mode = true
|
|
1404
|
+
return true;
|
|
1405
|
+
});
|
|
1406
|
+
// ACT
|
|
1407
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-3-test-model' }, // Start with a gemini-3 model
|
|
1408
|
+
'test fallback thinking', 'prompt-id-fb3', new AbortController().signal);
|
|
1409
|
+
for await (const _ of stream) {
|
|
1410
|
+
// consume stream
|
|
1411
|
+
}
|
|
1412
|
+
// ASSERT
|
|
1413
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
|
|
1414
|
+
// First call: gemini-3 model, thinkingLevel set
|
|
1415
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
1416
|
+
model: 'gemini-3-test-model',
|
|
1417
|
+
config: expect.objectContaining({
|
|
1418
|
+
thinkingConfig: {
|
|
1419
|
+
thinkingBudget: undefined,
|
|
1420
|
+
thinkingLevel: ThinkingLevel.HIGH,
|
|
1421
|
+
},
|
|
1422
|
+
}),
|
|
1423
|
+
}), 'prompt-id-fb3');
|
|
1424
|
+
// Second call: DEFAULT_GEMINI_FLASH_MODEL (due to fallback), thinkingBudget set (due to fix)
|
|
1425
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
|
1426
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1427
|
+
config: expect.objectContaining({
|
|
1428
|
+
thinkingConfig: {
|
|
1429
|
+
thinkingBudget: DEFAULT_THINKING_MODE,
|
|
1430
|
+
thinkingLevel: undefined,
|
|
1431
|
+
},
|
|
1432
|
+
}),
|
|
1433
|
+
}), 'prompt-id-fb3');
|
|
1434
|
+
});
|
|
1302
1435
|
it('should stop retrying if handleFallback returns false (e.g., auth intent)', async () => {
|
|
1303
1436
|
vi.mocked(mockConfig.getModel).mockReturnValue('gemini-pro');
|
|
1304
1437
|
vi.mocked(mockContentGenerator.generateContentStream).mockRejectedValue(error429);
|
|
1305
1438
|
mockHandleFallback.mockResolvedValue(false);
|
|
1306
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
1439
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test stop', 'prompt-id-fb2', new AbortController().signal);
|
|
1307
1440
|
await expect((async () => {
|
|
1308
1441
|
for await (const _ of stream) {
|
|
1309
1442
|
/* consume stream */
|
|
@@ -1347,7 +1480,7 @@ describe('GeminiChat', () => {
|
|
|
1347
1480
|
};
|
|
1348
1481
|
})());
|
|
1349
1482
|
// Send a message and consume the stream
|
|
1350
|
-
const stream = await chat.sendMessageStream('gemini-2.0-flash',
|
|
1483
|
+
const stream = await chat.sendMessageStream({ model: 'gemini-2.0-flash' }, 'test message', 'prompt-id-discard-test', new AbortController().signal);
|
|
1351
1484
|
const events = [];
|
|
1352
1485
|
for await (const event of stream) {
|
|
1353
1486
|
events.push(event);
|
|
@@ -1411,7 +1544,7 @@ describe('GeminiChat', () => {
|
|
|
1411
1544
|
};
|
|
1412
1545
|
})();
|
|
1413
1546
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
|
|
1414
|
-
await chat.sendMessageStream('test-model',
|
|
1547
|
+
await chat.sendMessageStream({ model: 'test-model' }, 'test', 'prompt-id-preview-model-reset', new AbortController().signal);
|
|
1415
1548
|
expect(mockConfig.setPreviewModelBypassMode).toHaveBeenCalledWith(false);
|
|
1416
1549
|
});
|
|
1417
1550
|
it('should reset previewModelFallbackMode to false upon successful Preview Model usage', async () => {
|
|
@@ -1426,7 +1559,7 @@ describe('GeminiChat', () => {
|
|
|
1426
1559
|
};
|
|
1427
1560
|
})();
|
|
1428
1561
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
|
|
1429
|
-
const resultStream = await chat.sendMessageStream(
|
|
1562
|
+
const resultStream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-preview-model-healing', new AbortController().signal);
|
|
1430
1563
|
for await (const _ of resultStream) {
|
|
1431
1564
|
// consume stream
|
|
1432
1565
|
}
|
|
@@ -1446,7 +1579,7 @@ describe('GeminiChat', () => {
|
|
|
1446
1579
|
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
|
|
1447
1580
|
// Simulate bypass mode being active (downgrade happened)
|
|
1448
1581
|
vi.mocked(mockConfig.isPreviewModelBypassMode).mockReturnValue(true);
|
|
1449
|
-
const resultStream = await chat.sendMessageStream(
|
|
1582
|
+
const resultStream = await chat.sendMessageStream({ model: PREVIEW_GEMINI_MODEL }, 'test', 'prompt-id-bypass-no-healing', new AbortController().signal);
|
|
1450
1583
|
for await (const _ of resultStream) {
|
|
1451
1584
|
// consume stream
|
|
1452
1585
|
}
|
|
@@ -1455,7 +1588,7 @@ describe('GeminiChat', () => {
|
|
|
1455
1588
|
});
|
|
1456
1589
|
describe('ensureActiveLoopHasThoughtSignatures', () => {
|
|
1457
1590
|
it('should add thoughtSignature to the first functionCall in each model turn of the active loop', () => {
|
|
1458
|
-
const chat = new GeminiChat(mockConfig,
|
|
1591
|
+
const chat = new GeminiChat(mockConfig, '', [], []);
|
|
1459
1592
|
const history = [
|
|
1460
1593
|
{ role: 'user', parts: [{ text: 'Old message' }] },
|
|
1461
1594
|
{
|
|
@@ -1504,7 +1637,7 @@ describe('GeminiChat', () => {
|
|
|
1504
1637
|
expect(newContents[5]?.parts?.[1]).not.toHaveProperty('thoughtSignature');
|
|
1505
1638
|
});
|
|
1506
1639
|
it('should not modify contents if there is no user text message', () => {
|
|
1507
|
-
const chat = new GeminiChat(mockConfig,
|
|
1640
|
+
const chat = new GeminiChat(mockConfig, '', [], []);
|
|
1508
1641
|
const history = [
|
|
1509
1642
|
{
|
|
1510
1643
|
role: 'user',
|
|
@@ -1520,13 +1653,13 @@ describe('GeminiChat', () => {
|
|
|
1520
1653
|
expect(newContents[1]?.parts?.[0]).not.toHaveProperty('thoughtSignature');
|
|
1521
1654
|
});
|
|
1522
1655
|
it('should handle an empty history', () => {
|
|
1523
|
-
const chat = new GeminiChat(mockConfig,
|
|
1656
|
+
const chat = new GeminiChat(mockConfig, '', []);
|
|
1524
1657
|
const history = [];
|
|
1525
1658
|
const newContents = chat.ensureActiveLoopHasThoughtSignatures(history);
|
|
1526
1659
|
expect(newContents).toEqual([]);
|
|
1527
1660
|
});
|
|
1528
1661
|
it('should handle history with only a user message', () => {
|
|
1529
|
-
const chat = new GeminiChat(mockConfig,
|
|
1662
|
+
const chat = new GeminiChat(mockConfig, '', []);
|
|
1530
1663
|
const history = [{ role: 'user', parts: [{ text: 'Hello' }] }];
|
|
1531
1664
|
const newContents = chat.ensureActiveLoopHasThoughtSignatures(history);
|
|
1532
1665
|
expect(newContents).toEqual(history);
|