@google/gemini-cli-core 0.7.0-nightly.20250912.68035591 → 0.7.0-preview.0
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/index.d.ts +5 -4
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/src/code_assist/converter.d.ts +1 -0
- package/dist/src/code_assist/converter.js +1 -0
- package/dist/src/code_assist/converter.js.map +1 -1
- package/dist/src/code_assist/converter.test.js +10 -0
- package/dist/src/code_assist/converter.test.js.map +1 -1
- package/dist/src/code_assist/oauth-credential-storage.d.ts +5 -7
- package/dist/src/code_assist/oauth-credential-storage.js +5 -8
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
- package/dist/src/code_assist/oauth-credential-storage.test.js +35 -33
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +28 -2
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +674 -536
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/config/config.d.ts +19 -0
- package/dist/src/config/config.js +48 -6
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +93 -1
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/models.d.ts +1 -0
- package/dist/src/config/models.js +1 -0
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/core/baseLlmClient.d.ts +1 -0
- package/dist/src/core/baseLlmClient.js +24 -0
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +63 -0
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.d.ts +3 -4
- package/dist/src/core/client.js +62 -145
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +119 -202
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +9 -0
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +16 -11
- package/dist/src/core/geminiChat.js +124 -150
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +342 -204
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.js +5 -5
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +1 -0
- package/dist/src/core/nonInteractiveToolExecutor.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/generated/git-commit.js.map +1 -1
- package/dist/src/ide/constants.d.ts +1 -0
- package/dist/src/ide/constants.js +1 -0
- package/dist/src/ide/constants.js.map +1 -1
- package/dist/src/ide/detect-ide.d.ts +44 -14
- package/dist/src/ide/detect-ide.js +29 -69
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +29 -46
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +28 -17
- package/dist/src/ide/ide-client.js +125 -57
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +44 -10
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.d.ts +2 -2
- package/dist/src/ide/ide-installer.js +15 -11
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +30 -12
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +0 -93
- package/dist/src/ide/ideContext.js +0 -45
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/ide/types.d.ts +141 -0
- package/dist/src/ide/types.js +73 -0
- package/dist/src/ide/types.js.map +1 -1
- package/dist/src/index.d.ts +4 -2
- package/dist/src/index.js +4 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +4 -1
- package/dist/src/mcp/oauth-provider.js +32 -26
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +2 -0
- package/dist/src/mcp/oauth-token-storage.js +25 -0
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.test.js +251 -160
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
- package/dist/src/mcp/token-storage/index.d.ts +11 -0
- package/dist/src/mcp/token-storage/index.js +12 -0
- package/dist/src/mcp/token-storage/index.js.map +1 -0
- package/dist/src/policy/policy-engine.js +11 -2
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +45 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/routing/modelRouterService.js +37 -3
- package/dist/src/routing/modelRouterService.js.map +1 -1
- package/dist/src/routing/modelRouterService.test.js +37 -11
- package/dist/src/routing/modelRouterService.test.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/classifierStrategy.js +173 -0
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -0
- package/dist/src/routing/strategies/classifierStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js +192 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/compositeStrategy.js +4 -3
- package/dist/src/routing/strategies/compositeStrategy.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.js +13 -12
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.test.js +3 -2
- package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +2 -1
- package/dist/src/services/chatRecordingService.js +3 -3
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/chatRecordingService.test.js +8 -3
- package/dist/src/services/chatRecordingService.test.js.map +1 -1
- package/dist/src/services/gitService.js +9 -12
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +10 -20
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.js +23 -18
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +27 -13
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.js +30 -15
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +43 -11
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/activity-detector.d.ts +41 -0
- package/dist/src/telemetry/activity-detector.js +61 -0
- package/dist/src/telemetry/activity-detector.js.map +1 -0
- package/dist/src/telemetry/activity-detector.test.d.ts +6 -0
- package/dist/src/telemetry/activity-detector.test.js +136 -0
- package/dist/src/telemetry/activity-detector.test.js.map +1 -0
- package/dist/src/telemetry/activity-types.d.ts +19 -0
- package/dist/src/telemetry/activity-types.js +21 -0
- package/dist/src/telemetry/activity-types.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +14 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +109 -3
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +63 -5
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +12 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +27 -0
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/config.d.ts +31 -0
- package/dist/src/telemetry/config.js +76 -0
- package/dist/src/telemetry/config.js.map +1 -0
- package/dist/src/telemetry/config.test.d.ts +6 -0
- package/dist/src/telemetry/config.test.js +124 -0
- package/dist/src/telemetry/config.test.js.map +1 -0
- package/dist/src/telemetry/constants.d.ts +9 -0
- package/dist/src/telemetry/constants.js +9 -0
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/gcp-exporters.d.ts +34 -0
- package/dist/src/telemetry/gcp-exporters.js +117 -0
- package/dist/src/telemetry/gcp-exporters.js.map +1 -0
- package/dist/src/telemetry/gcp-exporters.test.d.ts +6 -0
- package/dist/src/telemetry/gcp-exporters.test.js +318 -0
- package/dist/src/telemetry/gcp-exporters.test.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +5 -1
- package/dist/src/telemetry/index.js +5 -1
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +8 -1
- package/dist/src/telemetry/loggers.js +111 -2
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +207 -4
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +3 -0
- package/dist/src/telemetry/metrics.js +43 -1
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +42 -0
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sdk.js +20 -2
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +108 -0
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +50 -3
- package/dist/src/telemetry/types.js +91 -6
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
- package/dist/src/telemetry/uiTelemetry.js +2 -3
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +11 -11
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/tools/edit.js +4 -2
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +77 -1
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/message-bus-integration.test.d.ts +6 -0
- package/dist/src/tools/message-bus-integration.test.js +183 -0
- package/dist/src/tools/message-bus-integration.test.js.map +1 -0
- package/dist/src/tools/shell.js +8 -11
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +33 -37
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +0 -1
- package/dist/src/tools/smart-edit.js +3 -15
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +16 -1
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-registry.js +1 -0
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tools.d.ts +13 -4
- package/dist/src/tools/tools.js +101 -3
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/write-file.js +2 -2
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +16 -10
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +25 -0
- package/dist/src/tools/write-todos.js +150 -0
- package/dist/src/tools/write-todos.js.map +1 -0
- package/dist/src/tools/write-todos.test.d.ts +6 -0
- package/dist/src/tools/write-todos.test.js +89 -0
- package/dist/src/tools/write-todos.test.js.map +1 -0
- package/dist/src/utils/editCorrector.d.ts +7 -6
- package/dist/src/utils/editCorrector.js +61 -18
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +30 -79
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/editor.js +31 -44
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +61 -75
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/errorParsing.js +2 -2
- package/dist/src/utils/errorParsing.js.map +1 -1
- package/dist/src/utils/errorParsing.test.js +7 -7
- package/dist/src/utils/errorParsing.test.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +17 -8
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +12 -6
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.d.ts +2 -2
- package/dist/src/utils/nextSpeakerChecker.js +8 -2
- package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +40 -33
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +5 -0
- package/dist/src/utils/shell-utils.js +23 -0
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/textUtils.d.ts +5 -0
- package/dist/src/utils/textUtils.js +14 -0
- package/dist/src/utils/textUtils.js.map +1 -1
- package/dist/src/utils/textUtils.test.d.ts +6 -0
- package/dist/src/utils/textUtils.test.js +59 -0
- package/dist/src/utils/textUtils.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -1
- package/dist/google-gemini-cli-core-0.6.0-nightly.tgz +0 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
-
import {
|
|
7
|
+
import { findCompressSplitPoint, isThinkingDefault, isThinkingSupported, GeminiClient, } from './client.js';
|
|
8
8
|
import { AuthType, } from './contentGenerator.js';
|
|
9
9
|
import {} from './geminiChat.js';
|
|
10
10
|
import { CompressionStatus, GeminiEventType, Turn, } from './turn.js';
|
|
@@ -15,6 +15,7 @@ import { setSimulate429 } from '../utils/testUtils.js';
|
|
|
15
15
|
import { tokenLimit } from './tokenLimits.js';
|
|
16
16
|
import { ideContextStore } from '../ide/ideContext.js';
|
|
17
17
|
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
|
18
|
+
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
|
|
18
19
|
// Mock fs module to prevent actual file system operations during tests
|
|
19
20
|
const mockFileSystem = new Map();
|
|
20
21
|
vi.mock('node:fs', () => {
|
|
@@ -76,6 +77,11 @@ vi.mock('../telemetry/index.js', () => ({
|
|
|
76
77
|
logApiError: vi.fn(),
|
|
77
78
|
}));
|
|
78
79
|
vi.mock('../ide/ideContext.js');
|
|
80
|
+
vi.mock('../telemetry/uiTelemetry.js', () => ({
|
|
81
|
+
uiTelemetryService: {
|
|
82
|
+
setLastPromptTokenCount: vi.fn(),
|
|
83
|
+
},
|
|
84
|
+
}));
|
|
79
85
|
/**
|
|
80
86
|
* Array.fromAsync ponyfill, which will be available in es 2024.
|
|
81
87
|
*
|
|
@@ -89,41 +95,59 @@ async function fromAsync(promise) {
|
|
|
89
95
|
return results;
|
|
90
96
|
}
|
|
91
97
|
describe('findIndexAfterFraction', () => {
|
|
92
|
-
const history = [
|
|
93
|
-
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66
|
|
94
|
-
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68
|
|
95
|
-
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66
|
|
96
|
-
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68
|
|
97
|
-
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65
|
|
98
|
-
];
|
|
99
|
-
// Total length: 333
|
|
100
98
|
it('should throw an error for non-positive numbers', () => {
|
|
101
|
-
expect(() =>
|
|
99
|
+
expect(() => findCompressSplitPoint([], 0)).toThrow('Fraction must be between 0 and 1');
|
|
102
100
|
});
|
|
103
101
|
it('should throw an error for a fraction greater than or equal to 1', () => {
|
|
104
|
-
expect(() =>
|
|
102
|
+
expect(() => findCompressSplitPoint([], 1)).toThrow('Fraction must be between 0 and 1');
|
|
103
|
+
});
|
|
104
|
+
it('should handle an empty history', () => {
|
|
105
|
+
expect(findCompressSplitPoint([], 0.5)).toBe(0);
|
|
105
106
|
});
|
|
106
107
|
it('should handle a fraction in the middle', () => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
108
|
+
const history = [
|
|
109
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (19%)
|
|
110
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (40%)
|
|
111
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (60%)
|
|
112
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
113
|
+
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
114
|
+
];
|
|
115
|
+
expect(findCompressSplitPoint(history, 0.5)).toBe(2);
|
|
113
116
|
});
|
|
114
|
-
it('should handle a fraction
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
it('should handle a fraction of last index', () => {
|
|
118
|
+
const history = [
|
|
119
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (19%)
|
|
120
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (40%)
|
|
121
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (60%)
|
|
122
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
123
|
+
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
124
|
+
];
|
|
125
|
+
expect(findCompressSplitPoint(history, 0.9)).toBe(4);
|
|
121
126
|
});
|
|
122
|
-
it('should handle
|
|
123
|
-
|
|
127
|
+
it('should handle a fraction of after last index', () => {
|
|
128
|
+
const history = [
|
|
129
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (24%%)
|
|
130
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (50%)
|
|
131
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (74%)
|
|
132
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (100%)
|
|
133
|
+
];
|
|
134
|
+
expect(findCompressSplitPoint(history, 0.8)).toBe(4);
|
|
135
|
+
});
|
|
136
|
+
it('should return earlier splitpoint if no valid ones are after threshhold', () => {
|
|
137
|
+
const history = [
|
|
138
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] },
|
|
139
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] },
|
|
140
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] },
|
|
141
|
+
{ role: 'model', parts: [{ functionCall: {} }] },
|
|
142
|
+
];
|
|
143
|
+
// Can't return 4 because the previous item has a function call.
|
|
144
|
+
expect(findCompressSplitPoint(history, 0.99)).toBe(2);
|
|
124
145
|
});
|
|
125
146
|
it('should handle a history with only one item', () => {
|
|
126
|
-
|
|
147
|
+
const historyWithEmptyParts = [
|
|
148
|
+
{ role: 'user', parts: [{ text: 'Message 1' }] },
|
|
149
|
+
];
|
|
150
|
+
expect(findCompressSplitPoint(historyWithEmptyParts, 0.5)).toBe(0);
|
|
127
151
|
});
|
|
128
152
|
it('should handle history with weird parts', () => {
|
|
129
153
|
const historyWithEmptyParts = [
|
|
@@ -131,7 +155,7 @@ describe('findIndexAfterFraction', () => {
|
|
|
131
155
|
{ role: 'model', parts: [{ fileData: { fileUri: 'derp' } }] },
|
|
132
156
|
{ role: 'user', parts: [{ text: 'Message 2' }] },
|
|
133
157
|
];
|
|
134
|
-
expect(
|
|
158
|
+
expect(findCompressSplitPoint(historyWithEmptyParts, 0.5)).toBe(2);
|
|
135
159
|
});
|
|
136
160
|
});
|
|
137
161
|
describe('isThinkingSupported', () => {
|
|
@@ -168,6 +192,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
168
192
|
let mockGenerateContentFn;
|
|
169
193
|
beforeEach(async () => {
|
|
170
194
|
vi.resetAllMocks();
|
|
195
|
+
vi.mocked(uiTelemetryService.setLastPromptTokenCount).mockClear();
|
|
171
196
|
mockGenerateContentFn = vi.fn().mockResolvedValue({
|
|
172
197
|
candidates: [{ content: { parts: [{ text: '{"key": "value"}' }] } }],
|
|
173
198
|
});
|
|
@@ -177,7 +202,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
177
202
|
generateContent: mockGenerateContentFn,
|
|
178
203
|
generateContentStream: vi.fn(),
|
|
179
204
|
countTokens: vi.fn().mockResolvedValue({ totalTokens: 100 }),
|
|
180
|
-
embedContent: vi.fn(),
|
|
181
205
|
batchEmbedContents: vi.fn(),
|
|
182
206
|
};
|
|
183
207
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
@@ -229,11 +253,18 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
229
253
|
getChatCompression: vi.fn().mockReturnValue(undefined),
|
|
230
254
|
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
|
231
255
|
getUseSmartEdit: vi.fn().mockReturnValue(false),
|
|
256
|
+
getUseModelRouter: vi.fn().mockReturnValue(false),
|
|
232
257
|
getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
|
|
233
258
|
storage: {
|
|
234
259
|
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
|
|
235
260
|
},
|
|
236
261
|
getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
|
|
262
|
+
getBaseLlmClient: vi.fn().mockReturnValue({
|
|
263
|
+
generateJson: vi.fn().mockResolvedValue({
|
|
264
|
+
next_speaker: 'user',
|
|
265
|
+
reasoning: 'test',
|
|
266
|
+
}),
|
|
267
|
+
}),
|
|
237
268
|
};
|
|
238
269
|
client = new GeminiClient(mockConfig);
|
|
239
270
|
await client.initialize();
|
|
@@ -242,129 +273,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
242
273
|
afterEach(() => {
|
|
243
274
|
vi.restoreAllMocks();
|
|
244
275
|
});
|
|
245
|
-
describe('generateEmbedding', () => {
|
|
246
|
-
const texts = ['hello world', 'goodbye world'];
|
|
247
|
-
const testEmbeddingModel = 'test-embedding-model';
|
|
248
|
-
it('should call embedContent with correct parameters and return embeddings', async () => {
|
|
249
|
-
const mockEmbeddings = [
|
|
250
|
-
[0.1, 0.2, 0.3],
|
|
251
|
-
[0.4, 0.5, 0.6],
|
|
252
|
-
];
|
|
253
|
-
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
254
|
-
embeddings: [
|
|
255
|
-
{ values: mockEmbeddings[0] },
|
|
256
|
-
{ values: mockEmbeddings[1] },
|
|
257
|
-
],
|
|
258
|
-
});
|
|
259
|
-
const result = await client.generateEmbedding(texts);
|
|
260
|
-
expect(mockContentGenerator.embedContent).toHaveBeenCalledTimes(1);
|
|
261
|
-
expect(mockContentGenerator.embedContent).toHaveBeenCalledWith({
|
|
262
|
-
model: testEmbeddingModel,
|
|
263
|
-
contents: texts,
|
|
264
|
-
});
|
|
265
|
-
expect(result).toEqual(mockEmbeddings);
|
|
266
|
-
});
|
|
267
|
-
it('should return an empty array if an empty array is passed', async () => {
|
|
268
|
-
const result = await client.generateEmbedding([]);
|
|
269
|
-
expect(result).toEqual([]);
|
|
270
|
-
expect(mockContentGenerator.embedContent).not.toHaveBeenCalled();
|
|
271
|
-
});
|
|
272
|
-
it('should throw an error if API response has no embeddings array', async () => {
|
|
273
|
-
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({});
|
|
274
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
275
|
-
});
|
|
276
|
-
it('should throw an error if API response has an empty embeddings array', async () => {
|
|
277
|
-
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
278
|
-
embeddings: [],
|
|
279
|
-
});
|
|
280
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
281
|
-
});
|
|
282
|
-
it('should throw an error if API returns a mismatched number of embeddings', async () => {
|
|
283
|
-
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
284
|
-
embeddings: [{ values: [1, 2, 3] }], // Only one for two texts
|
|
285
|
-
});
|
|
286
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned a mismatched number of embeddings. Expected 2, got 1.');
|
|
287
|
-
});
|
|
288
|
-
it('should throw an error if any embedding has nullish values', async () => {
|
|
289
|
-
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
290
|
-
embeddings: [{ values: [1, 2, 3] }, { values: undefined }], // Second one is bad
|
|
291
|
-
});
|
|
292
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 1: "goodbye world"');
|
|
293
|
-
});
|
|
294
|
-
it('should throw an error if any embedding has an empty values array', async () => {
|
|
295
|
-
vi.mocked(mockContentGenerator.embedContent).mockResolvedValue({
|
|
296
|
-
embeddings: [{ values: [] }, { values: [1, 2, 3] }], // First one is bad
|
|
297
|
-
});
|
|
298
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 0: "hello world"');
|
|
299
|
-
});
|
|
300
|
-
it('should propagate errors from the API call', async () => {
|
|
301
|
-
vi.mocked(mockContentGenerator.embedContent).mockRejectedValue(new Error('API Failure'));
|
|
302
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API Failure');
|
|
303
|
-
});
|
|
304
|
-
});
|
|
305
|
-
describe('generateJson', () => {
|
|
306
|
-
it('should call generateContent with the correct parameters', async () => {
|
|
307
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
308
|
-
const schema = { type: 'string' };
|
|
309
|
-
const abortSignal = new AbortController().signal;
|
|
310
|
-
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
311
|
-
totalTokens: 1,
|
|
312
|
-
});
|
|
313
|
-
await client.generateJson(contents, schema, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
314
|
-
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
315
|
-
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
316
|
-
config: {
|
|
317
|
-
abortSignal,
|
|
318
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
319
|
-
temperature: 0,
|
|
320
|
-
topP: 1,
|
|
321
|
-
responseJsonSchema: schema,
|
|
322
|
-
responseMimeType: 'application/json',
|
|
323
|
-
},
|
|
324
|
-
contents,
|
|
325
|
-
}, 'test-session-id');
|
|
326
|
-
});
|
|
327
|
-
it('should allow overriding model and config', async () => {
|
|
328
|
-
const contents = [
|
|
329
|
-
{ role: 'user', parts: [{ text: 'hello' }] },
|
|
330
|
-
];
|
|
331
|
-
const schema = { type: 'string' };
|
|
332
|
-
const abortSignal = new AbortController().signal;
|
|
333
|
-
const customModel = 'custom-json-model';
|
|
334
|
-
const customConfig = { temperature: 0.9, topK: 20 };
|
|
335
|
-
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
336
|
-
totalTokens: 1,
|
|
337
|
-
});
|
|
338
|
-
await client.generateJson(contents, schema, abortSignal, customModel, customConfig);
|
|
339
|
-
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
340
|
-
model: customModel,
|
|
341
|
-
config: {
|
|
342
|
-
abortSignal,
|
|
343
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
344
|
-
temperature: 0.9,
|
|
345
|
-
topP: 1, // from default
|
|
346
|
-
topK: 20,
|
|
347
|
-
responseJsonSchema: schema,
|
|
348
|
-
responseMimeType: 'application/json',
|
|
349
|
-
},
|
|
350
|
-
contents,
|
|
351
|
-
}, 'test-session-id');
|
|
352
|
-
});
|
|
353
|
-
it('should use the Flash model when fallback mode is active', async () => {
|
|
354
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
355
|
-
const schema = { type: 'string' };
|
|
356
|
-
const abortSignal = new AbortController().signal;
|
|
357
|
-
const requestedModel = 'gemini-2.5-pro'; // A non-flash model
|
|
358
|
-
// Mock config to be in fallback mode
|
|
359
|
-
// We access the mock via the client instance which holds the mocked config
|
|
360
|
-
vi.spyOn(client['config'], 'isInFallbackMode').mockReturnValue(true);
|
|
361
|
-
await client.generateJson(contents, schema, abortSignal, requestedModel);
|
|
362
|
-
// Assert that the Flash model was used, not the requested model
|
|
363
|
-
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith(expect.objectContaining({
|
|
364
|
-
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
365
|
-
}), 'test-session-id');
|
|
366
|
-
});
|
|
367
|
-
});
|
|
368
276
|
describe('addHistory', () => {
|
|
369
277
|
it('should call chat.addHistory with the provided content', async () => {
|
|
370
278
|
const mockChat = {
|
|
@@ -434,7 +342,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
434
342
|
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
435
343
|
totalTokens: 1000,
|
|
436
344
|
});
|
|
437
|
-
await client.tryCompressChat('prompt-id-4'); // Fails
|
|
345
|
+
await client.tryCompressChat('prompt-id-4', false); // Fails
|
|
438
346
|
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
439
347
|
expect(result).toEqual({
|
|
440
348
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
@@ -447,16 +355,18 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
447
355
|
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
448
356
|
totalTokens: 1000,
|
|
449
357
|
});
|
|
450
|
-
const result = await client.tryCompressChat('prompt-id-4',
|
|
358
|
+
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
451
359
|
expect(result).toEqual({
|
|
452
360
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
453
361
|
newTokenCount: 5000,
|
|
454
362
|
originalTokenCount: 1000,
|
|
455
363
|
});
|
|
364
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledWith(5000);
|
|
365
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledTimes(1);
|
|
456
366
|
});
|
|
457
367
|
it('does not manipulate the source chat', async () => {
|
|
458
368
|
const { client, mockChat } = setup();
|
|
459
|
-
await client.tryCompressChat('prompt-id-4',
|
|
369
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
460
370
|
expect(client['chat']).toBe(mockChat); // a new chat session was not created
|
|
461
371
|
});
|
|
462
372
|
it('restores the history back to the original', async () => {
|
|
@@ -472,14 +382,14 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
472
382
|
const { client } = setup({
|
|
473
383
|
chatHistory: originalHistory,
|
|
474
384
|
});
|
|
475
|
-
const { compressionStatus } = await client.tryCompressChat('prompt-id-4');
|
|
385
|
+
const { compressionStatus } = await client.tryCompressChat('prompt-id-4', false);
|
|
476
386
|
expect(compressionStatus).toBe(CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT);
|
|
477
387
|
expect(client['chat']?.setHistory).toHaveBeenCalledWith(originalHistory);
|
|
478
388
|
});
|
|
479
389
|
it('will not attempt to compress context after a failure', async () => {
|
|
480
390
|
const { client } = setup();
|
|
481
|
-
await client.tryCompressChat('prompt-id-4');
|
|
482
|
-
const result = await client.tryCompressChat('prompt-id-5');
|
|
391
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
392
|
+
const result = await client.tryCompressChat('prompt-id-5', false);
|
|
483
393
|
// it counts tokens for {original, compressed} and then never again
|
|
484
394
|
expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
|
|
485
395
|
expect(result).toEqual({
|
|
@@ -499,7 +409,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
499
409
|
totalTokens: MOCKED_TOKEN_LIMIT * 0.699, // TOKEN_THRESHOLD_FOR_SUMMARIZATION = 0.7
|
|
500
410
|
});
|
|
501
411
|
const initialChat = client.getChat();
|
|
502
|
-
const result = await client.tryCompressChat('prompt-id-2');
|
|
412
|
+
const result = await client.tryCompressChat('prompt-id-2', false);
|
|
503
413
|
const newChat = client.getChat();
|
|
504
414
|
expect(tokenLimit).toHaveBeenCalled();
|
|
505
415
|
expect(result).toEqual({
|
|
@@ -536,11 +446,13 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
536
446
|
},
|
|
537
447
|
],
|
|
538
448
|
});
|
|
539
|
-
await client.tryCompressChat('prompt-id-3');
|
|
449
|
+
await client.tryCompressChat('prompt-id-3', false);
|
|
540
450
|
expect(ClearcutLogger.prototype.logChatCompressionEvent).toHaveBeenCalledWith(expect.objectContaining({
|
|
541
451
|
tokens_before: originalTokenCount,
|
|
542
452
|
tokens_after: newTokenCount,
|
|
543
453
|
}));
|
|
454
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledWith(newTokenCount);
|
|
455
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledTimes(1);
|
|
544
456
|
});
|
|
545
457
|
it('should trigger summarization if token count is at threshold with contextPercentageThreshold setting', async () => {
|
|
546
458
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
@@ -569,7 +481,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
569
481
|
],
|
|
570
482
|
});
|
|
571
483
|
const initialChat = client.getChat();
|
|
572
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
484
|
+
const result = await client.tryCompressChat('prompt-id-3', false);
|
|
573
485
|
const newChat = client.getChat();
|
|
574
486
|
expect(tokenLimit).toHaveBeenCalled();
|
|
575
487
|
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
@@ -620,7 +532,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
620
532
|
],
|
|
621
533
|
});
|
|
622
534
|
const initialChat = client.getChat();
|
|
623
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
535
|
+
const result = await client.tryCompressChat('prompt-id-3', false);
|
|
624
536
|
const newChat = client.getChat();
|
|
625
537
|
expect(tokenLimit).toHaveBeenCalled();
|
|
626
538
|
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
@@ -660,7 +572,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
660
572
|
],
|
|
661
573
|
});
|
|
662
574
|
const initialChat = client.getChat();
|
|
663
|
-
const result = await client.tryCompressChat('prompt-id-1',
|
|
575
|
+
const result = await client.tryCompressChat('prompt-id-1', false); // force = true
|
|
664
576
|
const newChat = client.getChat();
|
|
665
577
|
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
666
578
|
expect(result).toEqual({
|
|
@@ -671,45 +583,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
671
583
|
// Assert that the chat was reset
|
|
672
584
|
expect(newChat).not.toBe(initialChat);
|
|
673
585
|
});
|
|
674
|
-
it('should use current model from config for token counting after sendMessage', async () => {
|
|
675
|
-
const initialModel = mockConfig.getModel();
|
|
676
|
-
// mock the model has been changed between calls of `countTokens`
|
|
677
|
-
const firstCurrentModel = initialModel + '-changed-1';
|
|
678
|
-
const secondCurrentModel = initialModel + '-changed-2';
|
|
679
|
-
vi.mocked(mockConfig.getModel)
|
|
680
|
-
.mockReturnValueOnce(firstCurrentModel)
|
|
681
|
-
.mockReturnValueOnce(secondCurrentModel);
|
|
682
|
-
vi.mocked(mockContentGenerator.countTokens)
|
|
683
|
-
.mockResolvedValueOnce({ totalTokens: 100000 })
|
|
684
|
-
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
685
|
-
const mockSendMessage = vi.fn().mockResolvedValue({ text: 'Summary' });
|
|
686
|
-
const mockChatHistory = [
|
|
687
|
-
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
688
|
-
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
689
|
-
];
|
|
690
|
-
const mockChat = {
|
|
691
|
-
getHistory: vi.fn().mockReturnValue(mockChatHistory),
|
|
692
|
-
setHistory: vi.fn(),
|
|
693
|
-
sendMessage: mockSendMessage,
|
|
694
|
-
};
|
|
695
|
-
client['chat'] = mockChat;
|
|
696
|
-
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
|
697
|
-
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
698
|
-
expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
|
|
699
|
-
expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(1, {
|
|
700
|
-
model: firstCurrentModel,
|
|
701
|
-
contents: mockChatHistory,
|
|
702
|
-
});
|
|
703
|
-
expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(2, {
|
|
704
|
-
model: secondCurrentModel,
|
|
705
|
-
contents: expect.any(Array),
|
|
706
|
-
});
|
|
707
|
-
expect(result).toEqual({
|
|
708
|
-
compressionStatus: CompressionStatus.COMPRESSED,
|
|
709
|
-
originalTokenCount: 100000,
|
|
710
|
-
newTokenCount: 5000,
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
586
|
});
|
|
714
587
|
describe('sendMessageStream', () => {
|
|
715
588
|
it('emits a compression event when the context was automatically compressed', async () => {
|
|
@@ -785,6 +658,11 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
785
658
|
},
|
|
786
659
|
});
|
|
787
660
|
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
|
661
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
662
|
+
originalTokenCount: 0,
|
|
663
|
+
newTokenCount: 0,
|
|
664
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
665
|
+
});
|
|
788
666
|
mockTurnRunFn.mockReturnValue((async function* () {
|
|
789
667
|
yield { type: 'content', value: 'Hello' };
|
|
790
668
|
})());
|
|
@@ -870,6 +748,11 @@ ${JSON.stringify({
|
|
|
870
748
|
},
|
|
871
749
|
});
|
|
872
750
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
751
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
752
|
+
originalTokenCount: 0,
|
|
753
|
+
newTokenCount: 0,
|
|
754
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
755
|
+
});
|
|
873
756
|
const mockStream = (async function* () {
|
|
874
757
|
yield { type: 'content', value: 'Hello' };
|
|
875
758
|
})();
|
|
@@ -925,6 +808,11 @@ ${JSON.stringify({
|
|
|
925
808
|
},
|
|
926
809
|
});
|
|
927
810
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
811
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
812
|
+
originalTokenCount: 0,
|
|
813
|
+
newTokenCount: 0,
|
|
814
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
815
|
+
});
|
|
928
816
|
const mockStream = (async function* () {
|
|
929
817
|
yield { type: 'content', value: 'Hello' };
|
|
930
818
|
})();
|
|
@@ -1750,6 +1638,35 @@ ${JSON.stringify({
|
|
|
1750
1638
|
// Assert
|
|
1751
1639
|
expect(mockCheckNextSpeaker).not.toHaveBeenCalled();
|
|
1752
1640
|
});
|
|
1641
|
+
it('should abort linked signal when loop is detected', async () => {
|
|
1642
|
+
// Arrange
|
|
1643
|
+
vi.spyOn(client['loopDetector'], 'turnStarted').mockResolvedValue(false);
|
|
1644
|
+
vi.spyOn(client['loopDetector'], 'addAndCheck')
|
|
1645
|
+
.mockReturnValueOnce(false)
|
|
1646
|
+
.mockReturnValueOnce(true);
|
|
1647
|
+
let capturedSignal;
|
|
1648
|
+
mockTurnRunFn.mockImplementation((model, request, signal) => {
|
|
1649
|
+
capturedSignal = signal;
|
|
1650
|
+
return (async function* () {
|
|
1651
|
+
yield { type: 'content', value: 'First event' };
|
|
1652
|
+
yield { type: 'content', value: 'Second event' };
|
|
1653
|
+
})();
|
|
1654
|
+
});
|
|
1655
|
+
const mockChat = {
|
|
1656
|
+
addHistory: vi.fn(),
|
|
1657
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1658
|
+
};
|
|
1659
|
+
client['chat'] = mockChat;
|
|
1660
|
+
// Act
|
|
1661
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-loop');
|
|
1662
|
+
const events = [];
|
|
1663
|
+
for await (const event of stream) {
|
|
1664
|
+
events.push(event);
|
|
1665
|
+
}
|
|
1666
|
+
// Assert
|
|
1667
|
+
expect(events).toContainEqual({ type: GeminiEventType.LoopDetected });
|
|
1668
|
+
expect(capturedSignal.aborted).toBe(true);
|
|
1669
|
+
});
|
|
1753
1670
|
});
|
|
1754
1671
|
describe('generateContent', () => {
|
|
1755
1672
|
it('should call generateContent with the correct parameters', async () => {
|