@google/gemini-cli-core 0.0.8999999 → 0.0.77777773
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/README.md +105 -62
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/src/agents/codebase-investigator.d.ts +46 -0
- package/dist/src/agents/codebase-investigator.js +136 -0
- package/dist/src/agents/codebase-investigator.js.map +1 -0
- package/dist/src/agents/executor.d.ts +92 -0
- package/dist/src/agents/executor.js +579 -0
- package/dist/src/agents/executor.js.map +1 -0
- package/dist/src/agents/executor.test.d.ts +6 -0
- package/dist/src/agents/executor.test.js +595 -0
- package/dist/src/agents/executor.test.js.map +1 -0
- package/dist/src/agents/invocation.d.ts +46 -0
- package/dist/src/agents/invocation.js +102 -0
- package/dist/src/agents/invocation.js.map +1 -0
- package/dist/src/agents/invocation.test.d.ts +6 -0
- package/dist/src/agents/invocation.test.js +215 -0
- package/dist/src/agents/invocation.test.js.map +1 -0
- package/dist/src/agents/registry.d.ts +36 -0
- package/dist/src/agents/registry.js +60 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/agents/registry.test.d.ts +6 -0
- package/dist/src/agents/registry.test.js +146 -0
- package/dist/src/agents/registry.test.js.map +1 -0
- package/dist/src/agents/schema-utils.d.ts +39 -0
- package/dist/src/agents/schema-utils.js +57 -0
- package/dist/src/agents/schema-utils.js.map +1 -0
- package/dist/src/agents/schema-utils.test.d.ts +6 -0
- package/dist/src/agents/schema-utils.test.js +144 -0
- package/dist/src/agents/schema-utils.test.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.d.ts +38 -0
- package/dist/src/agents/subagent-tool-wrapper.js +48 -0
- package/dist/src/agents/subagent-tool-wrapper.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.test.d.ts +6 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js +112 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -0
- package/dist/src/agents/types.d.ts +145 -0
- package/dist/src/agents/types.js +18 -0
- package/dist/src/agents/types.js.map +1 -0
- package/dist/src/agents/utils.d.ts +15 -0
- package/dist/src/agents/utils.js +29 -0
- package/dist/src/agents/utils.js.map +1 -0
- package/dist/src/agents/utils.test.d.ts +6 -0
- package/dist/src/agents/utils.test.js +87 -0
- package/dist/src/agents/utils.test.js.map +1 -0
- package/dist/src/code_assist/oauth-credential-storage.js +1 -1
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +14 -13
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/setup.js +4 -2
- package/dist/src/code_assist/setup.js.map +1 -1
- package/dist/src/config/config.d.ts +46 -15
- package/dist/src/config/config.js +106 -27
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +88 -3
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/constants.d.ts +11 -0
- package/dist/src/config/constants.js +16 -0
- package/dist/src/config/constants.js.map +1 -0
- package/dist/src/config/storage.d.ts +0 -1
- package/dist/src/config/storage.js +2 -2
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/config/storage.test.js +7 -6
- package/dist/src/config/storage.test.js.map +1 -1
- package/dist/src/core/baseLlmClient.d.ts +4 -0
- package/dist/src/core/baseLlmClient.js +24 -23
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +76 -13
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.d.ts +3 -1
- package/dist/src/core/client.js +68 -47
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +385 -134
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.js +3 -1
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/coreToolScheduler.js +24 -15
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +359 -23
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +11 -14
- package/dist/src/core/geminiChat.js +75 -124
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +260 -239
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/logger.test.js +2 -2
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +11 -11
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.d.ts +2 -1
- package/dist/src/core/prompts.js +93 -18
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +78 -29
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +15 -6
- package/dist/src/core/turn.js +14 -13
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +14 -2
- package/dist/src/core/turn.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/detect-ide.d.ts +5 -2
- package/dist/src/ide/detect-ide.js +11 -2
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +34 -0
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +2 -1
- package/dist/src/ide/ide-client.js +25 -20
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +6 -6
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.js +1 -1
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +13 -1
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- 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 +19 -14
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +137 -1
- package/dist/src/mcp/oauth-provider.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/sa-impersonation-provider.d.ts +33 -0
- package/dist/src/mcp/sa-impersonation-provider.js +130 -0
- package/dist/src/mcp/sa-impersonation-provider.js.map +1 -0
- package/dist/src/mcp/sa-impersonation-provider.test.d.ts +6 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js +117 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js.map +1 -0
- package/dist/src/mcp/token-storage/file-token-storage.js +2 -1
- package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -1
- package/dist/src/mcp/token-storage/file-token-storage.test.js +4 -3
- package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -1
- package/dist/src/routing/strategies/compositeStrategy.js +4 -3
- package/dist/src/routing/strategies/compositeStrategy.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +3 -2
- package/dist/src/services/chatRecordingService.js +3 -2
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/fileSystemService.d.ts +9 -0
- package/dist/src/services/fileSystemService.js +11 -0
- package/dist/src/services/fileSystemService.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +3 -0
- package/dist/src/services/shellExecutionService.js +165 -49
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +74 -5
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +18 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +156 -11
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +226 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +19 -3
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +44 -5
- 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 +0 -34
- package/dist/src/telemetry/constants.js +0 -34
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +6 -2
- package/dist/src/telemetry/index.js +17 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +10 -2
- package/dist/src/telemetry/loggers.js +206 -273
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +3 -3
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +260 -13
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/memory-monitor.d.ts +149 -0
- package/dist/src/telemetry/memory-monitor.js +335 -0
- package/dist/src/telemetry/memory-monitor.js.map +1 -0
- package/dist/src/telemetry/memory-monitor.test.d.ts +6 -0
- package/dist/src/telemetry/memory-monitor.test.js +472 -0
- package/dist/src/telemetry/memory-monitor.test.js.map +1 -0
- package/dist/src/telemetry/metrics.d.ts +436 -11
- package/dist/src/telemetry/metrics.js +600 -110
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +898 -16
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sdk.js +4 -1
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +13 -0
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/telemetryAttributes.d.ts +8 -0
- package/dist/src/telemetry/telemetryAttributes.js +18 -0
- package/dist/src/telemetry/telemetryAttributes.js.map +1 -0
- package/dist/src/telemetry/types.d.ts +167 -5
- package/dist/src/telemetry/types.js +692 -35
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
- package/dist/src/telemetry/uiTelemetry.js +1 -1
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +3 -3
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/mock-tool.d.ts +28 -3
- package/dist/src/test-utils/mock-tool.js +71 -1
- package/dist/src/test-utils/mock-tool.js.map +1 -1
- package/dist/src/tools/edit.js +6 -0
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +41 -0
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.js +4 -2
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/ls.js +1 -1
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +5 -14
- package/dist/src/tools/mcp-client.js +50 -97
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +175 -157
- package/dist/src/tools/mcp-client.test.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/memoryTool.d.ts +1 -1
- package/dist/src/tools/memoryTool.js +1 -2
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/memoryTool.test.js +9 -8
- package/dist/src/tools/memoryTool.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 +60 -4
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +2 -1
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +1 -1
- package/dist/src/tools/smart-edit.js +122 -12
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +136 -29
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-error.d.ts +22 -0
- package/dist/src/tools/tool-error.js +28 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +9 -0
- package/dist/src/tools/tool-names.js +18 -0
- package/dist/src/tools/tool-names.js.map +1 -0
- package/dist/src/tools/tool-registry.test.js +10 -10
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +11 -3
- package/dist/src/tools/tools.js +94 -3
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +3 -0
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +44 -0
- package/dist/src/tools/web-fetch.test.js.map +1 -1
- package/dist/src/tools/web-search.js +2 -1
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/write-file.js +2 -1
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +25 -0
- package/dist/src/tools/write-todos.js +151 -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/bfsFileSearch.d.ts +1 -1
- package/dist/src/utils/editCorrector.js +2 -2
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editor.js +1 -0
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +1 -0
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/errorParsing.d.ts +1 -1
- package/dist/src/utils/errorParsing.js +5 -33
- package/dist/src/utils/errorParsing.js.map +1 -1
- package/dist/src/utils/errorParsing.test.js +0 -88
- package/dist/src/utils/errorParsing.test.js.map +1 -1
- package/dist/src/utils/flashFallback.test.js +26 -45
- package/dist/src/utils/flashFallback.test.js.map +1 -1
- package/dist/src/utils/formatters.d.ts +1 -0
- package/dist/src/utils/formatters.js +2 -1
- package/dist/src/utils/formatters.js.map +1 -1
- package/dist/src/utils/formatters.test.d.ts +6 -0
- package/dist/src/utils/formatters.test.js +26 -0
- package/dist/src/utils/formatters.test.js.map +1 -0
- package/dist/src/utils/getFolderStructure.d.ts +1 -1
- package/dist/src/utils/getFolderStructure.js +1 -1
- package/dist/src/utils/getFolderStructure.js.map +1 -1
- package/dist/src/utils/getFolderStructure.test.js +7 -6
- package/dist/src/utils/getFolderStructure.test.js.map +1 -1
- package/dist/src/utils/googleErrors.d.ts +104 -0
- package/dist/src/utils/googleErrors.js +108 -0
- package/dist/src/utils/googleErrors.js.map +1 -0
- package/dist/src/utils/googleErrors.test.d.ts +6 -0
- package/dist/src/utils/googleErrors.test.js +212 -0
- package/dist/src/utils/googleErrors.test.js.map +1 -0
- package/dist/src/utils/googleQuotaErrors.d.ts +35 -0
- package/dist/src/utils/googleQuotaErrors.js +108 -0
- package/dist/src/utils/googleQuotaErrors.js.map +1 -0
- package/dist/src/utils/googleQuotaErrors.test.d.ts +6 -0
- package/dist/src/utils/googleQuotaErrors.test.js +189 -0
- package/dist/src/utils/googleQuotaErrors.test.js.map +1 -0
- package/dist/src/utils/installationManager.test.js +2 -1
- package/dist/src/utils/installationManager.test.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.js +14 -4
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.js +81 -0
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.d.ts +2 -1
- package/dist/src/utils/memoryDiscovery.js +3 -2
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +99 -21
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +13 -20
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.test.js +14 -0
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
- package/dist/src/utils/pathCorrector.d.ts +25 -0
- package/dist/src/utils/pathCorrector.js +33 -0
- package/dist/src/utils/pathCorrector.js.map +1 -0
- package/dist/src/utils/pathCorrector.test.d.ts +6 -0
- package/dist/src/utils/pathCorrector.test.js +83 -0
- package/dist/src/utils/pathCorrector.test.js.map +1 -0
- package/dist/src/utils/quotaErrorDetection.d.ts +0 -2
- package/dist/src/utils/quotaErrorDetection.js +0 -46
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/retry.d.ts +3 -1
- package/dist/src/utils/retry.js +60 -162
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +105 -135
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/schemaValidator.js +11 -1
- package/dist/src/utils/schemaValidator.js.map +1 -1
- package/dist/src/utils/schemaValidator.test.d.ts +6 -0
- package/dist/src/utils/schemaValidator.test.js +113 -0
- package/dist/src/utils/schemaValidator.test.js.map +1 -0
- package/dist/src/utils/shell-utils.d.ts +1 -0
- package/dist/src/utils/shell-utils.js +6 -2
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +5 -0
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/terminalSerializer.d.ts +1 -4
- package/dist/src/utils/terminalSerializer.js +3 -3
- package/dist/src/utils/terminalSerializer.js.map +1 -1
- package/dist/src/utils/thoughtUtils.d.ts +21 -0
- package/dist/src/utils/thoughtUtils.js +39 -0
- package/dist/src/utils/thoughtUtils.js.map +1 -0
- package/dist/src/utils/thoughtUtils.test.d.ts +6 -0
- package/dist/src/utils/thoughtUtils.test.js +78 -0
- package/dist/src/utils/thoughtUtils.test.js.map +1 -0
- package/dist/src/utils/tool-utils.js +2 -2
- package/dist/src/utils/tool-utils.js.map +1 -1
- package/dist/src/utils/tool-utils.test.js +8 -0
- package/dist/src/utils/tool-utils.test.js.map +1 -1
- package/dist/src/utils/userAccountManager.test.js +2 -1
- package/dist/src/utils/userAccountManager.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/dist/src/test-utils/tools.d.ts +0 -45
- package/dist/src/test-utils/tools.js +0 -105
- package/dist/src/test-utils/tools.js.map +0 -1
|
@@ -80,6 +80,7 @@ vi.mock('../ide/ideContext.js');
|
|
|
80
80
|
vi.mock('../telemetry/uiTelemetry.js', () => ({
|
|
81
81
|
uiTelemetryService: {
|
|
82
82
|
setLastPromptTokenCount: vi.fn(),
|
|
83
|
+
getLastPromptTokenCount: vi.fn(),
|
|
83
84
|
},
|
|
84
85
|
}));
|
|
85
86
|
/**
|
|
@@ -94,7 +95,7 @@ async function fromAsync(promise) {
|
|
|
94
95
|
}
|
|
95
96
|
return results;
|
|
96
97
|
}
|
|
97
|
-
describe('
|
|
98
|
+
describe('findCompressSplitPoint', () => {
|
|
98
99
|
it('should throw an error for non-positive numbers', () => {
|
|
99
100
|
expect(() => findCompressSplitPoint([], 0)).toThrow('Fraction must be between 0 and 1');
|
|
100
101
|
});
|
|
@@ -112,7 +113,7 @@ describe('findIndexAfterFraction', () => {
|
|
|
112
113
|
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
113
114
|
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
114
115
|
];
|
|
115
|
-
expect(findCompressSplitPoint(history, 0.5)).toBe(
|
|
116
|
+
expect(findCompressSplitPoint(history, 0.5)).toBe(4);
|
|
116
117
|
});
|
|
117
118
|
it('should handle a fraction of last index', () => {
|
|
118
119
|
const history = [
|
|
@@ -201,7 +202,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
201
202
|
mockContentGenerator = {
|
|
202
203
|
generateContent: mockGenerateContentFn,
|
|
203
204
|
generateContentStream: vi.fn(),
|
|
204
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 100 }),
|
|
205
205
|
batchEmbedContents: vi.fn(),
|
|
206
206
|
};
|
|
207
207
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
@@ -254,6 +254,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
254
254
|
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
|
255
255
|
getUseSmartEdit: vi.fn().mockReturnValue(false),
|
|
256
256
|
getUseModelRouter: vi.fn().mockReturnValue(false),
|
|
257
|
+
getContinueOnFailedApiCall: vi.fn(),
|
|
257
258
|
getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
|
|
258
259
|
storage: {
|
|
259
260
|
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
|
|
@@ -324,74 +325,128 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
324
325
|
function setup({ chatHistory = [
|
|
325
326
|
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
326
327
|
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
327
|
-
], } = {}) {
|
|
328
|
-
const
|
|
329
|
-
getHistory: vi.fn()
|
|
328
|
+
], originalTokenCount = 1000, summaryText = 'This is a summary.', } = {}) {
|
|
329
|
+
const mockOriginalChat = {
|
|
330
|
+
getHistory: vi.fn((_curated) => chatHistory),
|
|
330
331
|
setHistory: vi.fn(),
|
|
331
332
|
};
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
333
|
+
client['chat'] = mockOriginalChat;
|
|
334
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
335
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
336
|
+
candidates: [
|
|
337
|
+
{
|
|
338
|
+
content: {
|
|
339
|
+
role: 'model',
|
|
340
|
+
parts: [{ text: summaryText }],
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
],
|
|
344
|
+
});
|
|
345
|
+
// Calculate what the new history will be
|
|
346
|
+
const splitPoint = findCompressSplitPoint(chatHistory, 0.7); // 1 - 0.3
|
|
347
|
+
const historyToKeep = chatHistory.slice(splitPoint);
|
|
348
|
+
// This is the history that the new chat will have.
|
|
349
|
+
// It includes the default startChat history + the extra history from tryCompressChat
|
|
350
|
+
const newCompressedHistory = [
|
|
351
|
+
// Mocked envParts + canned response from startChat
|
|
352
|
+
{
|
|
353
|
+
role: 'user',
|
|
354
|
+
parts: [{ text: 'Mocked env context' }],
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
role: 'model',
|
|
358
|
+
parts: [{ text: 'Got it. Thanks for the context!' }],
|
|
359
|
+
},
|
|
360
|
+
// extraHistory from tryCompressChat
|
|
361
|
+
{
|
|
362
|
+
role: 'user',
|
|
363
|
+
parts: [{ text: summaryText }],
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
role: 'model',
|
|
367
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
368
|
+
},
|
|
369
|
+
...historyToKeep,
|
|
370
|
+
];
|
|
371
|
+
const mockNewChat = {
|
|
372
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
373
|
+
setHistory: vi.fn(),
|
|
374
|
+
};
|
|
375
|
+
client['startChat'] = vi
|
|
376
|
+
.fn()
|
|
377
|
+
.mockResolvedValue(mockNewChat);
|
|
378
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
379
|
+
const estimatedNewTokenCount = Math.floor(totalChars / 4);
|
|
380
|
+
return {
|
|
381
|
+
client,
|
|
382
|
+
mockOriginalChat,
|
|
383
|
+
mockNewChat,
|
|
384
|
+
estimatedNewTokenCount,
|
|
385
|
+
};
|
|
338
386
|
}
|
|
339
387
|
describe('when compression inflates the token count', () => {
|
|
340
388
|
it('allows compression to be forced/manual after a failure', async () => {
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
389
|
+
// Call 1 (Fails): Setup with a long summary to inflate tokens
|
|
390
|
+
const longSummary = 'long summary '.repeat(100);
|
|
391
|
+
const { client, estimatedNewTokenCount: inflatedTokenCount } = setup({
|
|
392
|
+
originalTokenCount: 100,
|
|
393
|
+
summaryText: longSummary,
|
|
344
394
|
});
|
|
395
|
+
expect(inflatedTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
345
396
|
await client.tryCompressChat('prompt-id-4', false); // Fails
|
|
346
|
-
|
|
397
|
+
// Call 2 (Forced): Re-setup with a short summary
|
|
398
|
+
const shortSummary = 'short';
|
|
399
|
+
const { estimatedNewTokenCount: compressedTokenCount } = setup({
|
|
400
|
+
originalTokenCount: 100,
|
|
401
|
+
summaryText: shortSummary,
|
|
402
|
+
});
|
|
403
|
+
expect(compressedTokenCount).toBeLessThanOrEqual(100); // Ensure setup is correct
|
|
404
|
+
const result = await client.tryCompressChat('prompt-id-4', true); // Forced
|
|
347
405
|
expect(result).toEqual({
|
|
348
406
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
349
|
-
newTokenCount:
|
|
350
|
-
originalTokenCount:
|
|
407
|
+
newTokenCount: compressedTokenCount,
|
|
408
|
+
originalTokenCount: 100,
|
|
351
409
|
});
|
|
352
410
|
});
|
|
353
411
|
it('yields the result even if the compression inflated the tokens', async () => {
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
412
|
+
const longSummary = 'long summary '.repeat(100);
|
|
413
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
414
|
+
originalTokenCount: 100,
|
|
415
|
+
summaryText: longSummary,
|
|
357
416
|
});
|
|
417
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
358
418
|
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
359
419
|
expect(result).toEqual({
|
|
360
420
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
361
|
-
newTokenCount:
|
|
362
|
-
originalTokenCount:
|
|
421
|
+
newTokenCount: estimatedNewTokenCount,
|
|
422
|
+
originalTokenCount: 100,
|
|
363
423
|
});
|
|
364
|
-
|
|
365
|
-
expect(uiTelemetryService.setLastPromptTokenCount).
|
|
424
|
+
// IMPORTANT: The change in client.ts means setLastPromptTokenCount is NOT called on failure
|
|
425
|
+
expect(uiTelemetryService.setLastPromptTokenCount).not.toHaveBeenCalled();
|
|
366
426
|
});
|
|
367
427
|
it('does not manipulate the source chat', async () => {
|
|
368
|
-
const
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
it('restores the history back to the original', async () => {
|
|
373
|
-
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
374
|
-
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
375
|
-
totalTokens: 999,
|
|
376
|
-
});
|
|
377
|
-
const originalHistory = [
|
|
378
|
-
{ role: 'user', parts: [{ text: 'what is your wisdom?' }] },
|
|
379
|
-
{ role: 'model', parts: [{ text: 'some wisdom' }] },
|
|
380
|
-
{ role: 'user', parts: [{ text: 'ahh that is a good a wisdom' }] },
|
|
381
|
-
];
|
|
382
|
-
const { client } = setup({
|
|
383
|
-
chatHistory: originalHistory,
|
|
428
|
+
const longSummary = 'long summary '.repeat(100);
|
|
429
|
+
const { client, mockOriginalChat, estimatedNewTokenCount } = setup({
|
|
430
|
+
originalTokenCount: 100,
|
|
431
|
+
summaryText: longSummary,
|
|
384
432
|
});
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
433
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
434
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
435
|
+
// On failure, the chat should NOT be replaced
|
|
436
|
+
expect(client['chat']).toBe(mockOriginalChat);
|
|
388
437
|
});
|
|
389
438
|
it('will not attempt to compress context after a failure', async () => {
|
|
390
|
-
const
|
|
391
|
-
|
|
439
|
+
const longSummary = 'long summary '.repeat(100);
|
|
440
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
441
|
+
originalTokenCount: 100,
|
|
442
|
+
summaryText: longSummary,
|
|
443
|
+
});
|
|
444
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
445
|
+
await client.tryCompressChat('prompt-id-4', false); // This fails and sets hasFailedCompressionAttempt = true
|
|
446
|
+
// This call should now be a NOOP
|
|
392
447
|
const result = await client.tryCompressChat('prompt-id-5', false);
|
|
393
|
-
//
|
|
394
|
-
expect(
|
|
448
|
+
// generateContent (for summary) should only have been called once
|
|
449
|
+
expect(mockGenerateContentFn).toHaveBeenCalledTimes(1);
|
|
395
450
|
expect(result).toEqual({
|
|
396
451
|
compressionStatus: CompressionStatus.NOOP,
|
|
397
452
|
newTokenCount: 0,
|
|
@@ -405,17 +460,16 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
405
460
|
mockGetHistory.mockReturnValue([
|
|
406
461
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
407
462
|
]);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
});
|
|
463
|
+
const originalTokenCount = MOCKED_TOKEN_LIMIT * 0.699;
|
|
464
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
411
465
|
const initialChat = client.getChat();
|
|
412
466
|
const result = await client.tryCompressChat('prompt-id-2', false);
|
|
413
467
|
const newChat = client.getChat();
|
|
414
468
|
expect(tokenLimit).toHaveBeenCalled();
|
|
415
469
|
expect(result).toEqual({
|
|
416
470
|
compressionStatus: CompressionStatus.NOOP,
|
|
417
|
-
newTokenCount:
|
|
418
|
-
originalTokenCount
|
|
471
|
+
newTokenCount: originalTokenCount,
|
|
472
|
+
originalTokenCount,
|
|
419
473
|
});
|
|
420
474
|
expect(newChat).toBe(initialChat);
|
|
421
475
|
});
|
|
@@ -427,21 +481,40 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
427
481
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
428
482
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
429
483
|
});
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
]);
|
|
484
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
485
|
+
mockGetHistory.mockReturnValue(history);
|
|
433
486
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
487
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
488
|
+
// We need to control the estimated new token count.
|
|
489
|
+
// We mock startChat to return a chat with a known history.
|
|
490
|
+
const summaryText = 'This is a summary.';
|
|
491
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
492
|
+
const historyToKeep = history.slice(splitPoint);
|
|
493
|
+
const newCompressedHistory = [
|
|
494
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
495
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
496
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
497
|
+
{
|
|
498
|
+
role: 'model',
|
|
499
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
500
|
+
},
|
|
501
|
+
...historyToKeep,
|
|
502
|
+
];
|
|
503
|
+
const mockNewChat = {
|
|
504
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
505
|
+
};
|
|
506
|
+
client['startChat'] = vi
|
|
507
|
+
.fn()
|
|
508
|
+
.mockResolvedValue(mockNewChat);
|
|
509
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
510
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
438
511
|
// Mock the summary response from the chat
|
|
439
512
|
mockGenerateContentFn.mockResolvedValue({
|
|
440
513
|
candidates: [
|
|
441
514
|
{
|
|
442
515
|
content: {
|
|
443
516
|
role: 'model',
|
|
444
|
-
parts: [{ text:
|
|
517
|
+
parts: [{ text: summaryText }],
|
|
445
518
|
},
|
|
446
519
|
},
|
|
447
520
|
],
|
|
@@ -461,21 +534,39 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
461
534
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
462
535
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
463
536
|
});
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
]);
|
|
537
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
538
|
+
mockGetHistory.mockReturnValue(history);
|
|
467
539
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
540
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
541
|
+
// Mock summary and new chat
|
|
542
|
+
const summaryText = 'This is a summary.';
|
|
543
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
544
|
+
const historyToKeep = history.slice(splitPoint);
|
|
545
|
+
const newCompressedHistory = [
|
|
546
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
547
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
548
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
549
|
+
{
|
|
550
|
+
role: 'model',
|
|
551
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
552
|
+
},
|
|
553
|
+
...historyToKeep,
|
|
554
|
+
];
|
|
555
|
+
const mockNewChat = {
|
|
556
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
557
|
+
};
|
|
558
|
+
client['startChat'] = vi
|
|
559
|
+
.fn()
|
|
560
|
+
.mockResolvedValue(mockNewChat);
|
|
561
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
562
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
472
563
|
// Mock the summary response from the chat
|
|
473
564
|
mockGenerateContentFn.mockResolvedValue({
|
|
474
565
|
candidates: [
|
|
475
566
|
{
|
|
476
567
|
content: {
|
|
477
568
|
role: 'model',
|
|
478
|
-
parts: [{ text:
|
|
569
|
+
parts: [{ text: summaryText }],
|
|
479
570
|
},
|
|
480
571
|
},
|
|
481
572
|
],
|
|
@@ -497,7 +588,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
497
588
|
it('should not compress across a function call response', async () => {
|
|
498
589
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
499
590
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
500
|
-
|
|
591
|
+
const history = [
|
|
501
592
|
{ role: 'user', parts: [{ text: '...history 1...' }] },
|
|
502
593
|
{ role: 'model', parts: [{ text: '...history 2...' }] },
|
|
503
594
|
{ role: 'user', parts: [{ text: '...history 3...' }] },
|
|
@@ -514,19 +605,43 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
514
605
|
{ role: 'model', parts: [{ text: '...history 10...' }] },
|
|
515
606
|
// Instead we will break here.
|
|
516
607
|
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
517
|
-
]
|
|
608
|
+
];
|
|
609
|
+
mockGetHistory.mockReturnValue(history);
|
|
518
610
|
const originalTokenCount = 1000 * 0.7;
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
611
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
612
|
+
// Mock summary and new chat
|
|
613
|
+
const summaryText = 'This is a summary.';
|
|
614
|
+
const splitPoint = findCompressSplitPoint(history, 0.7); // This should be 10
|
|
615
|
+
expect(splitPoint).toBe(10); // Verify split point logic
|
|
616
|
+
const historyToKeep = history.slice(splitPoint); // Should keep last user message
|
|
617
|
+
expect(historyToKeep).toEqual([
|
|
618
|
+
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
619
|
+
]);
|
|
620
|
+
const newCompressedHistory = [
|
|
621
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
622
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
623
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
624
|
+
{
|
|
625
|
+
role: 'model',
|
|
626
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
627
|
+
},
|
|
628
|
+
...historyToKeep,
|
|
629
|
+
];
|
|
630
|
+
const mockNewChat = {
|
|
631
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
632
|
+
};
|
|
633
|
+
client['startChat'] = vi
|
|
634
|
+
.fn()
|
|
635
|
+
.mockResolvedValue(mockNewChat);
|
|
636
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
637
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
523
638
|
// Mock the summary response from the chat
|
|
524
639
|
mockGenerateContentFn.mockResolvedValue({
|
|
525
640
|
candidates: [
|
|
526
641
|
{
|
|
527
642
|
content: {
|
|
528
643
|
role: 'model',
|
|
529
|
-
parts: [{ text:
|
|
644
|
+
parts: [{ text: summaryText }],
|
|
530
645
|
},
|
|
531
646
|
},
|
|
532
647
|
],
|
|
@@ -544,35 +659,53 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
544
659
|
});
|
|
545
660
|
// Assert that the chat was reset
|
|
546
661
|
expect(newChat).not.toBe(initialChat);
|
|
547
|
-
// 1. standard start context message
|
|
548
|
-
// 2. standard canned
|
|
549
|
-
// 3. compressed summary message
|
|
550
|
-
// 4. standard canned
|
|
551
|
-
// 5. The last user message (
|
|
662
|
+
// 1. standard start context message (env)
|
|
663
|
+
// 2. standard canned model response
|
|
664
|
+
// 3. compressed summary message (user)
|
|
665
|
+
// 4. standard canned model response
|
|
666
|
+
// 5. The last user message (historyToKeep)
|
|
552
667
|
expect(newChat.getHistory().length).toEqual(5);
|
|
553
668
|
});
|
|
554
669
|
it('should always trigger summarization when force is true, regardless of token count', async () => {
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
670
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
671
|
+
mockGetHistory.mockReturnValue(history);
|
|
672
|
+
const originalTokenCount = 100; // Well below threshold, but > estimated new count
|
|
673
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
674
|
+
// Mock summary and new chat
|
|
675
|
+
const summaryText = 'This is a summary.';
|
|
676
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
677
|
+
const historyToKeep = history.slice(splitPoint);
|
|
678
|
+
const newCompressedHistory = [
|
|
679
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
680
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
681
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
682
|
+
{
|
|
683
|
+
role: 'model',
|
|
684
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
685
|
+
},
|
|
686
|
+
...historyToKeep,
|
|
687
|
+
];
|
|
688
|
+
const mockNewChat = {
|
|
689
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
690
|
+
};
|
|
691
|
+
client['startChat'] = vi
|
|
692
|
+
.fn()
|
|
693
|
+
.mockResolvedValue(mockNewChat);
|
|
694
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
695
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
563
696
|
// Mock the summary response from the chat
|
|
564
697
|
mockGenerateContentFn.mockResolvedValue({
|
|
565
698
|
candidates: [
|
|
566
699
|
{
|
|
567
700
|
content: {
|
|
568
701
|
role: 'model',
|
|
569
|
-
parts: [{ text:
|
|
702
|
+
parts: [{ text: summaryText }],
|
|
570
703
|
},
|
|
571
704
|
},
|
|
572
705
|
],
|
|
573
706
|
});
|
|
574
707
|
const initialChat = client.getChat();
|
|
575
|
-
const result = await client.tryCompressChat('prompt-id-1',
|
|
708
|
+
const result = await client.tryCompressChat('prompt-id-1', true); // force = true
|
|
576
709
|
const newChat = client.getChat();
|
|
577
710
|
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
578
711
|
expect(result).toEqual({
|
|
@@ -583,45 +716,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
583
716
|
// Assert that the chat was reset
|
|
584
717
|
expect(newChat).not.toBe(initialChat);
|
|
585
718
|
});
|
|
586
|
-
it('should use current model from config for token counting after sendMessage', async () => {
|
|
587
|
-
const initialModel = mockConfig.getModel();
|
|
588
|
-
// mock the model has been changed between calls of `countTokens`
|
|
589
|
-
const firstCurrentModel = initialModel + '-changed-1';
|
|
590
|
-
const secondCurrentModel = initialModel + '-changed-2';
|
|
591
|
-
vi.mocked(mockConfig.getModel)
|
|
592
|
-
.mockReturnValueOnce(firstCurrentModel)
|
|
593
|
-
.mockReturnValueOnce(secondCurrentModel);
|
|
594
|
-
vi.mocked(mockContentGenerator.countTokens)
|
|
595
|
-
.mockResolvedValueOnce({ totalTokens: 100000 })
|
|
596
|
-
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
597
|
-
const mockSendMessage = vi.fn().mockResolvedValue({ text: 'Summary' });
|
|
598
|
-
const mockChatHistory = [
|
|
599
|
-
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
600
|
-
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
601
|
-
];
|
|
602
|
-
const mockChat = {
|
|
603
|
-
getHistory: vi.fn().mockImplementation(() => [...mockChatHistory]),
|
|
604
|
-
setHistory: vi.fn(),
|
|
605
|
-
sendMessage: mockSendMessage,
|
|
606
|
-
};
|
|
607
|
-
client['chat'] = mockChat;
|
|
608
|
-
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
|
609
|
-
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
610
|
-
expect(mockContentGenerator.countTokens).toHaveBeenCalledTimes(2);
|
|
611
|
-
expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(1, {
|
|
612
|
-
model: firstCurrentModel,
|
|
613
|
-
contents: [...mockChatHistory],
|
|
614
|
-
});
|
|
615
|
-
expect(mockContentGenerator.countTokens).toHaveBeenNthCalledWith(2, {
|
|
616
|
-
model: secondCurrentModel,
|
|
617
|
-
contents: expect.any(Array),
|
|
618
|
-
});
|
|
619
|
-
expect(result).toEqual({
|
|
620
|
-
compressionStatus: CompressionStatus.COMPRESSED,
|
|
621
|
-
originalTokenCount: 100000,
|
|
622
|
-
newTokenCount: 5000,
|
|
623
|
-
});
|
|
624
|
-
});
|
|
625
719
|
});
|
|
626
720
|
describe('sendMessageStream', () => {
|
|
627
721
|
it('emits a compression event when the context was automatically compressed', async () => {
|
|
@@ -649,9 +743,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
649
743
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
650
744
|
},
|
|
651
745
|
{ compressionStatus: CompressionStatus.NOOP },
|
|
652
|
-
{
|
|
653
|
-
compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
|
|
654
|
-
},
|
|
655
746
|
])('does not emit a compression event when the status is $compressionStatus', async ({ compressionStatus }) => {
|
|
656
747
|
// Arrange
|
|
657
748
|
const mockStream = (async function* () {
|
|
@@ -909,6 +1000,7 @@ ${JSON.stringify({
|
|
|
909
1000
|
expect(finalResult).toBeInstanceOf(Turn);
|
|
910
1001
|
});
|
|
911
1002
|
it('should stop infinite loop after MAX_TURNS when nextSpeaker always returns model', async () => {
|
|
1003
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(true);
|
|
912
1004
|
// Get the mocked checkNextSpeaker function and configure it to trigger infinite loop
|
|
913
1005
|
const { checkNextSpeaker } = await import('../utils/nextSpeakerChecker.js');
|
|
914
1006
|
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
|
|
@@ -1064,6 +1156,82 @@ ${JSON.stringify({
|
|
|
1064
1156
|
console.log(`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
|
|
1065
1157
|
`${eventCount} events generated (properly bounded by MAX_TURNS)`);
|
|
1066
1158
|
});
|
|
1159
|
+
it('should yield ContextWindowWillOverflow when the context window is about to overflow', async () => {
|
|
1160
|
+
// Arrange
|
|
1161
|
+
const MOCKED_TOKEN_LIMIT = 1000;
|
|
1162
|
+
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
1163
|
+
// Set last prompt token count
|
|
1164
|
+
const lastPromptTokenCount = 900;
|
|
1165
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(lastPromptTokenCount);
|
|
1166
|
+
// Remaining = 100. Threshold (95%) = 95.
|
|
1167
|
+
// We need a request > 95 tokens.
|
|
1168
|
+
// A string of length 400 is roughly 100 tokens.
|
|
1169
|
+
const longText = 'a'.repeat(400);
|
|
1170
|
+
const request = [{ text: longText }];
|
|
1171
|
+
const estimatedRequestTokenCount = Math.floor(JSON.stringify(request).length / 4);
|
|
1172
|
+
const remainingTokenCount = MOCKED_TOKEN_LIMIT - lastPromptTokenCount;
|
|
1173
|
+
// Mock tryCompressChat to not compress
|
|
1174
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
1175
|
+
originalTokenCount: lastPromptTokenCount,
|
|
1176
|
+
newTokenCount: lastPromptTokenCount,
|
|
1177
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
1178
|
+
});
|
|
1179
|
+
// Act
|
|
1180
|
+
const stream = client.sendMessageStream(request, new AbortController().signal, 'prompt-id-overflow');
|
|
1181
|
+
const events = await fromAsync(stream);
|
|
1182
|
+
// Assert
|
|
1183
|
+
expect(events).toContainEqual({
|
|
1184
|
+
type: GeminiEventType.ContextWindowWillOverflow,
|
|
1185
|
+
value: {
|
|
1186
|
+
estimatedRequestTokenCount,
|
|
1187
|
+
remainingTokenCount,
|
|
1188
|
+
},
|
|
1189
|
+
});
|
|
1190
|
+
// Ensure turn.run is not called
|
|
1191
|
+
expect(mockTurnRunFn).not.toHaveBeenCalled();
|
|
1192
|
+
});
|
|
1193
|
+
it("should use the sticky model's token limit for the overflow check", async () => {
|
|
1194
|
+
// Arrange
|
|
1195
|
+
const STICKY_MODEL = 'gemini-1.5-flash';
|
|
1196
|
+
const STICKY_MODEL_LIMIT = 1000;
|
|
1197
|
+
const CONFIG_MODEL_LIMIT = 2000;
|
|
1198
|
+
// Set up token limits
|
|
1199
|
+
vi.mocked(tokenLimit).mockImplementation((model) => {
|
|
1200
|
+
if (model === STICKY_MODEL)
|
|
1201
|
+
return STICKY_MODEL_LIMIT;
|
|
1202
|
+
return CONFIG_MODEL_LIMIT;
|
|
1203
|
+
});
|
|
1204
|
+
// Set the sticky model
|
|
1205
|
+
client['currentSequenceModel'] = STICKY_MODEL;
|
|
1206
|
+
// Set token count
|
|
1207
|
+
const lastPromptTokenCount = 900;
|
|
1208
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(lastPromptTokenCount);
|
|
1209
|
+
// Remaining (sticky) = 100. Threshold (95%) = 95.
|
|
1210
|
+
// We need a request > 95 tokens.
|
|
1211
|
+
const longText = 'a'.repeat(400);
|
|
1212
|
+
const request = [{ text: longText }];
|
|
1213
|
+
const estimatedRequestTokenCount = Math.floor(JSON.stringify(request).length / 4);
|
|
1214
|
+
const remainingTokenCount = STICKY_MODEL_LIMIT - lastPromptTokenCount;
|
|
1215
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
1216
|
+
originalTokenCount: lastPromptTokenCount,
|
|
1217
|
+
newTokenCount: lastPromptTokenCount,
|
|
1218
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
1219
|
+
});
|
|
1220
|
+
// Act
|
|
1221
|
+
const stream = client.sendMessageStream(request, new AbortController().signal, 'test-session-id');
|
|
1222
|
+
const events = await fromAsync(stream);
|
|
1223
|
+
// Assert
|
|
1224
|
+
// Should overflow based on the sticky model's limit
|
|
1225
|
+
expect(events).toContainEqual({
|
|
1226
|
+
type: GeminiEventType.ContextWindowWillOverflow,
|
|
1227
|
+
value: {
|
|
1228
|
+
estimatedRequestTokenCount,
|
|
1229
|
+
remainingTokenCount,
|
|
1230
|
+
},
|
|
1231
|
+
});
|
|
1232
|
+
expect(tokenLimit).toHaveBeenCalledWith(STICKY_MODEL);
|
|
1233
|
+
expect(mockTurnRunFn).not.toHaveBeenCalled();
|
|
1234
|
+
});
|
|
1067
1235
|
describe('Model Routing', () => {
|
|
1068
1236
|
let mockRouterService;
|
|
1069
1237
|
beforeEach(() => {
|
|
@@ -1149,6 +1317,89 @@ ${JSON.stringify({
|
|
|
1149
1317
|
[{ text: 'Continue' }], expect.any(Object));
|
|
1150
1318
|
});
|
|
1151
1319
|
});
|
|
1320
|
+
it('should recursively call sendMessageStream with "Please continue." when InvalidStream event is received', async () => {
|
|
1321
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(true);
|
|
1322
|
+
// Arrange
|
|
1323
|
+
const mockStream1 = (async function* () {
|
|
1324
|
+
yield { type: GeminiEventType.InvalidStream };
|
|
1325
|
+
})();
|
|
1326
|
+
const mockStream2 = (async function* () {
|
|
1327
|
+
yield { type: GeminiEventType.Content, value: 'Continued content' };
|
|
1328
|
+
})();
|
|
1329
|
+
mockTurnRunFn
|
|
1330
|
+
.mockReturnValueOnce(mockStream1)
|
|
1331
|
+
.mockReturnValueOnce(mockStream2);
|
|
1332
|
+
const mockChat = {
|
|
1333
|
+
addHistory: vi.fn(),
|
|
1334
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1335
|
+
};
|
|
1336
|
+
client['chat'] = mockChat;
|
|
1337
|
+
const initialRequest = [{ text: 'Hi' }];
|
|
1338
|
+
const promptId = 'prompt-id-invalid-stream';
|
|
1339
|
+
const signal = new AbortController().signal;
|
|
1340
|
+
// Act
|
|
1341
|
+
const stream = client.sendMessageStream(initialRequest, signal, promptId);
|
|
1342
|
+
const events = await fromAsync(stream);
|
|
1343
|
+
// Assert
|
|
1344
|
+
expect(events).toEqual([
|
|
1345
|
+
{ type: GeminiEventType.InvalidStream },
|
|
1346
|
+
{ type: GeminiEventType.Content, value: 'Continued content' },
|
|
1347
|
+
]);
|
|
1348
|
+
// Verify that turn.run was called twice
|
|
1349
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(2);
|
|
1350
|
+
// First call with original request
|
|
1351
|
+
expect(mockTurnRunFn).toHaveBeenNthCalledWith(1, expect.any(String), initialRequest, expect.any(Object));
|
|
1352
|
+
// Second call with "Please continue."
|
|
1353
|
+
expect(mockTurnRunFn).toHaveBeenNthCalledWith(2, expect.any(String), [{ text: 'System: Please continue.' }], expect.any(Object));
|
|
1354
|
+
});
|
|
1355
|
+
it('should not recursively call sendMessageStream with "Please continue." when InvalidStream event is received and flag is false', async () => {
|
|
1356
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(false);
|
|
1357
|
+
// Arrange
|
|
1358
|
+
const mockStream1 = (async function* () {
|
|
1359
|
+
yield { type: GeminiEventType.InvalidStream };
|
|
1360
|
+
})();
|
|
1361
|
+
mockTurnRunFn.mockReturnValueOnce(mockStream1);
|
|
1362
|
+
const mockChat = {
|
|
1363
|
+
addHistory: vi.fn(),
|
|
1364
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1365
|
+
};
|
|
1366
|
+
client['chat'] = mockChat;
|
|
1367
|
+
const initialRequest = [{ text: 'Hi' }];
|
|
1368
|
+
const promptId = 'prompt-id-invalid-stream';
|
|
1369
|
+
const signal = new AbortController().signal;
|
|
1370
|
+
// Act
|
|
1371
|
+
const stream = client.sendMessageStream(initialRequest, signal, promptId);
|
|
1372
|
+
const events = await fromAsync(stream);
|
|
1373
|
+
// Assert
|
|
1374
|
+
expect(events).toEqual([{ type: GeminiEventType.InvalidStream }]);
|
|
1375
|
+
// Verify that turn.run was called only once
|
|
1376
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(1);
|
|
1377
|
+
});
|
|
1378
|
+
it('should stop recursing after one retry when InvalidStream events are repeatedly received', async () => {
|
|
1379
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(true);
|
|
1380
|
+
// Arrange
|
|
1381
|
+
// Always return a new invalid stream
|
|
1382
|
+
mockTurnRunFn.mockImplementation(() => (async function* () {
|
|
1383
|
+
yield { type: GeminiEventType.InvalidStream };
|
|
1384
|
+
})());
|
|
1385
|
+
const mockChat = {
|
|
1386
|
+
addHistory: vi.fn(),
|
|
1387
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1388
|
+
};
|
|
1389
|
+
client['chat'] = mockChat;
|
|
1390
|
+
const initialRequest = [{ text: 'Hi' }];
|
|
1391
|
+
const promptId = 'prompt-id-infinite-invalid-stream';
|
|
1392
|
+
const signal = new AbortController().signal;
|
|
1393
|
+
// Act
|
|
1394
|
+
const stream = client.sendMessageStream(initialRequest, signal, promptId);
|
|
1395
|
+
const events = await fromAsync(stream);
|
|
1396
|
+
// Assert
|
|
1397
|
+
// We expect 2 InvalidStream events (original + 1 retry)
|
|
1398
|
+
expect(events.length).toBe(2);
|
|
1399
|
+
expect(events.every((e) => e.type === GeminiEventType.InvalidStream)).toBe(true);
|
|
1400
|
+
// Verify that turn.run was called twice
|
|
1401
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(2);
|
|
1402
|
+
});
|
|
1152
1403
|
describe('Editor context delta', () => {
|
|
1153
1404
|
const mockStream = (async function* () {
|
|
1154
1405
|
yield { type: 'content', value: 'Hello' };
|
|
@@ -1717,7 +1968,7 @@ ${JSON.stringify({
|
|
|
1717
1968
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1718
1969
|
config: {
|
|
1719
1970
|
abortSignal,
|
|
1720
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
1971
|
+
systemInstruction: getCoreSystemPrompt({}, ''),
|
|
1721
1972
|
temperature: 0.5,
|
|
1722
1973
|
topP: 1,
|
|
1723
1974
|
},
|