@machina.ai/cell-cli-core 1.4.0-rc2 → 1.6.1-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/index.d.ts +6 -4
- package/dist/index.js +6 -4
- package/dist/index.js.map +1 -1
- package/dist/package.json +6 -2
- package/dist/src/code_assist/codeAssist.js +1 -1
- package/dist/src/code_assist/codeAssist.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 +25 -0
- package/dist/src/code_assist/oauth-credential-storage.js +109 -0
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js +136 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -0
- 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/code_assist/server.d.ts +1 -1
- package/dist/src/code_assist/server.js +24 -1
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +25 -0
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/code_assist/types.d.ts +17 -2
- package/dist/src/config/config.d.ts +62 -5
- package/dist/src/config/config.js +154 -42
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +235 -137
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/models.d.ts +15 -0
- package/dist/src/config/models.js +27 -0
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.d.ts +6 -0
- package/dist/src/config/models.test.js +55 -0
- package/dist/src/config/models.test.js.map +1 -0
- package/dist/src/config/storage.d.ts +2 -0
- package/dist/src/config/storage.js +6 -1
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/config/storage.test.js +4 -0
- package/dist/src/config/storage.test.js.map +1 -1
- package/dist/src/confirmation-bus/index.d.ts +7 -0
- package/dist/src/confirmation-bus/index.js +8 -0
- package/dist/src/confirmation-bus/index.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.d.ts +17 -0
- package/dist/src/confirmation-bus/message-bus.js +81 -0
- package/dist/src/confirmation-bus/message-bus.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.test.d.ts +6 -0
- package/dist/src/confirmation-bus/message-bus.test.js +164 -0
- package/dist/src/confirmation-bus/message-bus.test.js.map +1 -0
- package/dist/src/confirmation-bus/types.d.ts +38 -0
- package/dist/src/confirmation-bus/types.js +15 -0
- package/dist/src/confirmation-bus/types.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +46 -0
- package/dist/src/core/baseLlmClient.js +112 -0
- package/dist/src/core/baseLlmClient.js.map +1 -0
- package/dist/src/core/baseLlmClient.test.d.ts +6 -0
- package/dist/src/core/baseLlmClient.test.js +253 -0
- package/dist/src/core/baseLlmClient.test.js.map +1 -0
- package/dist/src/core/client.d.ts +8 -18
- package/dist/src/core/client.js +108 -227
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +269 -491
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +0 -1
- package/dist/src/core/contentGenerator.js +0 -4
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +1 -3
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +8 -3
- package/dist/src/core/coreToolScheduler.js +118 -8
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +314 -5
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +15 -38
- package/dist/src/core/geminiChat.js +108 -257
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +429 -491
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.js +7 -10
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +57 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.d.ts +5 -0
- package/dist/src/core/prompts.js +64 -43
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +146 -17
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/subagent.js +2 -4
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagent.test.js +12 -13
- package/dist/src/core/subagent.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +3 -1
- package/dist/src/core/turn.js +2 -2
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +18 -18
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/fallback/handler.d.ts +7 -0
- package/dist/src/fallback/handler.js +51 -0
- package/dist/src/fallback/handler.js.map +1 -0
- package/dist/src/fallback/handler.test.d.ts +6 -0
- package/dist/src/fallback/handler.test.js +130 -0
- package/dist/src/fallback/handler.test.js.map +1 -0
- package/dist/src/fallback/types.d.ts +14 -0
- package/dist/src/fallback/types.js +7 -0
- package/dist/src/fallback/types.js.map +1 -0
- 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 +3 -0
- package/dist/src/ide/constants.js +3 -0
- package/dist/src/ide/constants.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +51 -13
- package/dist/src/ide/ide-client.js +241 -35
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +236 -0
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.js +8 -2
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +13 -2
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +35 -377
- package/dist/src/ide/ideContext.js +60 -107
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/ide/ideContext.test.js +152 -24
- package/dist/src/ide/ideContext.test.js.map +1 -1
- package/dist/src/ide/process-utils.js +8 -1
- package/dist/src/ide/process-utils.js.map +1 -1
- package/dist/src/ide/types.d.ts +486 -0
- package/dist/src/ide/types.js +138 -0
- package/dist/src/ide/types.js.map +1 -0
- package/dist/src/index.d.ts +6 -1
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +1 -0
- package/dist/src/mcp/oauth-provider.js +22 -17
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +149 -13
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +10 -6
- package/dist/src/mcp/oauth-token-storage.js +48 -16
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.test.js +254 -163
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
- package/dist/src/mcp/oauth-utils.js +1 -0
- package/dist/src/mcp/oauth-utils.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/output/json-formatter.d.ts +11 -0
- package/dist/src/output/json-formatter.js +30 -0
- package/dist/src/output/json-formatter.js.map +1 -0
- package/dist/src/output/json-formatter.test.d.ts +6 -0
- package/dist/src/output/json-formatter.test.js +266 -0
- package/dist/src/output/json-formatter.test.js.map +1 -0
- package/dist/src/output/types.d.ts +20 -0
- package/dist/src/output/types.js +11 -0
- package/dist/src/output/types.js.map +1 -0
- package/dist/src/policy/index.d.ts +7 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/policy-engine.d.ts +30 -0
- package/dist/src/policy/policy-engine.js +83 -0
- package/dist/src/policy/policy-engine.js.map +1 -0
- package/dist/src/policy/policy-engine.test.d.ts +6 -0
- package/dist/src/policy/policy-engine.test.js +470 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -0
- package/dist/src/policy/stable-stringify.d.ts +58 -0
- package/dist/src/policy/stable-stringify.js +122 -0
- package/dist/src/policy/stable-stringify.js.map +1 -0
- package/dist/src/policy/types.d.ts +47 -0
- package/dist/src/policy/types.js +12 -0
- package/dist/src/policy/types.js.map +1 -0
- package/dist/src/routing/modelRouterService.d.ts +23 -0
- package/dist/src/routing/modelRouterService.js +70 -0
- package/dist/src/routing/modelRouterService.js.map +1 -0
- package/dist/src/routing/modelRouterService.test.d.ts +6 -0
- package/dist/src/routing/modelRouterService.test.js +98 -0
- package/dist/src/routing/modelRouterService.test.js.map +1 -0
- package/dist/src/routing/routingStrategy.d.ts +62 -0
- package/dist/src/routing/routingStrategy.js +7 -0
- package/dist/src/routing/routingStrategy.js.map +1 -0
- 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.d.ts +26 -0
- package/dist/src/routing/strategies/compositeStrategy.js +67 -0
- package/dist/src/routing/strategies/compositeStrategy.js.map +1 -0
- package/dist/src/routing/strategies/compositeStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/compositeStrategy.test.js +123 -0
- package/dist/src/routing/strategies/compositeStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/defaultStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/defaultStrategy.js +20 -0
- package/dist/src/routing/strategies/defaultStrategy.js.map +1 -0
- package/dist/src/routing/strategies/defaultStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/defaultStrategy.test.js +26 -0
- package/dist/src/routing/strategies/defaultStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/fallbackStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/fallbackStrategy.js +25 -0
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js +55 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/overrideStrategy.d.ts +15 -0
- package/dist/src/routing/strategies/overrideStrategy.js +28 -0
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -0
- package/dist/src/routing/strategies/overrideStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js +42 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -0
- 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/fileDiscoveryService.d.ts +10 -0
- package/dist/src/services/fileDiscoveryService.js +31 -17
- package/dist/src/services/fileDiscoveryService.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.d.ts +5 -0
- package/dist/src/services/loopDetectionService.js +36 -20
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +41 -12
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +34 -2
- package/dist/src/services/shellExecutionService.js +192 -43
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +184 -55
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +14 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +107 -5
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +82 -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 +13 -2
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +33 -2
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +7 -0
- package/dist/src/telemetry/constants.js +7 -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/high-water-mark-tracker.d.ts +43 -0
- package/dist/src/telemetry/high-water-mark-tracker.js +88 -0
- package/dist/src/telemetry/high-water-mark-tracker.js.map +1 -0
- package/dist/src/telemetry/high-water-mark-tracker.test.d.ts +6 -0
- package/dist/src/telemetry/high-water-mark-tracker.test.js +152 -0
- package/dist/src/telemetry/high-water-mark-tracker.test.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +5 -2
- package/dist/src/telemetry/index.js +5 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +8 -1
- package/dist/src/telemetry/loggers.js +114 -7
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +232 -39
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +3 -1
- package/dist/src/telemetry/metrics.js +32 -3
- 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/rate-limiter.d.ts +48 -0
- package/dist/src/telemetry/rate-limiter.js +100 -0
- package/dist/src/telemetry/rate-limiter.js.map +1 -0
- package/dist/src/telemetry/rate-limiter.test.d.ts +6 -0
- package/dist/src/telemetry/rate-limiter.test.js +207 -0
- package/dist/src/telemetry/rate-limiter.test.js.map +1 -0
- package/dist/src/telemetry/sdk.js +19 -1
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +95 -0
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/types.d.ts +60 -3
- package/dist/src/telemetry/types.js +93 -3
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/tools/edit.js +12 -5
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +120 -9
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +5 -1
- package/dist/src/tools/glob.js +24 -17
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +51 -0
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/ls.js +19 -32
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/ls.test.js +140 -280
- package/dist/src/tools/ls.test.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.js +5 -21
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/mcp-client.js +5 -5
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-tool.js +30 -2
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +117 -0
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/read-file.js +7 -2
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +29 -0
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +1 -1
- package/dist/src/tools/read-many-files.js +17 -49
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +8 -0
- package/dist/src/tools/ripGrep.js +26 -1
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +107 -5
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +12 -2
- package/dist/src/tools/shell.js +20 -27
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +33 -68
- 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 +12 -19
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +68 -9
- 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 +8 -5
- package/dist/src/tools/tools.js +9 -2
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/write-file.js +4 -5
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +94 -10
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.js +11 -5
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- 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/errors.d.ts +6 -0
- package/dist/src/utils/errors.js +10 -0
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/fileUtils.d.ts +1 -0
- package/dist/src/utils/fileUtils.js +10 -0
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +34 -9
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/filesearch/crawler.test.js +1 -1
- package/dist/src/utils/filesearch/crawler.test.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.test.js +1 -1
- package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -1
- package/dist/src/utils/filesearch/ignore.test.js +1 -1
- package/dist/src/utils/filesearch/ignore.test.js.map +1 -1
- package/dist/src/utils/flashFallback.test.d.ts +6 -0
- package/dist/src/utils/{flashFallback.integration.test.js → flashFallback.test.js} +31 -27
- package/dist/src/utils/flashFallback.test.js.map +1 -0
- package/dist/src/utils/geminiIgnoreParser.d.ts +18 -0
- package/dist/src/utils/geminiIgnoreParser.js +61 -0
- package/dist/src/utils/geminiIgnoreParser.js.map +1 -0
- package/dist/src/utils/geminiIgnoreParser.test.d.ts +6 -0
- package/dist/src/utils/geminiIgnoreParser.test.js +50 -0
- package/dist/src/utils/geminiIgnoreParser.test.js.map +1 -0
- package/dist/src/utils/gitIgnoreParser.d.ts +3 -8
- package/dist/src/utils/gitIgnoreParser.js +60 -60
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +18 -53
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
- package/dist/src/utils/installationManager.test.js +1 -1
- package/dist/src/utils/installationManager.test.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.d.ts +4 -3
- package/dist/src/utils/llm-edit-fixer.js +19 -10
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.d.ts +6 -0
- package/dist/src/utils/llm-edit-fixer.test.js +105 -0
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -0
- 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 +52 -74
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/promptIdContext.d.ts +7 -0
- package/dist/src/utils/promptIdContext.js +8 -0
- package/dist/src/utils/promptIdContext.js.map +1 -0
- 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/terminalSerializer.d.ts +28 -0
- package/dist/src/utils/terminalSerializer.js +432 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -0
- package/dist/src/utils/terminalSerializer.test.d.ts +6 -0
- package/dist/src/utils/terminalSerializer.test.js +176 -0
- package/dist/src/utils/terminalSerializer.test.js.map +1 -0
- 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/src/utils/userAccountManager.test.js +1 -1
- package/dist/src/utils/userAccountManager.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +7 -2
- package/dist/src/utils/flashFallback.integration.test.js.map +0 -1
- package/dist/src/utils/ide-trust.d.ts +0 -10
- package/dist/src/utils/ide-trust.js +0 -14
- package/dist/src/utils/ide-trust.js.map +0 -1
- /package/dist/src/{utils/flashFallback.integration.test.d.ts → code_assist/oauth-credential-storage.test.d.ts} +0 -0
|
@@ -4,18 +4,16 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
-
import { GoogleGenAI } from '@google/genai';
|
|
8
7
|
import { findIndexAfterFraction, isThinkingDefault, isThinkingSupported, GeminiClient, } from './client.js';
|
|
9
8
|
import { AuthType, } from './contentGenerator.js';
|
|
10
9
|
import {} from './geminiChat.js';
|
|
11
|
-
import { Config } from '../config/config.js';
|
|
12
10
|
import { CompressionStatus, GeminiEventType, Turn, } from './turn.js';
|
|
13
11
|
import { getCoreSystemPrompt } from './prompts.js';
|
|
14
12
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
|
15
13
|
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
|
16
14
|
import { setSimulate429 } from '../utils/testUtils.js';
|
|
17
15
|
import { tokenLimit } from './tokenLimits.js';
|
|
18
|
-
import {
|
|
16
|
+
import { ideContextStore } from '../ide/ideContext.js';
|
|
19
17
|
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
|
20
18
|
// Mock fs module to prevent actual file system operations during tests
|
|
21
19
|
const mockFileSystem = new Map();
|
|
@@ -41,11 +39,7 @@ vi.mock('node:fs', () => {
|
|
|
41
39
|
};
|
|
42
40
|
});
|
|
43
41
|
// --- Mocks ---
|
|
44
|
-
const mockChatCreateFn = vi.fn();
|
|
45
|
-
const mockGenerateContentFn = vi.fn();
|
|
46
|
-
const mockEmbedContentFn = vi.fn();
|
|
47
42
|
const mockTurnRunFn = vi.fn();
|
|
48
|
-
vi.mock('@google/genai');
|
|
49
43
|
vi.mock('./turn', async (importOriginal) => {
|
|
50
44
|
const actual = await importOriginal();
|
|
51
45
|
// Define a mock class that has the same shape as the real Turn
|
|
@@ -114,22 +108,22 @@ describe('findIndexAfterFraction', () => {
|
|
|
114
108
|
// 0: 66
|
|
115
109
|
// 1: 66 + 68 = 134
|
|
116
110
|
// 2: 134 + 66 = 200
|
|
117
|
-
// 200 >= 166.5, so index is
|
|
118
|
-
expect(findIndexAfterFraction(history, 0.5)).toBe(
|
|
111
|
+
// 200 >= 166.5, so index is 3
|
|
112
|
+
expect(findIndexAfterFraction(history, 0.5)).toBe(3);
|
|
119
113
|
});
|
|
120
114
|
it('should handle a fraction that results in the last index', () => {
|
|
121
115
|
// 333 * 0.9 = 299.7
|
|
122
116
|
// ...
|
|
123
117
|
// 3: 200 + 68 = 268
|
|
124
118
|
// 4: 268 + 65 = 333
|
|
125
|
-
// 333 >= 299.7, so index is
|
|
126
|
-
expect(findIndexAfterFraction(history, 0.9)).toBe(
|
|
119
|
+
// 333 >= 299.7, so index is 5
|
|
120
|
+
expect(findIndexAfterFraction(history, 0.9)).toBe(5);
|
|
127
121
|
});
|
|
128
122
|
it('should handle an empty history', () => {
|
|
129
123
|
expect(findIndexAfterFraction([], 0.5)).toBe(0);
|
|
130
124
|
});
|
|
131
125
|
it('should handle a history with only one item', () => {
|
|
132
|
-
expect(findIndexAfterFraction(history.slice(0, 1), 0.5)).toBe(
|
|
126
|
+
expect(findIndexAfterFraction(history.slice(0, 1), 0.5)).toBe(1);
|
|
133
127
|
});
|
|
134
128
|
it('should handle history with weird parts', () => {
|
|
135
129
|
const historyWithEmptyParts = [
|
|
@@ -137,7 +131,7 @@ describe('findIndexAfterFraction', () => {
|
|
|
137
131
|
{ role: 'model', parts: [{ fileData: { fileUri: 'derp' } }] },
|
|
138
132
|
{ role: 'user', parts: [{ text: 'Message 2' }] },
|
|
139
133
|
];
|
|
140
|
-
expect(findIndexAfterFraction(historyWithEmptyParts, 0.5)).toBe(
|
|
134
|
+
expect(findIndexAfterFraction(historyWithEmptyParts, 0.5)).toBe(2);
|
|
141
135
|
});
|
|
142
136
|
});
|
|
143
137
|
describe('isThinkingSupported', () => {
|
|
@@ -168,33 +162,23 @@ describe('isThinkingDefault', () => {
|
|
|
168
162
|
});
|
|
169
163
|
});
|
|
170
164
|
describe('Gemini Client (client.ts)', () => {
|
|
165
|
+
let mockContentGenerator;
|
|
166
|
+
let mockConfig;
|
|
171
167
|
let client;
|
|
168
|
+
let mockGenerateContentFn;
|
|
172
169
|
beforeEach(async () => {
|
|
173
170
|
vi.resetAllMocks();
|
|
171
|
+
mockGenerateContentFn = vi.fn().mockResolvedValue({
|
|
172
|
+
candidates: [{ content: { parts: [{ text: '{"key": "value"}' }] } }],
|
|
173
|
+
});
|
|
174
174
|
// Disable 429 simulation for tests
|
|
175
175
|
setSimulate429(false);
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
generateContent: mockGenerateContentFn,
|
|
183
|
-
embedContent: mockEmbedContentFn,
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
return mock;
|
|
187
|
-
});
|
|
188
|
-
mockChatCreateFn.mockResolvedValue({});
|
|
189
|
-
mockGenerateContentFn.mockResolvedValue({
|
|
190
|
-
candidates: [
|
|
191
|
-
{
|
|
192
|
-
content: {
|
|
193
|
-
parts: [{ text: '{"key": "value"}' }],
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
});
|
|
176
|
+
mockContentGenerator = {
|
|
177
|
+
generateContent: mockGenerateContentFn,
|
|
178
|
+
generateContentStream: vi.fn(),
|
|
179
|
+
countTokens: vi.fn().mockResolvedValue({ totalTokens: 100 }),
|
|
180
|
+
batchEmbedContents: vi.fn(),
|
|
181
|
+
};
|
|
198
182
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
199
183
|
// that depends on a fully-formed Config object, we need to mock the
|
|
200
184
|
// entire implementation of Config for these tests.
|
|
@@ -204,12 +188,11 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
204
188
|
};
|
|
205
189
|
const fileService = new FileDiscoveryService('/test/dir');
|
|
206
190
|
const contentGeneratorConfig = {
|
|
207
|
-
model: 'test-model',
|
|
208
191
|
apiKey: 'test-key',
|
|
209
192
|
vertexai: false,
|
|
210
193
|
authType: AuthType.USE_GEMINI,
|
|
211
194
|
};
|
|
212
|
-
|
|
195
|
+
mockConfig = {
|
|
213
196
|
getContentGeneratorConfig: vi
|
|
214
197
|
.fn()
|
|
215
198
|
.mockReturnValue(contentGeneratorConfig),
|
|
@@ -237,164 +220,34 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
237
220
|
getDirectories: vi.fn().mockReturnValue(['/test/dir']),
|
|
238
221
|
}),
|
|
239
222
|
getGeminiClient: vi.fn(),
|
|
223
|
+
getModelRouterService: vi.fn().mockReturnValue({
|
|
224
|
+
route: vi.fn().mockResolvedValue({ model: 'default-routed-model' }),
|
|
225
|
+
}),
|
|
226
|
+
isInFallbackMode: vi.fn().mockReturnValue(false),
|
|
240
227
|
setFallbackMode: vi.fn(),
|
|
241
228
|
getChatCompression: vi.fn().mockReturnValue(undefined),
|
|
242
229
|
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
|
243
230
|
getUseSmartEdit: vi.fn().mockReturnValue(false),
|
|
231
|
+
getUseModelRouter: vi.fn().mockReturnValue(false),
|
|
244
232
|
getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
|
|
245
233
|
storage: {
|
|
246
234
|
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
|
|
247
235
|
},
|
|
236
|
+
getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
|
|
237
|
+
getBaseLlmClient: vi.fn().mockReturnValue({
|
|
238
|
+
generateJson: vi.fn().mockResolvedValue({
|
|
239
|
+
next_speaker: 'user',
|
|
240
|
+
reasoning: 'test',
|
|
241
|
+
}),
|
|
242
|
+
}),
|
|
248
243
|
};
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
// and the constructor will use the mocked GoogleGenAI
|
|
253
|
-
client = new GeminiClient(new Config({ sessionId: 'test-session-id' }));
|
|
254
|
-
mockConfigObject.getGeminiClient.mockReturnValue(client);
|
|
255
|
-
await client.initialize(contentGeneratorConfig);
|
|
244
|
+
client = new GeminiClient(mockConfig);
|
|
245
|
+
await client.initialize();
|
|
246
|
+
vi.mocked(mockConfig.getGeminiClient).mockReturnValue(client);
|
|
256
247
|
});
|
|
257
248
|
afterEach(() => {
|
|
258
249
|
vi.restoreAllMocks();
|
|
259
250
|
});
|
|
260
|
-
// NOTE: The following tests for startChat were removed due to persistent issues with
|
|
261
|
-
// the @google/genai mock. Specifically, the mockChatCreateFn (representing instance.chats.create)
|
|
262
|
-
// was not being detected as called by the GeminiClient instance.
|
|
263
|
-
// This likely points to a subtle issue in how the GoogleGenerativeAI class constructor
|
|
264
|
-
// and its instance methods are mocked and then used by the class under test.
|
|
265
|
-
// For future debugging, ensure that the `this.client` in `GeminiClient` (which is an
|
|
266
|
-
// instance of the mocked GoogleGenerativeAI) correctly has its `chats.create` method
|
|
267
|
-
// pointing to `mockChatCreateFn`.
|
|
268
|
-
// it('startChat should call getCoreSystemPrompt with userMemory and pass to chats.create', async () => { ... });
|
|
269
|
-
// it('startChat should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
270
|
-
// NOTE: The following tests for generateJson were removed due to persistent issues with
|
|
271
|
-
// the @google/genai mock, similar to the startChat tests. The mockGenerateContentFn
|
|
272
|
-
// (representing instance.models.generateContent) was not being detected as called, or the mock
|
|
273
|
-
// was not preventing an actual API call (leading to API key errors).
|
|
274
|
-
// For future debugging, ensure `this.client.models.generateContent` in `GeminiClient` correctly
|
|
275
|
-
// uses the `mockGenerateContentFn`.
|
|
276
|
-
// it('generateJson should call getCoreSystemPrompt with userMemory and pass to generateContent', async () => { ... });
|
|
277
|
-
// it('generateJson should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
278
|
-
describe('generateEmbedding', () => {
|
|
279
|
-
const texts = ['hello world', 'goodbye world'];
|
|
280
|
-
const testEmbeddingModel = 'test-embedding-model';
|
|
281
|
-
it('should call embedContent with correct parameters and return embeddings', async () => {
|
|
282
|
-
const mockEmbeddings = [
|
|
283
|
-
[0.1, 0.2, 0.3],
|
|
284
|
-
[0.4, 0.5, 0.6],
|
|
285
|
-
];
|
|
286
|
-
const mockResponse = {
|
|
287
|
-
embeddings: [
|
|
288
|
-
{ values: mockEmbeddings[0] },
|
|
289
|
-
{ values: mockEmbeddings[1] },
|
|
290
|
-
],
|
|
291
|
-
};
|
|
292
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
293
|
-
const result = await client.generateEmbedding(texts);
|
|
294
|
-
expect(mockEmbedContentFn).toHaveBeenCalledTimes(1);
|
|
295
|
-
expect(mockEmbedContentFn).toHaveBeenCalledWith({
|
|
296
|
-
model: testEmbeddingModel,
|
|
297
|
-
contents: texts,
|
|
298
|
-
});
|
|
299
|
-
expect(result).toEqual(mockEmbeddings);
|
|
300
|
-
});
|
|
301
|
-
it('should return an empty array if an empty array is passed', async () => {
|
|
302
|
-
const result = await client.generateEmbedding([]);
|
|
303
|
-
expect(result).toEqual([]);
|
|
304
|
-
expect(mockEmbedContentFn).not.toHaveBeenCalled();
|
|
305
|
-
});
|
|
306
|
-
it('should throw an error if API response has no embeddings array', async () => {
|
|
307
|
-
mockEmbedContentFn.mockResolvedValue({}); // No `embeddings` key
|
|
308
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
309
|
-
});
|
|
310
|
-
it('should throw an error if API response has an empty embeddings array', async () => {
|
|
311
|
-
const mockResponse = {
|
|
312
|
-
embeddings: [],
|
|
313
|
-
};
|
|
314
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
315
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
316
|
-
});
|
|
317
|
-
it('should throw an error if API returns a mismatched number of embeddings', async () => {
|
|
318
|
-
const mockResponse = {
|
|
319
|
-
embeddings: [{ values: [1, 2, 3] }], // Only one for two texts
|
|
320
|
-
};
|
|
321
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
322
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned a mismatched number of embeddings. Expected 2, got 1.');
|
|
323
|
-
});
|
|
324
|
-
it('should throw an error if any embedding has nullish values', async () => {
|
|
325
|
-
const mockResponse = {
|
|
326
|
-
embeddings: [{ values: [1, 2, 3] }, { values: undefined }], // Second one is bad
|
|
327
|
-
};
|
|
328
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
329
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 1: "goodbye world"');
|
|
330
|
-
});
|
|
331
|
-
it('should throw an error if any embedding has an empty values array', async () => {
|
|
332
|
-
const mockResponse = {
|
|
333
|
-
embeddings: [{ values: [] }, { values: [1, 2, 3] }], // First one is bad
|
|
334
|
-
};
|
|
335
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
336
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 0: "hello world"');
|
|
337
|
-
});
|
|
338
|
-
it('should propagate errors from the API call', async () => {
|
|
339
|
-
const apiError = new Error('API Failure');
|
|
340
|
-
mockEmbedContentFn.mockRejectedValue(apiError);
|
|
341
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API Failure');
|
|
342
|
-
});
|
|
343
|
-
});
|
|
344
|
-
describe('generateJson', () => {
|
|
345
|
-
it('should call generateContent with the correct parameters', async () => {
|
|
346
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
347
|
-
const schema = { type: 'string' };
|
|
348
|
-
const abortSignal = new AbortController().signal;
|
|
349
|
-
// Mock countTokens
|
|
350
|
-
const mockGenerator = {
|
|
351
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
352
|
-
generateContent: mockGenerateContentFn,
|
|
353
|
-
};
|
|
354
|
-
client['contentGenerator'] = mockGenerator;
|
|
355
|
-
await client.generateJson(contents, schema, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
356
|
-
expect(mockGenerateContentFn).toHaveBeenCalledWith({
|
|
357
|
-
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
358
|
-
config: {
|
|
359
|
-
abortSignal,
|
|
360
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
361
|
-
temperature: 0,
|
|
362
|
-
topP: 1,
|
|
363
|
-
responseJsonSchema: schema,
|
|
364
|
-
responseMimeType: 'application/json',
|
|
365
|
-
},
|
|
366
|
-
contents,
|
|
367
|
-
}, 'test-session-id');
|
|
368
|
-
});
|
|
369
|
-
it('should allow overriding model and config', async () => {
|
|
370
|
-
const contents = [
|
|
371
|
-
{ role: 'user', parts: [{ text: 'hello' }] },
|
|
372
|
-
];
|
|
373
|
-
const schema = { type: 'string' };
|
|
374
|
-
const abortSignal = new AbortController().signal;
|
|
375
|
-
const customModel = 'custom-json-model';
|
|
376
|
-
const customConfig = { temperature: 0.9, topK: 20 };
|
|
377
|
-
const mockGenerator = {
|
|
378
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
379
|
-
generateContent: mockGenerateContentFn,
|
|
380
|
-
};
|
|
381
|
-
client['contentGenerator'] = mockGenerator;
|
|
382
|
-
await client.generateJson(contents, schema, abortSignal, customModel, customConfig);
|
|
383
|
-
expect(mockGenerateContentFn).toHaveBeenCalledWith({
|
|
384
|
-
model: customModel,
|
|
385
|
-
config: {
|
|
386
|
-
abortSignal,
|
|
387
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
388
|
-
temperature: 0.9,
|
|
389
|
-
topP: 1, // from default
|
|
390
|
-
topK: 20,
|
|
391
|
-
responseJsonSchema: schema,
|
|
392
|
-
responseMimeType: 'application/json',
|
|
393
|
-
},
|
|
394
|
-
contents,
|
|
395
|
-
}, 'test-session-id');
|
|
396
|
-
});
|
|
397
|
-
});
|
|
398
251
|
describe('addHistory', () => {
|
|
399
252
|
it('should call chat.addHistory with the provided content', async () => {
|
|
400
253
|
const mockChat = {
|
|
@@ -432,21 +285,15 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
432
285
|
});
|
|
433
286
|
});
|
|
434
287
|
describe('tryCompressChat', () => {
|
|
435
|
-
const mockCountTokens = vi.fn();
|
|
436
|
-
const mockSendMessage = vi.fn();
|
|
437
288
|
const mockGetHistory = vi.fn();
|
|
438
289
|
beforeEach(() => {
|
|
439
290
|
vi.mock('./tokenLimits', () => ({
|
|
440
291
|
tokenLimit: vi.fn(),
|
|
441
292
|
}));
|
|
442
|
-
client['contentGenerator'] = {
|
|
443
|
-
countTokens: mockCountTokens,
|
|
444
|
-
};
|
|
445
293
|
client['chat'] = {
|
|
446
294
|
getHistory: mockGetHistory,
|
|
447
295
|
addHistory: vi.fn(),
|
|
448
296
|
setHistory: vi.fn(),
|
|
449
|
-
sendMessage: mockSendMessage,
|
|
450
297
|
};
|
|
451
298
|
});
|
|
452
299
|
function setup({ chatHistory = [
|
|
@@ -456,28 +303,21 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
456
303
|
const mockChat = {
|
|
457
304
|
getHistory: vi.fn().mockReturnValue(chatHistory),
|
|
458
305
|
setHistory: vi.fn(),
|
|
459
|
-
sendMessage: vi.fn().mockResolvedValue({ text: 'Summary' }),
|
|
460
306
|
};
|
|
461
|
-
|
|
462
|
-
.fn()
|
|
307
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
463
308
|
.mockResolvedValueOnce({ totalTokens: 1000 })
|
|
464
309
|
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
465
|
-
const mockGenerator = {
|
|
466
|
-
countTokens: mockCountTokens,
|
|
467
|
-
};
|
|
468
310
|
client['chat'] = mockChat;
|
|
469
|
-
client['contentGenerator'] = mockGenerator;
|
|
470
311
|
client['startChat'] = vi.fn().mockResolvedValue({ ...mockChat });
|
|
471
|
-
return { client, mockChat
|
|
312
|
+
return { client, mockChat };
|
|
472
313
|
}
|
|
473
314
|
describe('when compression inflates the token count', () => {
|
|
474
|
-
it('uses the truncated history for compression');
|
|
475
315
|
it('allows compression to be forced/manual after a failure', async () => {
|
|
476
|
-
const { client
|
|
477
|
-
|
|
316
|
+
const { client } = setup();
|
|
317
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
478
318
|
totalTokens: 1000,
|
|
479
319
|
});
|
|
480
|
-
await client.tryCompressChat('prompt-id-4'); // Fails
|
|
320
|
+
await client.tryCompressChat('prompt-id-4', false); // Fails
|
|
481
321
|
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
482
322
|
expect(result).toEqual({
|
|
483
323
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
@@ -487,7 +327,10 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
487
327
|
});
|
|
488
328
|
it('yields the result even if the compression inflated the tokens', async () => {
|
|
489
329
|
const { client } = setup();
|
|
490
|
-
|
|
330
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
331
|
+
totalTokens: 1000,
|
|
332
|
+
});
|
|
333
|
+
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
491
334
|
expect(result).toEqual({
|
|
492
335
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
493
336
|
newTokenCount: 5000,
|
|
@@ -496,12 +339,12 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
496
339
|
});
|
|
497
340
|
it('does not manipulate the source chat', async () => {
|
|
498
341
|
const { client, mockChat } = setup();
|
|
499
|
-
await client.tryCompressChat('prompt-id-4',
|
|
342
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
500
343
|
expect(client['chat']).toBe(mockChat); // a new chat session was not created
|
|
501
344
|
});
|
|
502
345
|
it('restores the history back to the original', async () => {
|
|
503
346
|
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
504
|
-
|
|
347
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
505
348
|
totalTokens: 999,
|
|
506
349
|
});
|
|
507
350
|
const originalHistory = [
|
|
@@ -512,16 +355,16 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
512
355
|
const { client } = setup({
|
|
513
356
|
chatHistory: originalHistory,
|
|
514
357
|
});
|
|
515
|
-
const { compressionStatus } = await client.tryCompressChat('prompt-id-4');
|
|
358
|
+
const { compressionStatus } = await client.tryCompressChat('prompt-id-4', false);
|
|
516
359
|
expect(compressionStatus).toBe(CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT);
|
|
517
360
|
expect(client['chat']?.setHistory).toHaveBeenCalledWith(originalHistory);
|
|
518
361
|
});
|
|
519
362
|
it('will not attempt to compress context after a failure', async () => {
|
|
520
|
-
const { client
|
|
521
|
-
await client.tryCompressChat('prompt-id-4');
|
|
522
|
-
const result = await client.tryCompressChat('prompt-id-5');
|
|
363
|
+
const { client } = setup();
|
|
364
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
365
|
+
const result = await client.tryCompressChat('prompt-id-5', false);
|
|
523
366
|
// it counts tokens for {original, compressed} and then never again
|
|
524
|
-
expect(
|
|
367
|
+
expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
|
|
525
368
|
expect(result).toEqual({
|
|
526
369
|
compressionStatus: CompressionStatus.NOOP,
|
|
527
370
|
newTokenCount: 0,
|
|
@@ -529,37 +372,17 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
529
372
|
});
|
|
530
373
|
});
|
|
531
374
|
});
|
|
532
|
-
it('attempts to compress with a maxOutputTokens set to the original token count', async () => {
|
|
533
|
-
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
534
|
-
mockCountTokens.mockResolvedValue({
|
|
535
|
-
totalTokens: 999,
|
|
536
|
-
});
|
|
537
|
-
mockGetHistory.mockReturnValue([
|
|
538
|
-
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
539
|
-
]);
|
|
540
|
-
// Mock the summary response from the chat
|
|
541
|
-
mockSendMessage.mockResolvedValue({
|
|
542
|
-
role: 'model',
|
|
543
|
-
parts: [{ text: 'This is a summary.' }],
|
|
544
|
-
});
|
|
545
|
-
await client.tryCompressChat('prompt-id-2', true);
|
|
546
|
-
expect(mockSendMessage).toHaveBeenCalledWith(expect.objectContaining({
|
|
547
|
-
config: expect.objectContaining({
|
|
548
|
-
maxOutputTokens: 999,
|
|
549
|
-
}),
|
|
550
|
-
}), 'prompt-id-2');
|
|
551
|
-
});
|
|
552
375
|
it('should not trigger summarization if token count is below threshold', async () => {
|
|
553
376
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
554
377
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
555
378
|
mockGetHistory.mockReturnValue([
|
|
556
379
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
557
380
|
]);
|
|
558
|
-
|
|
381
|
+
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
559
382
|
totalTokens: MOCKED_TOKEN_LIMIT * 0.699, // TOKEN_THRESHOLD_FOR_SUMMARIZATION = 0.7
|
|
560
383
|
});
|
|
561
384
|
const initialChat = client.getChat();
|
|
562
|
-
const result = await client.tryCompressChat('prompt-id-2');
|
|
385
|
+
const result = await client.tryCompressChat('prompt-id-2', false);
|
|
563
386
|
const newChat = client.getChat();
|
|
564
387
|
expect(tokenLimit).toHaveBeenCalled();
|
|
565
388
|
expect(result).toEqual({
|
|
@@ -582,15 +405,21 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
582
405
|
]);
|
|
583
406
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
584
407
|
const newTokenCount = 100;
|
|
585
|
-
|
|
408
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
586
409
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
587
410
|
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
588
411
|
// Mock the summary response from the chat
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
412
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
413
|
+
candidates: [
|
|
414
|
+
{
|
|
415
|
+
content: {
|
|
416
|
+
role: 'model',
|
|
417
|
+
parts: [{ text: 'This is a summary.' }],
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
],
|
|
592
421
|
});
|
|
593
|
-
await client.tryCompressChat('prompt-id-3');
|
|
422
|
+
await client.tryCompressChat('prompt-id-3', false);
|
|
594
423
|
expect(ClearcutLogger.prototype.logChatCompressionEvent).toHaveBeenCalledWith(expect.objectContaining({
|
|
595
424
|
tokens_before: originalTokenCount,
|
|
596
425
|
tokens_after: newTokenCount,
|
|
@@ -608,19 +437,25 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
608
437
|
]);
|
|
609
438
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
610
439
|
const newTokenCount = 100;
|
|
611
|
-
|
|
440
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
612
441
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
613
442
|
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
614
443
|
// Mock the summary response from the chat
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
444
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
445
|
+
candidates: [
|
|
446
|
+
{
|
|
447
|
+
content: {
|
|
448
|
+
role: 'model',
|
|
449
|
+
parts: [{ text: 'This is a summary.' }],
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
],
|
|
618
453
|
});
|
|
619
454
|
const initialChat = client.getChat();
|
|
620
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
455
|
+
const result = await client.tryCompressChat('prompt-id-3', false);
|
|
621
456
|
const newChat = client.getChat();
|
|
622
457
|
expect(tokenLimit).toHaveBeenCalled();
|
|
623
|
-
expect(
|
|
458
|
+
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
624
459
|
// Assert that summarization happened and returned the correct stats
|
|
625
460
|
expect(result).toEqual({
|
|
626
461
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
@@ -653,19 +488,25 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
653
488
|
]);
|
|
654
489
|
const originalTokenCount = 1000 * 0.7;
|
|
655
490
|
const newTokenCount = 100;
|
|
656
|
-
|
|
491
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
657
492
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
658
493
|
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
659
494
|
// Mock the summary response from the chat
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
495
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
496
|
+
candidates: [
|
|
497
|
+
{
|
|
498
|
+
content: {
|
|
499
|
+
role: 'model',
|
|
500
|
+
parts: [{ text: 'This is a summary.' }],
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
],
|
|
663
504
|
});
|
|
664
505
|
const initialChat = client.getChat();
|
|
665
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
506
|
+
const result = await client.tryCompressChat('prompt-id-3', false);
|
|
666
507
|
const newChat = client.getChat();
|
|
667
508
|
expect(tokenLimit).toHaveBeenCalled();
|
|
668
|
-
expect(
|
|
509
|
+
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
669
510
|
// Assert that summarization happened and returned the correct stats
|
|
670
511
|
expect(result).toEqual({
|
|
671
512
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
@@ -687,18 +528,24 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
687
528
|
]);
|
|
688
529
|
const originalTokenCount = 10; // Well below threshold
|
|
689
530
|
const newTokenCount = 5;
|
|
690
|
-
|
|
531
|
+
vi.mocked(mockContentGenerator.countTokens)
|
|
691
532
|
.mockResolvedValueOnce({ totalTokens: originalTokenCount })
|
|
692
533
|
.mockResolvedValueOnce({ totalTokens: newTokenCount });
|
|
693
534
|
// Mock the summary response from the chat
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
535
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
536
|
+
candidates: [
|
|
537
|
+
{
|
|
538
|
+
content: {
|
|
539
|
+
role: 'model',
|
|
540
|
+
parts: [{ text: 'This is a summary.' }],
|
|
541
|
+
},
|
|
542
|
+
},
|
|
543
|
+
],
|
|
697
544
|
});
|
|
698
545
|
const initialChat = client.getChat();
|
|
699
|
-
const result = await client.tryCompressChat('prompt-id-1',
|
|
546
|
+
const result = await client.tryCompressChat('prompt-id-1', false); // force = true
|
|
700
547
|
const newChat = client.getChat();
|
|
701
|
-
expect(
|
|
548
|
+
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
702
549
|
expect(result).toEqual({
|
|
703
550
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
704
551
|
originalTokenCount,
|
|
@@ -707,68 +554,13 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
707
554
|
// Assert that the chat was reset
|
|
708
555
|
expect(newChat).not.toBe(initialChat);
|
|
709
556
|
});
|
|
710
|
-
it('should use current model from config for token counting after sendMessage', async () => {
|
|
711
|
-
const initialModel = client['config'].getModel();
|
|
712
|
-
const mockCountTokens = vi
|
|
713
|
-
.fn()
|
|
714
|
-
.mockResolvedValueOnce({ totalTokens: 100000 })
|
|
715
|
-
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
716
|
-
const mockSendMessage = vi.fn().mockResolvedValue({ text: 'Summary' });
|
|
717
|
-
const mockChatHistory = [
|
|
718
|
-
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
719
|
-
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
720
|
-
];
|
|
721
|
-
const mockChat = {
|
|
722
|
-
getHistory: vi.fn().mockReturnValue(mockChatHistory),
|
|
723
|
-
setHistory: vi.fn(),
|
|
724
|
-
sendMessage: mockSendMessage,
|
|
725
|
-
};
|
|
726
|
-
const mockGenerator = {
|
|
727
|
-
countTokens: mockCountTokens,
|
|
728
|
-
};
|
|
729
|
-
// mock the model has been changed between calls of `countTokens`
|
|
730
|
-
const firstCurrentModel = initialModel + '-changed-1';
|
|
731
|
-
const secondCurrentModel = initialModel + '-changed-2';
|
|
732
|
-
vi.spyOn(client['config'], 'getModel')
|
|
733
|
-
.mockReturnValueOnce(firstCurrentModel)
|
|
734
|
-
.mockReturnValueOnce(secondCurrentModel);
|
|
735
|
-
client['chat'] = mockChat;
|
|
736
|
-
client['contentGenerator'] = mockGenerator;
|
|
737
|
-
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
|
738
|
-
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
739
|
-
expect(mockCountTokens).toHaveBeenCalledTimes(2);
|
|
740
|
-
expect(mockCountTokens).toHaveBeenNthCalledWith(1, {
|
|
741
|
-
model: firstCurrentModel,
|
|
742
|
-
contents: mockChatHistory,
|
|
743
|
-
});
|
|
744
|
-
expect(mockCountTokens).toHaveBeenNthCalledWith(2, {
|
|
745
|
-
model: secondCurrentModel,
|
|
746
|
-
contents: expect.any(Array),
|
|
747
|
-
});
|
|
748
|
-
expect(result).toEqual({
|
|
749
|
-
compressionStatus: CompressionStatus.COMPRESSED,
|
|
750
|
-
originalTokenCount: 100000,
|
|
751
|
-
newTokenCount: 5000,
|
|
752
|
-
});
|
|
753
|
-
});
|
|
754
557
|
});
|
|
755
558
|
describe('sendMessageStream', () => {
|
|
756
559
|
it('emits a compression event when the context was automatically compressed', async () => {
|
|
757
560
|
// Arrange
|
|
758
|
-
|
|
561
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
759
562
|
yield { type: 'content', value: 'Hello' };
|
|
760
|
-
})();
|
|
761
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
762
|
-
const mockChat = {
|
|
763
|
-
addHistory: vi.fn(),
|
|
764
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
765
|
-
};
|
|
766
|
-
client['chat'] = mockChat;
|
|
767
|
-
const mockGenerator = {
|
|
768
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
769
|
-
generateContent: mockGenerateContentFn,
|
|
770
|
-
};
|
|
771
|
-
client['contentGenerator'] = mockGenerator;
|
|
563
|
+
})());
|
|
772
564
|
const compressionInfo = {
|
|
773
565
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
774
566
|
originalTokenCount: 1000,
|
|
@@ -798,16 +590,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
798
590
|
yield { type: 'content', value: 'Hello' };
|
|
799
591
|
})();
|
|
800
592
|
mockTurnRunFn.mockReturnValue(mockStream);
|
|
801
|
-
const mockChat = {
|
|
802
|
-
addHistory: vi.fn(),
|
|
803
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
804
|
-
};
|
|
805
|
-
client['chat'] = mockChat;
|
|
806
|
-
const mockGenerator = {
|
|
807
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
808
|
-
generateContent: mockGenerateContentFn,
|
|
809
|
-
};
|
|
810
|
-
client['contentGenerator'] = mockGenerator;
|
|
811
593
|
const compressionInfo = {
|
|
812
594
|
compressionStatus,
|
|
813
595
|
originalTokenCount: 1000,
|
|
@@ -825,7 +607,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
825
607
|
});
|
|
826
608
|
it('should include editor context when ideMode is enabled', async () => {
|
|
827
609
|
// Arrange
|
|
828
|
-
vi.mocked(
|
|
610
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
829
611
|
workspaceState: {
|
|
830
612
|
openFiles: [
|
|
831
613
|
{
|
|
@@ -846,21 +628,20 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
846
628
|
],
|
|
847
629
|
},
|
|
848
630
|
});
|
|
849
|
-
vi.
|
|
850
|
-
|
|
631
|
+
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
|
632
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
633
|
+
originalTokenCount: 0,
|
|
634
|
+
newTokenCount: 0,
|
|
635
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
636
|
+
});
|
|
637
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
851
638
|
yield { type: 'content', value: 'Hello' };
|
|
852
|
-
})();
|
|
853
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
639
|
+
})());
|
|
854
640
|
const mockChat = {
|
|
855
641
|
addHistory: vi.fn(),
|
|
856
642
|
getHistory: vi.fn().mockReturnValue([]),
|
|
857
643
|
};
|
|
858
644
|
client['chat'] = mockChat;
|
|
859
|
-
const mockGenerator = {
|
|
860
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
861
|
-
generateContent: mockGenerateContentFn,
|
|
862
|
-
};
|
|
863
|
-
client['contentGenerator'] = mockGenerator;
|
|
864
645
|
const initialRequest = [{ text: 'Hi' }];
|
|
865
646
|
// Act
|
|
866
647
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -868,7 +649,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
868
649
|
// consume stream
|
|
869
650
|
}
|
|
870
651
|
// Assert
|
|
871
|
-
expect(
|
|
652
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
872
653
|
const expectedContext = `
|
|
873
654
|
Here is the user's editor context as a JSON object. This is for your information only.
|
|
874
655
|
\`\`\`json
|
|
@@ -893,7 +674,7 @@ ${JSON.stringify({
|
|
|
893
674
|
});
|
|
894
675
|
it('should not add context if ideMode is enabled but no open files', async () => {
|
|
895
676
|
// Arrange
|
|
896
|
-
vi.mocked(
|
|
677
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
897
678
|
workspaceState: {
|
|
898
679
|
openFiles: [],
|
|
899
680
|
},
|
|
@@ -908,11 +689,6 @@ ${JSON.stringify({
|
|
|
908
689
|
getHistory: vi.fn().mockReturnValue([]),
|
|
909
690
|
};
|
|
910
691
|
client['chat'] = mockChat;
|
|
911
|
-
const mockGenerator = {
|
|
912
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
913
|
-
generateContent: mockGenerateContentFn,
|
|
914
|
-
};
|
|
915
|
-
client['contentGenerator'] = mockGenerator;
|
|
916
692
|
const initialRequest = [{ text: 'Hi' }];
|
|
917
693
|
// Act
|
|
918
694
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -920,12 +696,16 @@ ${JSON.stringify({
|
|
|
920
696
|
// consume stream
|
|
921
697
|
}
|
|
922
698
|
// Assert
|
|
923
|
-
expect(
|
|
924
|
-
|
|
699
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
700
|
+
// The `turn.run` method is now called with the model name as the first
|
|
701
|
+
// argument. We use `expect.any(String)` because this test is
|
|
702
|
+
// concerned with the IDE context logic, not the model routing,
|
|
703
|
+
// which is tested in its own dedicated suite.
|
|
704
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith(expect.any(String), initialRequest, expect.any(Object));
|
|
925
705
|
});
|
|
926
706
|
it('should add context if ideMode is enabled and there is one active file', async () => {
|
|
927
707
|
// Arrange
|
|
928
|
-
vi.mocked(
|
|
708
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
929
709
|
workspaceState: {
|
|
930
710
|
openFiles: [
|
|
931
711
|
{
|
|
@@ -939,6 +719,11 @@ ${JSON.stringify({
|
|
|
939
719
|
},
|
|
940
720
|
});
|
|
941
721
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
722
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
723
|
+
originalTokenCount: 0,
|
|
724
|
+
newTokenCount: 0,
|
|
725
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
726
|
+
});
|
|
942
727
|
const mockStream = (async function* () {
|
|
943
728
|
yield { type: 'content', value: 'Hello' };
|
|
944
729
|
})();
|
|
@@ -948,11 +733,6 @@ ${JSON.stringify({
|
|
|
948
733
|
getHistory: vi.fn().mockReturnValue([]),
|
|
949
734
|
};
|
|
950
735
|
client['chat'] = mockChat;
|
|
951
|
-
const mockGenerator = {
|
|
952
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
953
|
-
generateContent: mockGenerateContentFn,
|
|
954
|
-
};
|
|
955
|
-
client['contentGenerator'] = mockGenerator;
|
|
956
736
|
const initialRequest = [{ text: 'Hi' }];
|
|
957
737
|
// Act
|
|
958
738
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -960,7 +740,7 @@ ${JSON.stringify({
|
|
|
960
740
|
// consume stream
|
|
961
741
|
}
|
|
962
742
|
// Assert
|
|
963
|
-
expect(
|
|
743
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
964
744
|
const expectedContext = `
|
|
965
745
|
Here is the user's editor context as a JSON object. This is for your information only.
|
|
966
746
|
\`\`\`json
|
|
@@ -984,7 +764,7 @@ ${JSON.stringify({
|
|
|
984
764
|
});
|
|
985
765
|
it('should add context if ideMode is enabled and there are open files but no active file', async () => {
|
|
986
766
|
// Arrange
|
|
987
|
-
vi.mocked(
|
|
767
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
988
768
|
workspaceState: {
|
|
989
769
|
openFiles: [
|
|
990
770
|
{
|
|
@@ -999,6 +779,11 @@ ${JSON.stringify({
|
|
|
999
779
|
},
|
|
1000
780
|
});
|
|
1001
781
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
782
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
783
|
+
originalTokenCount: 0,
|
|
784
|
+
newTokenCount: 0,
|
|
785
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
786
|
+
});
|
|
1002
787
|
const mockStream = (async function* () {
|
|
1003
788
|
yield { type: 'content', value: 'Hello' };
|
|
1004
789
|
})();
|
|
@@ -1008,11 +793,6 @@ ${JSON.stringify({
|
|
|
1008
793
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1009
794
|
};
|
|
1010
795
|
client['chat'] = mockChat;
|
|
1011
|
-
const mockGenerator = {
|
|
1012
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1013
|
-
generateContent: mockGenerateContentFn,
|
|
1014
|
-
};
|
|
1015
|
-
client['contentGenerator'] = mockGenerator;
|
|
1016
796
|
const initialRequest = [{ text: 'Hi' }];
|
|
1017
797
|
// Act
|
|
1018
798
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -1020,7 +800,7 @@ ${JSON.stringify({
|
|
|
1020
800
|
// consume stream
|
|
1021
801
|
}
|
|
1022
802
|
// Assert
|
|
1023
|
-
expect(
|
|
803
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
1024
804
|
const expectedContext = `
|
|
1025
805
|
Here is the user's editor context as a JSON object. This is for your information only.
|
|
1026
806
|
\`\`\`json
|
|
@@ -1046,11 +826,6 @@ ${JSON.stringify({
|
|
|
1046
826
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1047
827
|
};
|
|
1048
828
|
client['chat'] = mockChat;
|
|
1049
|
-
const mockGenerator = {
|
|
1050
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1051
|
-
generateContent: mockGenerateContentFn,
|
|
1052
|
-
};
|
|
1053
|
-
client['contentGenerator'] = mockGenerator;
|
|
1054
829
|
// Act
|
|
1055
830
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
|
|
1056
831
|
// Consume the stream manually to get the final return value.
|
|
@@ -1083,11 +858,6 @@ ${JSON.stringify({
|
|
|
1083
858
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1084
859
|
};
|
|
1085
860
|
client['chat'] = mockChat;
|
|
1086
|
-
const mockGenerator = {
|
|
1087
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1088
|
-
generateContent: mockGenerateContentFn,
|
|
1089
|
-
};
|
|
1090
|
-
client['contentGenerator'] = mockGenerator;
|
|
1091
861
|
// Use a signal that never gets aborted
|
|
1092
862
|
const abortController = new AbortController();
|
|
1093
863
|
const signal = abortController.signal;
|
|
@@ -1150,11 +920,6 @@ ${JSON.stringify({
|
|
|
1150
920
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1151
921
|
};
|
|
1152
922
|
client['chat'] = mockChat;
|
|
1153
|
-
const mockGenerator = {
|
|
1154
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1155
|
-
generateContent: mockGenerateContentFn,
|
|
1156
|
-
};
|
|
1157
|
-
client['contentGenerator'] = mockGenerator;
|
|
1158
923
|
// Act & Assert
|
|
1159
924
|
// Run up to the limit
|
|
1160
925
|
for (let i = 0; i < MAX_SESSION_TURNS; i++) {
|
|
@@ -1193,11 +958,6 @@ ${JSON.stringify({
|
|
|
1193
958
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1194
959
|
};
|
|
1195
960
|
client['chat'] = mockChat;
|
|
1196
|
-
const mockGenerator = {
|
|
1197
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1198
|
-
generateContent: mockGenerateContentFn,
|
|
1199
|
-
};
|
|
1200
|
-
client['contentGenerator'] = mockGenerator;
|
|
1201
961
|
// Use a signal that never gets aborted
|
|
1202
962
|
const abortController = new AbortController();
|
|
1203
963
|
const signal = abortController.signal;
|
|
@@ -1236,6 +996,91 @@ ${JSON.stringify({
|
|
|
1236
996
|
console.log(`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
|
|
1237
997
|
`${eventCount} events generated (properly bounded by MAX_TURNS)`);
|
|
1238
998
|
});
|
|
999
|
+
describe('Model Routing', () => {
|
|
1000
|
+
let mockRouterService;
|
|
1001
|
+
beforeEach(() => {
|
|
1002
|
+
mockRouterService = {
|
|
1003
|
+
route: vi
|
|
1004
|
+
.fn()
|
|
1005
|
+
.mockResolvedValue({ model: 'routed-model', reason: 'test' }),
|
|
1006
|
+
};
|
|
1007
|
+
vi.mocked(mockConfig.getModelRouterService).mockReturnValue(mockRouterService);
|
|
1008
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
1009
|
+
yield { type: 'content', value: 'Hello' };
|
|
1010
|
+
})());
|
|
1011
|
+
});
|
|
1012
|
+
it('should use the model router service to select a model on the first turn', async () => {
|
|
1013
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1014
|
+
await fromAsync(stream); // consume stream
|
|
1015
|
+
expect(mockConfig.getModelRouterService).toHaveBeenCalled();
|
|
1016
|
+
expect(mockRouterService.route).toHaveBeenCalled();
|
|
1017
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', // The model from the router
|
|
1018
|
+
[{ text: 'Hi' }], expect.any(Object));
|
|
1019
|
+
});
|
|
1020
|
+
it('should use the same model for subsequent turns in the same prompt (stickiness)', async () => {
|
|
1021
|
+
// First turn
|
|
1022
|
+
let stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1023
|
+
await fromAsync(stream);
|
|
1024
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
|
1025
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', [{ text: 'Hi' }], expect.any(Object));
|
|
1026
|
+
// Second turn
|
|
1027
|
+
stream = client.sendMessageStream([{ text: 'Continue' }], new AbortController().signal, 'prompt-1');
|
|
1028
|
+
await fromAsync(stream);
|
|
1029
|
+
// Router should not be called again
|
|
1030
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
|
1031
|
+
// Should stick to the first model
|
|
1032
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', [{ text: 'Continue' }], expect.any(Object));
|
|
1033
|
+
});
|
|
1034
|
+
it('should reset the sticky model and re-route when the prompt_id changes', async () => {
|
|
1035
|
+
// First prompt
|
|
1036
|
+
let stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1037
|
+
await fromAsync(stream);
|
|
1038
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
|
1039
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', [{ text: 'Hi' }], expect.any(Object));
|
|
1040
|
+
// New prompt
|
|
1041
|
+
mockRouterService.route.mockResolvedValue({
|
|
1042
|
+
model: 'new-routed-model',
|
|
1043
|
+
reason: 'test',
|
|
1044
|
+
});
|
|
1045
|
+
stream = client.sendMessageStream([{ text: 'A new topic' }], new AbortController().signal, 'prompt-2');
|
|
1046
|
+
await fromAsync(stream);
|
|
1047
|
+
// Router should be called again for the new prompt
|
|
1048
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(2);
|
|
1049
|
+
// Should use the newly routed model
|
|
1050
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('new-routed-model', [{ text: 'A new topic' }], expect.any(Object));
|
|
1051
|
+
});
|
|
1052
|
+
it('should use the fallback model and bypass routing when in fallback mode', async () => {
|
|
1053
|
+
vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(true);
|
|
1054
|
+
mockRouterService.route.mockResolvedValue({
|
|
1055
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1056
|
+
reason: 'fallback',
|
|
1057
|
+
});
|
|
1058
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1059
|
+
await fromAsync(stream);
|
|
1060
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith(DEFAULT_GEMINI_FLASH_MODEL, [{ text: 'Hi' }], expect.any(Object));
|
|
1061
|
+
});
|
|
1062
|
+
it('should stick to the fallback model for the entire sequence even if fallback mode ends', async () => {
|
|
1063
|
+
// Start the sequence in fallback mode
|
|
1064
|
+
vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(true);
|
|
1065
|
+
mockRouterService.route.mockResolvedValue({
|
|
1066
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1067
|
+
reason: 'fallback',
|
|
1068
|
+
});
|
|
1069
|
+
let stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-fallback-stickiness');
|
|
1070
|
+
await fromAsync(stream);
|
|
1071
|
+
// First call should use fallback model
|
|
1072
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith(DEFAULT_GEMINI_FLASH_MODEL, [{ text: 'Hi' }], expect.any(Object));
|
|
1073
|
+
// End fallback mode
|
|
1074
|
+
vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(false);
|
|
1075
|
+
// Second call in the same sequence
|
|
1076
|
+
stream = client.sendMessageStream([{ text: 'Continue' }], new AbortController().signal, 'prompt-fallback-stickiness');
|
|
1077
|
+
await fromAsync(stream);
|
|
1078
|
+
// Router should still not be called, and it should stick to the fallback model
|
|
1079
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(2); // Ensure it was called again
|
|
1080
|
+
expect(mockTurnRunFn).toHaveBeenLastCalledWith(DEFAULT_GEMINI_FLASH_MODEL, // Still the fallback model
|
|
1081
|
+
[{ text: 'Continue' }], expect.any(Object));
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1239
1084
|
describe('Editor context delta', () => {
|
|
1240
1085
|
const mockStream = (async function* () {
|
|
1241
1086
|
yield { type: 'content', value: 'Hello' };
|
|
@@ -1252,7 +1097,6 @@ ${JSON.stringify({
|
|
|
1252
1097
|
const mockChat = {
|
|
1253
1098
|
addHistory: vi.fn(),
|
|
1254
1099
|
setHistory: vi.fn(),
|
|
1255
|
-
sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
|
|
1256
1100
|
// Assume history is not empty for delta checks
|
|
1257
1101
|
getHistory: vi
|
|
1258
1102
|
.fn()
|
|
@@ -1261,11 +1105,6 @@ ${JSON.stringify({
|
|
|
1261
1105
|
]),
|
|
1262
1106
|
};
|
|
1263
1107
|
client['chat'] = mockChat;
|
|
1264
|
-
const mockGenerator = {
|
|
1265
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1266
|
-
generateContent: mockGenerateContentFn,
|
|
1267
|
-
};
|
|
1268
|
-
client['contentGenerator'] = mockGenerator;
|
|
1269
1108
|
});
|
|
1270
1109
|
const testCases = [
|
|
1271
1110
|
{
|
|
@@ -1381,7 +1220,7 @@ ${JSON.stringify({
|
|
|
1381
1220
|
},
|
|
1382
1221
|
};
|
|
1383
1222
|
// Setup current context
|
|
1384
|
-
vi.mocked(
|
|
1223
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
1385
1224
|
workspaceState: {
|
|
1386
1225
|
openFiles: [
|
|
1387
1226
|
{ ...currentActiveFile, isActive: true, timestamp: Date.now() },
|
|
@@ -1427,7 +1266,7 @@ ${JSON.stringify({
|
|
|
1427
1266
|
},
|
|
1428
1267
|
};
|
|
1429
1268
|
// Setup current context (same as previous)
|
|
1430
|
-
vi.mocked(
|
|
1269
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
1431
1270
|
workspaceState: {
|
|
1432
1271
|
openFiles: [
|
|
1433
1272
|
{ ...activeFile, isActive: true, timestamp: Date.now() },
|
|
@@ -1472,15 +1311,10 @@ ${JSON.stringify({
|
|
|
1472
1311
|
addHistory: vi.fn(),
|
|
1473
1312
|
getHistory: vi.fn().mockReturnValue([]), // Default empty history
|
|
1474
1313
|
setHistory: vi.fn(),
|
|
1475
|
-
sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
|
|
1476
1314
|
};
|
|
1477
1315
|
client['chat'] = mockChat;
|
|
1478
|
-
const mockGenerator = {
|
|
1479
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1480
|
-
};
|
|
1481
|
-
client['contentGenerator'] = mockGenerator;
|
|
1482
1316
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
1483
|
-
vi.mocked(
|
|
1317
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
1484
1318
|
workspaceState: {
|
|
1485
1319
|
openFiles: [{ path: '/path/to/file.ts', timestamp: Date.now() }],
|
|
1486
1320
|
},
|
|
@@ -1556,7 +1390,7 @@ ${JSON.stringify({
|
|
|
1556
1390
|
openFiles: [{ path: '/path/to/fileA.ts', timestamp: Date.now() }],
|
|
1557
1391
|
},
|
|
1558
1392
|
};
|
|
1559
|
-
vi.mocked(
|
|
1393
|
+
vi.mocked(ideContextStore.get).mockReturnValue(initialIdeContext);
|
|
1560
1394
|
// Act: Send the tool response
|
|
1561
1395
|
let stream = client.sendMessageStream([
|
|
1562
1396
|
{
|
|
@@ -1602,7 +1436,7 @@ ${JSON.stringify({
|
|
|
1602
1436
|
openFiles: [{ path: '/path/to/fileB.ts', timestamp: Date.now() }],
|
|
1603
1437
|
},
|
|
1604
1438
|
};
|
|
1605
|
-
vi.mocked(
|
|
1439
|
+
vi.mocked(ideContextStore.get).mockReturnValue(newIdeContext);
|
|
1606
1440
|
// Act: Send a new, regular user message
|
|
1607
1441
|
stream = client.sendMessageStream([{ text: 'Thanks!' }], new AbortController().signal, 'prompt-id-final');
|
|
1608
1442
|
for await (const _ of stream) {
|
|
@@ -1632,7 +1466,7 @@ ${JSON.stringify({
|
|
|
1632
1466
|
],
|
|
1633
1467
|
},
|
|
1634
1468
|
};
|
|
1635
|
-
vi.mocked(
|
|
1469
|
+
vi.mocked(ideContextStore.get).mockReturnValue(contextA);
|
|
1636
1470
|
// Act: Send a regular message to establish the initial context
|
|
1637
1471
|
let stream = client.sendMessageStream([{ text: 'Initial message' }], new AbortController().signal, 'prompt-id-initial');
|
|
1638
1472
|
for await (const _ of stream) {
|
|
@@ -1665,7 +1499,7 @@ ${JSON.stringify({
|
|
|
1665
1499
|
],
|
|
1666
1500
|
},
|
|
1667
1501
|
};
|
|
1668
|
-
vi.mocked(
|
|
1502
|
+
vi.mocked(ideContextStore.get).mockReturnValue(contextB);
|
|
1669
1503
|
// Act: Send the tool response
|
|
1670
1504
|
stream = client.sendMessageStream([
|
|
1671
1505
|
{
|
|
@@ -1710,7 +1544,7 @@ ${JSON.stringify({
|
|
|
1710
1544
|
],
|
|
1711
1545
|
},
|
|
1712
1546
|
};
|
|
1713
|
-
vi.mocked(
|
|
1547
|
+
vi.mocked(ideContextStore.get).mockReturnValue(contextC);
|
|
1714
1548
|
// Act: Send a new, regular user message
|
|
1715
1549
|
stream = client.sendMessageStream([{ text: 'Thanks!' }], new AbortController().signal, 'prompt-id-final');
|
|
1716
1550
|
for await (const _ of stream) {
|
|
@@ -1742,11 +1576,6 @@ ${JSON.stringify({
|
|
|
1742
1576
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1743
1577
|
};
|
|
1744
1578
|
client['chat'] = mockChat;
|
|
1745
|
-
const mockGenerator = {
|
|
1746
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1747
|
-
generateContent: mockGenerateContentFn,
|
|
1748
|
-
};
|
|
1749
|
-
client['contentGenerator'] = mockGenerator;
|
|
1750
1579
|
// Act
|
|
1751
1580
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
|
|
1752
1581
|
for await (const _ of stream) {
|
|
@@ -1772,11 +1601,6 @@ ${JSON.stringify({
|
|
|
1772
1601
|
getHistory: vi.fn().mockReturnValue([]),
|
|
1773
1602
|
};
|
|
1774
1603
|
client['chat'] = mockChat;
|
|
1775
|
-
const mockGenerator = {
|
|
1776
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1777
|
-
generateContent: mockGenerateContentFn,
|
|
1778
|
-
};
|
|
1779
|
-
client['contentGenerator'] = mockGenerator;
|
|
1780
1604
|
// Act
|
|
1781
1605
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
|
|
1782
1606
|
for await (const _ of stream) {
|
|
@@ -1785,20 +1609,43 @@ ${JSON.stringify({
|
|
|
1785
1609
|
// Assert
|
|
1786
1610
|
expect(mockCheckNextSpeaker).not.toHaveBeenCalled();
|
|
1787
1611
|
});
|
|
1612
|
+
it('should abort linked signal when loop is detected', async () => {
|
|
1613
|
+
// Arrange
|
|
1614
|
+
vi.spyOn(client['loopDetector'], 'turnStarted').mockResolvedValue(false);
|
|
1615
|
+
vi.spyOn(client['loopDetector'], 'addAndCheck')
|
|
1616
|
+
.mockReturnValueOnce(false)
|
|
1617
|
+
.mockReturnValueOnce(true);
|
|
1618
|
+
let capturedSignal;
|
|
1619
|
+
mockTurnRunFn.mockImplementation((model, request, signal) => {
|
|
1620
|
+
capturedSignal = signal;
|
|
1621
|
+
return (async function* () {
|
|
1622
|
+
yield { type: 'content', value: 'First event' };
|
|
1623
|
+
yield { type: 'content', value: 'Second event' };
|
|
1624
|
+
})();
|
|
1625
|
+
});
|
|
1626
|
+
const mockChat = {
|
|
1627
|
+
addHistory: vi.fn(),
|
|
1628
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1629
|
+
};
|
|
1630
|
+
client['chat'] = mockChat;
|
|
1631
|
+
// Act
|
|
1632
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-loop');
|
|
1633
|
+
const events = [];
|
|
1634
|
+
for await (const event of stream) {
|
|
1635
|
+
events.push(event);
|
|
1636
|
+
}
|
|
1637
|
+
// Assert
|
|
1638
|
+
expect(events).toContainEqual({ type: GeminiEventType.LoopDetected });
|
|
1639
|
+
expect(capturedSignal.aborted).toBe(true);
|
|
1640
|
+
});
|
|
1788
1641
|
});
|
|
1789
1642
|
describe('generateContent', () => {
|
|
1790
1643
|
it('should call generateContent with the correct parameters', async () => {
|
|
1791
1644
|
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
1792
1645
|
const generationConfig = { temperature: 0.5 };
|
|
1793
1646
|
const abortSignal = new AbortController().signal;
|
|
1794
|
-
// Mock countTokens
|
|
1795
|
-
const mockGenerator = {
|
|
1796
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
1797
|
-
generateContent: mockGenerateContentFn,
|
|
1798
|
-
};
|
|
1799
|
-
client['contentGenerator'] = mockGenerator;
|
|
1800
1647
|
await client.generateContent(contents, generationConfig, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
1801
|
-
expect(
|
|
1648
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
1802
1649
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1803
1650
|
config: {
|
|
1804
1651
|
abortSignal,
|
|
@@ -1814,98 +1661,29 @@ ${JSON.stringify({
|
|
|
1814
1661
|
const contents = [{ role: 'user', parts: [{ text: 'test' }] }];
|
|
1815
1662
|
const currentModel = initialModel + '-changed';
|
|
1816
1663
|
vi.spyOn(client['config'], 'getModel').mockReturnValueOnce(currentModel);
|
|
1817
|
-
const mockGenerator = {
|
|
1818
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
1819
|
-
generateContent: mockGenerateContentFn,
|
|
1820
|
-
};
|
|
1821
|
-
client['contentGenerator'] = mockGenerator;
|
|
1822
1664
|
await client.generateContent(contents, {}, new AbortController().signal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
1823
|
-
expect(
|
|
1665
|
+
expect(mockContentGenerator.generateContent).not.toHaveBeenCalledWith({
|
|
1824
1666
|
model: initialModel,
|
|
1825
1667
|
config: expect.any(Object),
|
|
1826
1668
|
contents,
|
|
1827
1669
|
});
|
|
1828
|
-
expect(
|
|
1670
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
1829
1671
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1830
1672
|
config: expect.any(Object),
|
|
1831
1673
|
contents,
|
|
1832
1674
|
}, 'test-session-id');
|
|
1833
1675
|
});
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
const
|
|
1838
|
-
const
|
|
1839
|
-
//
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
client['config'].setModel = vi.fn();
|
|
1846
|
-
const result = await client['handleFlashFallback'](AuthType.LOGIN_WITH_GOOGLE);
|
|
1847
|
-
expect(result).toBe(fallbackModel);
|
|
1848
|
-
expect(mockFallbackHandler).toHaveBeenCalledWith(currentModel, fallbackModel, undefined);
|
|
1849
|
-
});
|
|
1850
|
-
});
|
|
1851
|
-
describe('setHistory', () => {
|
|
1852
|
-
it('should strip thought signatures when stripThoughts is true', () => {
|
|
1853
|
-
const mockChat = {
|
|
1854
|
-
setHistory: vi.fn(),
|
|
1855
|
-
};
|
|
1856
|
-
client['chat'] = mockChat;
|
|
1857
|
-
const historyWithThoughts = [
|
|
1858
|
-
{
|
|
1859
|
-
role: 'user',
|
|
1860
|
-
parts: [{ text: 'hello' }],
|
|
1861
|
-
},
|
|
1862
|
-
{
|
|
1863
|
-
role: 'model',
|
|
1864
|
-
parts: [
|
|
1865
|
-
{ text: 'thinking...', thoughtSignature: 'thought-123' },
|
|
1866
|
-
{
|
|
1867
|
-
functionCall: { name: 'test', args: {} },
|
|
1868
|
-
thoughtSignature: 'thought-456',
|
|
1869
|
-
},
|
|
1870
|
-
],
|
|
1871
|
-
},
|
|
1872
|
-
];
|
|
1873
|
-
client.setHistory(historyWithThoughts, { stripThoughts: true });
|
|
1874
|
-
const expectedHistory = [
|
|
1875
|
-
{
|
|
1876
|
-
role: 'user',
|
|
1877
|
-
parts: [{ text: 'hello' }],
|
|
1878
|
-
},
|
|
1879
|
-
{
|
|
1880
|
-
role: 'model',
|
|
1881
|
-
parts: [
|
|
1882
|
-
{ text: 'thinking...' },
|
|
1883
|
-
{ functionCall: { name: 'test', args: {} } },
|
|
1884
|
-
],
|
|
1885
|
-
},
|
|
1886
|
-
];
|
|
1887
|
-
expect(mockChat.setHistory).toHaveBeenCalledWith(expectedHistory);
|
|
1888
|
-
});
|
|
1889
|
-
it('should not strip thought signatures when stripThoughts is false', () => {
|
|
1890
|
-
const mockChat = {
|
|
1891
|
-
setHistory: vi.fn(),
|
|
1892
|
-
};
|
|
1893
|
-
client['chat'] = mockChat;
|
|
1894
|
-
const historyWithThoughts = [
|
|
1895
|
-
{
|
|
1896
|
-
role: 'user',
|
|
1897
|
-
parts: [{ text: 'hello' }],
|
|
1898
|
-
},
|
|
1899
|
-
{
|
|
1900
|
-
role: 'model',
|
|
1901
|
-
parts: [
|
|
1902
|
-
{ text: 'thinking...', thoughtSignature: 'thought-123' },
|
|
1903
|
-
{ text: 'ok', thoughtSignature: 'thought-456' },
|
|
1904
|
-
],
|
|
1905
|
-
},
|
|
1906
|
-
];
|
|
1907
|
-
client.setHistory(historyWithThoughts, { stripThoughts: false });
|
|
1908
|
-
expect(mockChat.setHistory).toHaveBeenCalledWith(historyWithThoughts);
|
|
1676
|
+
it('should use the Flash model when fallback mode is active', async () => {
|
|
1677
|
+
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
1678
|
+
const generationConfig = { temperature: 0.5 };
|
|
1679
|
+
const abortSignal = new AbortController().signal;
|
|
1680
|
+
const requestedModel = 'gemini-2.5-pro'; // A non-flash model
|
|
1681
|
+
// Mock config to be in fallback mode
|
|
1682
|
+
vi.spyOn(client['config'], 'isInFallbackMode').mockReturnValue(true);
|
|
1683
|
+
await client.generateContent(contents, generationConfig, abortSignal, requestedModel);
|
|
1684
|
+
expect(mockGenerateContentFn).toHaveBeenCalledWith(expect.objectContaining({
|
|
1685
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1686
|
+
}), 'test-session-id');
|
|
1909
1687
|
});
|
|
1910
1688
|
});
|
|
1911
1689
|
});
|