@machina.ai/cell-cli-core 1.6.1-rc2 → 1.10.0-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 +4 -3
- package/dist/index.js +4 -3
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/dist/src/agents/codebase-investigator.d.ts +46 -0
- package/dist/src/agents/codebase-investigator.js +135 -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 +680 -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 +81 -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/oauth-credential-storage.test.js +1 -1
- package/dist/src/code_assist/oauth-credential-storage.test.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 +61 -15
- package/dist/src/config/config.js +132 -28
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +67 -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 +6 -3
- package/dist/src/core/client.js +103 -64
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +470 -118
- 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 +12 -12
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +260 -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 +80 -124
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +316 -239
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/logger.test.js +16 -16
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
- package/dist/src/core/nonInteractiveToolExecutor.js +2 -2
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +19 -19
- 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 +53 -111
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +83 -29
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/subagent.js +1 -1
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagent.test.js +38 -12
- package/dist/src/core/subagent.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 +45 -14
- package/dist/src/ide/detect-ide.js +32 -69
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +40 -46
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +4 -4
- package/dist/src/ide/ide-client.js +33 -32
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +12 -25
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.d.ts +2 -2
- package/dist/src/ide/ide-installer.js +8 -10
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +33 -14
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/ide/process-utils.js +85 -75
- package/dist/src/ide/process-utils.js.map +1 -1
- package/dist/src/ide/process-utils.test.js +83 -90
- package/dist/src/ide/process-utils.test.js.map +1 -1
- package/dist/src/index.d.ts +7 -2
- package/dist/src/index.js +7 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +4 -1
- package/dist/src/mcp/oauth-provider.js +31 -25
- package/dist/src/mcp/oauth-provider.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/policy/policy-engine.js +11 -2
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +45 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/routing/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/activity-detector.d.ts +41 -0
- package/dist/src/telemetry/activity-detector.js +61 -0
- package/dist/src/telemetry/activity-detector.js.map +1 -0
- package/dist/src/telemetry/activity-detector.test.d.ts +6 -0
- package/dist/src/telemetry/activity-detector.test.js +136 -0
- package/dist/src/telemetry/activity-detector.test.js.map +1 -0
- package/dist/src/telemetry/activity-types.d.ts +19 -0
- package/dist/src/telemetry/activity-types.js +21 -0
- package/dist/src/telemetry/activity-types.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +18 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +257 -108
- 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 +258 -33
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +118 -100
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +146 -103
- 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 +74 -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 +9 -3
- package/dist/src/telemetry/index.js +19 -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 +316 -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 +1 -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 +168 -5
- package/dist/src/telemetry/types.js +696 -34
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +2 -2
- package/dist/src/telemetry/uiTelemetry.js +3 -4
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +14 -14
- 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/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 +51 -98
- 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/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 +116 -12
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +91 -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.d.ts +7 -0
- package/dist/src/tools/web-fetch.js +42 -10
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +127 -8
- 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/flashFallback.test.js +2 -2
- 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/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/retry.d.ts +4 -1
- package/dist/src/utils/retry.js +40 -17
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +104 -31
- 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
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
-
import {
|
|
7
|
+
import { findCompressSplitPoint, isThinkingDefault, isThinkingSupported, GeminiClient, } from './client.js';
|
|
8
8
|
import { AuthType, } from './contentGenerator.js';
|
|
9
9
|
import {} from './geminiChat.js';
|
|
10
10
|
import { CompressionStatus, GeminiEventType, Turn, } from './turn.js';
|
|
@@ -15,6 +15,7 @@ import { setSimulate429 } from '../utils/testUtils.js';
|
|
|
15
15
|
import { tokenLimit } from './tokenLimits.js';
|
|
16
16
|
import { ideContextStore } from '../ide/ideContext.js';
|
|
17
17
|
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
|
18
|
+
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
|
|
18
19
|
// Mock fs module to prevent actual file system operations during tests
|
|
19
20
|
const mockFileSystem = new Map();
|
|
20
21
|
vi.mock('node:fs', () => {
|
|
@@ -76,6 +77,12 @@ vi.mock('../telemetry/index.js', () => ({
|
|
|
76
77
|
logApiError: vi.fn(),
|
|
77
78
|
}));
|
|
78
79
|
vi.mock('../ide/ideContext.js');
|
|
80
|
+
vi.mock('../telemetry/uiTelemetry.js', () => ({
|
|
81
|
+
uiTelemetryService: {
|
|
82
|
+
setLastPromptTokenCount: vi.fn(),
|
|
83
|
+
getLastPromptTokenCount: vi.fn(),
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
79
86
|
/**
|
|
80
87
|
* Array.fromAsync ponyfill, which will be available in es 2024.
|
|
81
88
|
*
|
|
@@ -88,42 +95,60 @@ async function fromAsync(promise) {
|
|
|
88
95
|
}
|
|
89
96
|
return results;
|
|
90
97
|
}
|
|
91
|
-
describe('
|
|
92
|
-
const history = [
|
|
93
|
-
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66
|
|
94
|
-
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68
|
|
95
|
-
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66
|
|
96
|
-
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68
|
|
97
|
-
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65
|
|
98
|
-
];
|
|
99
|
-
// Total length: 333
|
|
98
|
+
describe('findCompressSplitPoint', () => {
|
|
100
99
|
it('should throw an error for non-positive numbers', () => {
|
|
101
|
-
expect(() =>
|
|
100
|
+
expect(() => findCompressSplitPoint([], 0)).toThrow('Fraction must be between 0 and 1');
|
|
102
101
|
});
|
|
103
102
|
it('should throw an error for a fraction greater than or equal to 1', () => {
|
|
104
|
-
expect(() =>
|
|
103
|
+
expect(() => findCompressSplitPoint([], 1)).toThrow('Fraction must be between 0 and 1');
|
|
104
|
+
});
|
|
105
|
+
it('should handle an empty history', () => {
|
|
106
|
+
expect(findCompressSplitPoint([], 0.5)).toBe(0);
|
|
105
107
|
});
|
|
106
108
|
it('should handle a fraction in the middle', () => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
109
|
+
const history = [
|
|
110
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (19%)
|
|
111
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (40%)
|
|
112
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (60%)
|
|
113
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
114
|
+
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
115
|
+
];
|
|
116
|
+
expect(findCompressSplitPoint(history, 0.5)).toBe(4);
|
|
117
|
+
});
|
|
118
|
+
it('should handle a fraction of last index', () => {
|
|
119
|
+
const history = [
|
|
120
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (19%)
|
|
121
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (40%)
|
|
122
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (60%)
|
|
123
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
124
|
+
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
125
|
+
];
|
|
126
|
+
expect(findCompressSplitPoint(history, 0.9)).toBe(4);
|
|
113
127
|
});
|
|
114
|
-
it('should handle a fraction
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
it('should handle a fraction of after last index', () => {
|
|
129
|
+
const history = [
|
|
130
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (24%%)
|
|
131
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (50%)
|
|
132
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (74%)
|
|
133
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (100%)
|
|
134
|
+
];
|
|
135
|
+
expect(findCompressSplitPoint(history, 0.8)).toBe(4);
|
|
121
136
|
});
|
|
122
|
-
it('should
|
|
123
|
-
|
|
137
|
+
it('should return earlier splitpoint if no valid ones are after threshhold', () => {
|
|
138
|
+
const history = [
|
|
139
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] },
|
|
140
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] },
|
|
141
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] },
|
|
142
|
+
{ role: 'model', parts: [{ functionCall: {} }] },
|
|
143
|
+
];
|
|
144
|
+
// Can't return 4 because the previous item has a function call.
|
|
145
|
+
expect(findCompressSplitPoint(history, 0.99)).toBe(2);
|
|
124
146
|
});
|
|
125
147
|
it('should handle a history with only one item', () => {
|
|
126
|
-
|
|
148
|
+
const historyWithEmptyParts = [
|
|
149
|
+
{ role: 'user', parts: [{ text: 'Message 1' }] },
|
|
150
|
+
];
|
|
151
|
+
expect(findCompressSplitPoint(historyWithEmptyParts, 0.5)).toBe(0);
|
|
127
152
|
});
|
|
128
153
|
it('should handle history with weird parts', () => {
|
|
129
154
|
const historyWithEmptyParts = [
|
|
@@ -131,7 +156,7 @@ describe('findIndexAfterFraction', () => {
|
|
|
131
156
|
{ role: 'model', parts: [{ fileData: { fileUri: 'derp' } }] },
|
|
132
157
|
{ role: 'user', parts: [{ text: 'Message 2' }] },
|
|
133
158
|
];
|
|
134
|
-
expect(
|
|
159
|
+
expect(findCompressSplitPoint(historyWithEmptyParts, 0.5)).toBe(2);
|
|
135
160
|
});
|
|
136
161
|
});
|
|
137
162
|
describe('isThinkingSupported', () => {
|
|
@@ -168,6 +193,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
168
193
|
let mockGenerateContentFn;
|
|
169
194
|
beforeEach(async () => {
|
|
170
195
|
vi.resetAllMocks();
|
|
196
|
+
vi.mocked(uiTelemetryService.setLastPromptTokenCount).mockClear();
|
|
171
197
|
mockGenerateContentFn = vi.fn().mockResolvedValue({
|
|
172
198
|
candidates: [{ content: { parts: [{ text: '{"key": "value"}' }] } }],
|
|
173
199
|
});
|
|
@@ -176,7 +202,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
176
202
|
mockContentGenerator = {
|
|
177
203
|
generateContent: mockGenerateContentFn,
|
|
178
204
|
generateContentStream: vi.fn(),
|
|
179
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 100 }),
|
|
180
205
|
batchEmbedContents: vi.fn(),
|
|
181
206
|
};
|
|
182
207
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
@@ -229,6 +254,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
229
254
|
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
|
230
255
|
getUseSmartEdit: vi.fn().mockReturnValue(false),
|
|
231
256
|
getUseModelRouter: vi.fn().mockReturnValue(false),
|
|
257
|
+
getContinueOnFailedApiCall: vi.fn(),
|
|
232
258
|
getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
|
|
233
259
|
storage: {
|
|
234
260
|
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
|
|
@@ -299,72 +325,128 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
299
325
|
function setup({ chatHistory = [
|
|
300
326
|
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
301
327
|
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
302
|
-
], } = {}) {
|
|
303
|
-
const
|
|
304
|
-
getHistory: vi.fn()
|
|
328
|
+
], originalTokenCount = 1000, summaryText = 'This is a summary.', } = {}) {
|
|
329
|
+
const mockOriginalChat = {
|
|
330
|
+
getHistory: vi.fn((_curated) => chatHistory),
|
|
305
331
|
setHistory: vi.fn(),
|
|
306
332
|
};
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
+
};
|
|
313
386
|
}
|
|
314
387
|
describe('when compression inflates the token count', () => {
|
|
315
388
|
it('allows compression to be forced/manual after a failure', async () => {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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,
|
|
319
394
|
});
|
|
395
|
+
expect(inflatedTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
320
396
|
await client.tryCompressChat('prompt-id-4', false); // Fails
|
|
321
|
-
|
|
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
|
|
322
405
|
expect(result).toEqual({
|
|
323
406
|
compressionStatus: CompressionStatus.COMPRESSED,
|
|
324
|
-
newTokenCount:
|
|
325
|
-
originalTokenCount:
|
|
407
|
+
newTokenCount: compressedTokenCount,
|
|
408
|
+
originalTokenCount: 100,
|
|
326
409
|
});
|
|
327
410
|
});
|
|
328
411
|
it('yields the result even if the compression inflated the tokens', async () => {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
412
|
+
const longSummary = 'long summary '.repeat(100);
|
|
413
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
414
|
+
originalTokenCount: 100,
|
|
415
|
+
summaryText: longSummary,
|
|
332
416
|
});
|
|
417
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
333
418
|
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
334
419
|
expect(result).toEqual({
|
|
335
420
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
336
|
-
newTokenCount:
|
|
337
|
-
originalTokenCount:
|
|
421
|
+
newTokenCount: estimatedNewTokenCount,
|
|
422
|
+
originalTokenCount: 100,
|
|
338
423
|
});
|
|
424
|
+
// IMPORTANT: The change in client.ts means setLastPromptTokenCount is NOT called on failure
|
|
425
|
+
expect(uiTelemetryService.setLastPromptTokenCount).not.toHaveBeenCalled();
|
|
339
426
|
});
|
|
340
427
|
it('does not manipulate the source chat', async () => {
|
|
341
|
-
const
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
it('restores the history back to the original', async () => {
|
|
346
|
-
vi.mocked(tokenLimit).mockReturnValue(1000);
|
|
347
|
-
vi.mocked(mockContentGenerator.countTokens).mockResolvedValue({
|
|
348
|
-
totalTokens: 999,
|
|
349
|
-
});
|
|
350
|
-
const originalHistory = [
|
|
351
|
-
{ role: 'user', parts: [{ text: 'what is your wisdom?' }] },
|
|
352
|
-
{ role: 'model', parts: [{ text: 'some wisdom' }] },
|
|
353
|
-
{ role: 'user', parts: [{ text: 'ahh that is a good a wisdom' }] },
|
|
354
|
-
];
|
|
355
|
-
const { client } = setup({
|
|
356
|
-
chatHistory: originalHistory,
|
|
428
|
+
const longSummary = 'long summary '.repeat(100);
|
|
429
|
+
const { client, mockOriginalChat, estimatedNewTokenCount } = setup({
|
|
430
|
+
originalTokenCount: 100,
|
|
431
|
+
summaryText: longSummary,
|
|
357
432
|
});
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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);
|
|
361
437
|
});
|
|
362
438
|
it('will not attempt to compress context after a failure', async () => {
|
|
363
|
-
const
|
|
364
|
-
|
|
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
|
|
365
447
|
const result = await client.tryCompressChat('prompt-id-5', false);
|
|
366
|
-
//
|
|
367
|
-
expect(
|
|
448
|
+
// generateContent (for summary) should only have been called once
|
|
449
|
+
expect(mockGenerateContentFn).toHaveBeenCalledTimes(1);
|
|
368
450
|
expect(result).toEqual({
|
|
369
451
|
compressionStatus: CompressionStatus.NOOP,
|
|
370
452
|
newTokenCount: 0,
|
|
@@ -378,43 +460,80 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
378
460
|
mockGetHistory.mockReturnValue([
|
|
379
461
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
380
462
|
]);
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
});
|
|
463
|
+
const originalTokenCount = MOCKED_TOKEN_LIMIT * 0.699;
|
|
464
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
384
465
|
const initialChat = client.getChat();
|
|
385
466
|
const result = await client.tryCompressChat('prompt-id-2', false);
|
|
386
467
|
const newChat = client.getChat();
|
|
387
468
|
expect(tokenLimit).toHaveBeenCalled();
|
|
388
469
|
expect(result).toEqual({
|
|
389
470
|
compressionStatus: CompressionStatus.NOOP,
|
|
390
|
-
newTokenCount:
|
|
391
|
-
originalTokenCount
|
|
471
|
+
newTokenCount: originalTokenCount,
|
|
472
|
+
originalTokenCount,
|
|
392
473
|
});
|
|
393
474
|
expect(newChat).toBe(initialChat);
|
|
394
475
|
});
|
|
476
|
+
it('should return NOOP if history is too short to compress', async () => {
|
|
477
|
+
const { client } = setup({
|
|
478
|
+
chatHistory: [{ role: 'user', parts: [{ text: 'hi' }] }],
|
|
479
|
+
originalTokenCount: 50,
|
|
480
|
+
});
|
|
481
|
+
const result = await client.tryCompressChat('prompt-id-noop', false);
|
|
482
|
+
expect(result).toEqual({
|
|
483
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
484
|
+
originalTokenCount: 50,
|
|
485
|
+
newTokenCount: 50,
|
|
486
|
+
});
|
|
487
|
+
expect(mockGenerateContentFn).not.toHaveBeenCalled();
|
|
488
|
+
});
|
|
395
489
|
it('logs a telemetry event when compressing', async () => {
|
|
396
490
|
vi.spyOn(ClearcutLogger.prototype, 'logChatCompressionEvent');
|
|
397
491
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
398
492
|
const MOCKED_CONTEXT_PERCENTAGE_THRESHOLD = 0.5;
|
|
399
|
-
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
400
493
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
401
494
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
402
495
|
});
|
|
403
|
-
|
|
496
|
+
const history = [
|
|
404
497
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
405
|
-
|
|
498
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
499
|
+
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
500
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
501
|
+
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
502
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
503
|
+
];
|
|
504
|
+
mockGetHistory.mockReturnValue(history);
|
|
406
505
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
506
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
507
|
+
// We need to control the estimated new token count.
|
|
508
|
+
// We mock startChat to return a chat with a known history.
|
|
509
|
+
const summaryText = 'This is a summary.';
|
|
510
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
511
|
+
const historyToKeep = history.slice(splitPoint);
|
|
512
|
+
const newCompressedHistory = [
|
|
513
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
514
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
515
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
516
|
+
{
|
|
517
|
+
role: 'model',
|
|
518
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
519
|
+
},
|
|
520
|
+
...historyToKeep,
|
|
521
|
+
];
|
|
522
|
+
const mockNewChat = {
|
|
523
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
524
|
+
};
|
|
525
|
+
client['startChat'] = vi
|
|
526
|
+
.fn()
|
|
527
|
+
.mockResolvedValue(mockNewChat);
|
|
528
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
529
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
411
530
|
// Mock the summary response from the chat
|
|
412
531
|
mockGenerateContentFn.mockResolvedValue({
|
|
413
532
|
candidates: [
|
|
414
533
|
{
|
|
415
534
|
content: {
|
|
416
535
|
role: 'model',
|
|
417
|
-
parts: [{ text:
|
|
536
|
+
parts: [{ text: summaryText }],
|
|
418
537
|
},
|
|
419
538
|
},
|
|
420
539
|
],
|
|
@@ -424,6 +543,8 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
424
543
|
tokens_before: originalTokenCount,
|
|
425
544
|
tokens_after: newTokenCount,
|
|
426
545
|
}));
|
|
546
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledWith(newTokenCount);
|
|
547
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledTimes(1);
|
|
427
548
|
});
|
|
428
549
|
it('should trigger summarization if token count is at threshold with contextPercentageThreshold setting', async () => {
|
|
429
550
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
@@ -432,21 +553,46 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
432
553
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
433
554
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
434
555
|
});
|
|
435
|
-
|
|
556
|
+
const history = [
|
|
436
557
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
437
|
-
|
|
558
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
559
|
+
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
560
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
561
|
+
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
562
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
563
|
+
];
|
|
564
|
+
mockGetHistory.mockReturnValue(history);
|
|
438
565
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
566
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
567
|
+
// Mock summary and new chat
|
|
568
|
+
const summaryText = 'This is a summary.';
|
|
569
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
570
|
+
const historyToKeep = history.slice(splitPoint);
|
|
571
|
+
const newCompressedHistory = [
|
|
572
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
573
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
574
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
575
|
+
{
|
|
576
|
+
role: 'model',
|
|
577
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
578
|
+
},
|
|
579
|
+
...historyToKeep,
|
|
580
|
+
];
|
|
581
|
+
const mockNewChat = {
|
|
582
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
583
|
+
};
|
|
584
|
+
client['startChat'] = vi
|
|
585
|
+
.fn()
|
|
586
|
+
.mockResolvedValue(mockNewChat);
|
|
587
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
588
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
443
589
|
// Mock the summary response from the chat
|
|
444
590
|
mockGenerateContentFn.mockResolvedValue({
|
|
445
591
|
candidates: [
|
|
446
592
|
{
|
|
447
593
|
content: {
|
|
448
594
|
role: 'model',
|
|
449
|
-
parts: [{ text:
|
|
595
|
+
parts: [{ text: summaryText }],
|
|
450
596
|
},
|
|
451
597
|
},
|
|
452
598
|
],
|
|
@@ -468,7 +614,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
468
614
|
it('should not compress across a function call response', async () => {
|
|
469
615
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
470
616
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
471
|
-
|
|
617
|
+
const history = [
|
|
472
618
|
{ role: 'user', parts: [{ text: '...history 1...' }] },
|
|
473
619
|
{ role: 'model', parts: [{ text: '...history 2...' }] },
|
|
474
620
|
{ role: 'user', parts: [{ text: '...history 3...' }] },
|
|
@@ -485,19 +631,43 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
485
631
|
{ role: 'model', parts: [{ text: '...history 10...' }] },
|
|
486
632
|
// Instead we will break here.
|
|
487
633
|
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
488
|
-
]
|
|
634
|
+
];
|
|
635
|
+
mockGetHistory.mockReturnValue(history);
|
|
489
636
|
const originalTokenCount = 1000 * 0.7;
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
637
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
638
|
+
// Mock summary and new chat
|
|
639
|
+
const summaryText = 'This is a summary.';
|
|
640
|
+
const splitPoint = findCompressSplitPoint(history, 0.7); // This should be 10
|
|
641
|
+
expect(splitPoint).toBe(10); // Verify split point logic
|
|
642
|
+
const historyToKeep = history.slice(splitPoint); // Should keep last user message
|
|
643
|
+
expect(historyToKeep).toEqual([
|
|
644
|
+
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
645
|
+
]);
|
|
646
|
+
const newCompressedHistory = [
|
|
647
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
648
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
649
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
650
|
+
{
|
|
651
|
+
role: 'model',
|
|
652
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
653
|
+
},
|
|
654
|
+
...historyToKeep,
|
|
655
|
+
];
|
|
656
|
+
const mockNewChat = {
|
|
657
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
658
|
+
};
|
|
659
|
+
client['startChat'] = vi
|
|
660
|
+
.fn()
|
|
661
|
+
.mockResolvedValue(mockNewChat);
|
|
662
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
663
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
494
664
|
// Mock the summary response from the chat
|
|
495
665
|
mockGenerateContentFn.mockResolvedValue({
|
|
496
666
|
candidates: [
|
|
497
667
|
{
|
|
498
668
|
content: {
|
|
499
669
|
role: 'model',
|
|
500
|
-
parts: [{ text:
|
|
670
|
+
parts: [{ text: summaryText }],
|
|
501
671
|
},
|
|
502
672
|
},
|
|
503
673
|
],
|
|
@@ -515,35 +685,60 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
515
685
|
});
|
|
516
686
|
// Assert that the chat was reset
|
|
517
687
|
expect(newChat).not.toBe(initialChat);
|
|
518
|
-
// 1. standard start context message
|
|
519
|
-
// 2. standard canned
|
|
520
|
-
// 3. compressed summary message
|
|
521
|
-
// 4. standard canned
|
|
522
|
-
// 5. The last user message (
|
|
688
|
+
// 1. standard start context message (env)
|
|
689
|
+
// 2. standard canned model response
|
|
690
|
+
// 3. compressed summary message (user)
|
|
691
|
+
// 4. standard canned model response
|
|
692
|
+
// 5. The last user message (historyToKeep)
|
|
523
693
|
expect(newChat.getHistory().length).toEqual(5);
|
|
524
694
|
});
|
|
525
695
|
it('should always trigger summarization when force is true, regardless of token count', async () => {
|
|
526
|
-
|
|
696
|
+
const history = [
|
|
527
697
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
698
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
699
|
+
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
700
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
701
|
+
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
702
|
+
{ role: 'model', parts: [{ text: '...history...' }] },
|
|
703
|
+
];
|
|
704
|
+
mockGetHistory.mockReturnValue(history);
|
|
705
|
+
const originalTokenCount = 100; // Well below threshold, but > estimated new count
|
|
706
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
707
|
+
// Mock summary and new chat
|
|
708
|
+
const summaryText = 'This is a summary.';
|
|
709
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
710
|
+
const historyToKeep = history.slice(splitPoint);
|
|
711
|
+
const newCompressedHistory = [
|
|
712
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
713
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
714
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
715
|
+
{
|
|
716
|
+
role: 'model',
|
|
717
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
718
|
+
},
|
|
719
|
+
...historyToKeep,
|
|
720
|
+
];
|
|
721
|
+
const mockNewChat = {
|
|
722
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
723
|
+
};
|
|
724
|
+
client['startChat'] = vi
|
|
725
|
+
.fn()
|
|
726
|
+
.mockResolvedValue(mockNewChat);
|
|
727
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
728
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
534
729
|
// Mock the summary response from the chat
|
|
535
730
|
mockGenerateContentFn.mockResolvedValue({
|
|
536
731
|
candidates: [
|
|
537
732
|
{
|
|
538
733
|
content: {
|
|
539
734
|
role: 'model',
|
|
540
|
-
parts: [{ text:
|
|
735
|
+
parts: [{ text: summaryText }],
|
|
541
736
|
},
|
|
542
737
|
},
|
|
543
738
|
],
|
|
544
739
|
});
|
|
545
740
|
const initialChat = client.getChat();
|
|
546
|
-
const result = await client.tryCompressChat('prompt-id-1',
|
|
741
|
+
const result = await client.tryCompressChat('prompt-id-1', true); // force = true
|
|
547
742
|
const newChat = client.getChat();
|
|
548
743
|
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
549
744
|
expect(result).toEqual({
|
|
@@ -581,9 +776,6 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
581
776
|
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
582
777
|
},
|
|
583
778
|
{ compressionStatus: CompressionStatus.NOOP },
|
|
584
|
-
{
|
|
585
|
-
compressionStatus: CompressionStatus.COMPRESSION_FAILED_TOKEN_COUNT_ERROR,
|
|
586
|
-
},
|
|
587
779
|
])('does not emit a compression event when the status is $compressionStatus', async ({ compressionStatus }) => {
|
|
588
780
|
// Arrange
|
|
589
781
|
const mockStream = (async function* () {
|
|
@@ -841,6 +1033,7 @@ ${JSON.stringify({
|
|
|
841
1033
|
expect(finalResult).toBeInstanceOf(Turn);
|
|
842
1034
|
});
|
|
843
1035
|
it('should stop infinite loop after MAX_TURNS when nextSpeaker always returns model', async () => {
|
|
1036
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(true);
|
|
844
1037
|
// Get the mocked checkNextSpeaker function and configure it to trigger infinite loop
|
|
845
1038
|
const { checkNextSpeaker } = await import('../utils/nextSpeakerChecker.js');
|
|
846
1039
|
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
|
|
@@ -996,6 +1189,82 @@ ${JSON.stringify({
|
|
|
996
1189
|
console.log(`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
|
|
997
1190
|
`${eventCount} events generated (properly bounded by MAX_TURNS)`);
|
|
998
1191
|
});
|
|
1192
|
+
it('should yield ContextWindowWillOverflow when the context window is about to overflow', async () => {
|
|
1193
|
+
// Arrange
|
|
1194
|
+
const MOCKED_TOKEN_LIMIT = 1000;
|
|
1195
|
+
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
1196
|
+
// Set last prompt token count
|
|
1197
|
+
const lastPromptTokenCount = 900;
|
|
1198
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(lastPromptTokenCount);
|
|
1199
|
+
// Remaining = 100. Threshold (95%) = 95.
|
|
1200
|
+
// We need a request > 95 tokens.
|
|
1201
|
+
// A string of length 400 is roughly 100 tokens.
|
|
1202
|
+
const longText = 'a'.repeat(400);
|
|
1203
|
+
const request = [{ text: longText }];
|
|
1204
|
+
const estimatedRequestTokenCount = Math.floor(JSON.stringify(request).length / 4);
|
|
1205
|
+
const remainingTokenCount = MOCKED_TOKEN_LIMIT - lastPromptTokenCount;
|
|
1206
|
+
// Mock tryCompressChat to not compress
|
|
1207
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
1208
|
+
originalTokenCount: lastPromptTokenCount,
|
|
1209
|
+
newTokenCount: lastPromptTokenCount,
|
|
1210
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
1211
|
+
});
|
|
1212
|
+
// Act
|
|
1213
|
+
const stream = client.sendMessageStream(request, new AbortController().signal, 'prompt-id-overflow');
|
|
1214
|
+
const events = await fromAsync(stream);
|
|
1215
|
+
// Assert
|
|
1216
|
+
expect(events).toContainEqual({
|
|
1217
|
+
type: GeminiEventType.ContextWindowWillOverflow,
|
|
1218
|
+
value: {
|
|
1219
|
+
estimatedRequestTokenCount,
|
|
1220
|
+
remainingTokenCount,
|
|
1221
|
+
},
|
|
1222
|
+
});
|
|
1223
|
+
// Ensure turn.run is not called
|
|
1224
|
+
expect(mockTurnRunFn).not.toHaveBeenCalled();
|
|
1225
|
+
});
|
|
1226
|
+
it("should use the sticky model's token limit for the overflow check", async () => {
|
|
1227
|
+
// Arrange
|
|
1228
|
+
const STICKY_MODEL = 'gemini-1.5-flash';
|
|
1229
|
+
const STICKY_MODEL_LIMIT = 1000;
|
|
1230
|
+
const CONFIG_MODEL_LIMIT = 2000;
|
|
1231
|
+
// Set up token limits
|
|
1232
|
+
vi.mocked(tokenLimit).mockImplementation((model) => {
|
|
1233
|
+
if (model === STICKY_MODEL)
|
|
1234
|
+
return STICKY_MODEL_LIMIT;
|
|
1235
|
+
return CONFIG_MODEL_LIMIT;
|
|
1236
|
+
});
|
|
1237
|
+
// Set the sticky model
|
|
1238
|
+
client['currentSequenceModel'] = STICKY_MODEL;
|
|
1239
|
+
// Set token count
|
|
1240
|
+
const lastPromptTokenCount = 900;
|
|
1241
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(lastPromptTokenCount);
|
|
1242
|
+
// Remaining (sticky) = 100. Threshold (95%) = 95.
|
|
1243
|
+
// We need a request > 95 tokens.
|
|
1244
|
+
const longText = 'a'.repeat(400);
|
|
1245
|
+
const request = [{ text: longText }];
|
|
1246
|
+
const estimatedRequestTokenCount = Math.floor(JSON.stringify(request).length / 4);
|
|
1247
|
+
const remainingTokenCount = STICKY_MODEL_LIMIT - lastPromptTokenCount;
|
|
1248
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
1249
|
+
originalTokenCount: lastPromptTokenCount,
|
|
1250
|
+
newTokenCount: lastPromptTokenCount,
|
|
1251
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
1252
|
+
});
|
|
1253
|
+
// Act
|
|
1254
|
+
const stream = client.sendMessageStream(request, new AbortController().signal, 'test-session-id');
|
|
1255
|
+
const events = await fromAsync(stream);
|
|
1256
|
+
// Assert
|
|
1257
|
+
// Should overflow based on the sticky model's limit
|
|
1258
|
+
expect(events).toContainEqual({
|
|
1259
|
+
type: GeminiEventType.ContextWindowWillOverflow,
|
|
1260
|
+
value: {
|
|
1261
|
+
estimatedRequestTokenCount,
|
|
1262
|
+
remainingTokenCount,
|
|
1263
|
+
},
|
|
1264
|
+
});
|
|
1265
|
+
expect(tokenLimit).toHaveBeenCalledWith(STICKY_MODEL);
|
|
1266
|
+
expect(mockTurnRunFn).not.toHaveBeenCalled();
|
|
1267
|
+
});
|
|
999
1268
|
describe('Model Routing', () => {
|
|
1000
1269
|
let mockRouterService;
|
|
1001
1270
|
beforeEach(() => {
|
|
@@ -1081,6 +1350,89 @@ ${JSON.stringify({
|
|
|
1081
1350
|
[{ text: 'Continue' }], expect.any(Object));
|
|
1082
1351
|
});
|
|
1083
1352
|
});
|
|
1353
|
+
it('should recursively call sendMessageStream with "Please continue." when InvalidStream event is received', async () => {
|
|
1354
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(true);
|
|
1355
|
+
// Arrange
|
|
1356
|
+
const mockStream1 = (async function* () {
|
|
1357
|
+
yield { type: GeminiEventType.InvalidStream };
|
|
1358
|
+
})();
|
|
1359
|
+
const mockStream2 = (async function* () {
|
|
1360
|
+
yield { type: GeminiEventType.Content, value: 'Continued content' };
|
|
1361
|
+
})();
|
|
1362
|
+
mockTurnRunFn
|
|
1363
|
+
.mockReturnValueOnce(mockStream1)
|
|
1364
|
+
.mockReturnValueOnce(mockStream2);
|
|
1365
|
+
const mockChat = {
|
|
1366
|
+
addHistory: vi.fn(),
|
|
1367
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1368
|
+
};
|
|
1369
|
+
client['chat'] = mockChat;
|
|
1370
|
+
const initialRequest = [{ text: 'Hi' }];
|
|
1371
|
+
const promptId = 'prompt-id-invalid-stream';
|
|
1372
|
+
const signal = new AbortController().signal;
|
|
1373
|
+
// Act
|
|
1374
|
+
const stream = client.sendMessageStream(initialRequest, signal, promptId);
|
|
1375
|
+
const events = await fromAsync(stream);
|
|
1376
|
+
// Assert
|
|
1377
|
+
expect(events).toEqual([
|
|
1378
|
+
{ type: GeminiEventType.InvalidStream },
|
|
1379
|
+
{ type: GeminiEventType.Content, value: 'Continued content' },
|
|
1380
|
+
]);
|
|
1381
|
+
// Verify that turn.run was called twice
|
|
1382
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(2);
|
|
1383
|
+
// First call with original request
|
|
1384
|
+
expect(mockTurnRunFn).toHaveBeenNthCalledWith(1, expect.any(String), initialRequest, expect.any(Object));
|
|
1385
|
+
// Second call with "Please continue."
|
|
1386
|
+
expect(mockTurnRunFn).toHaveBeenNthCalledWith(2, expect.any(String), [{ text: 'System: Please continue.' }], expect.any(Object));
|
|
1387
|
+
});
|
|
1388
|
+
it('should not recursively call sendMessageStream with "Please continue." when InvalidStream event is received and flag is false', async () => {
|
|
1389
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(false);
|
|
1390
|
+
// Arrange
|
|
1391
|
+
const mockStream1 = (async function* () {
|
|
1392
|
+
yield { type: GeminiEventType.InvalidStream };
|
|
1393
|
+
})();
|
|
1394
|
+
mockTurnRunFn.mockReturnValueOnce(mockStream1);
|
|
1395
|
+
const mockChat = {
|
|
1396
|
+
addHistory: vi.fn(),
|
|
1397
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1398
|
+
};
|
|
1399
|
+
client['chat'] = mockChat;
|
|
1400
|
+
const initialRequest = [{ text: 'Hi' }];
|
|
1401
|
+
const promptId = 'prompt-id-invalid-stream';
|
|
1402
|
+
const signal = new AbortController().signal;
|
|
1403
|
+
// Act
|
|
1404
|
+
const stream = client.sendMessageStream(initialRequest, signal, promptId);
|
|
1405
|
+
const events = await fromAsync(stream);
|
|
1406
|
+
// Assert
|
|
1407
|
+
expect(events).toEqual([{ type: GeminiEventType.InvalidStream }]);
|
|
1408
|
+
// Verify that turn.run was called only once
|
|
1409
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(1);
|
|
1410
|
+
});
|
|
1411
|
+
it('should stop recursing after one retry when InvalidStream events are repeatedly received', async () => {
|
|
1412
|
+
vi.spyOn(client['config'], 'getContinueOnFailedApiCall').mockReturnValue(true);
|
|
1413
|
+
// Arrange
|
|
1414
|
+
// Always return a new invalid stream
|
|
1415
|
+
mockTurnRunFn.mockImplementation(() => (async function* () {
|
|
1416
|
+
yield { type: GeminiEventType.InvalidStream };
|
|
1417
|
+
})());
|
|
1418
|
+
const mockChat = {
|
|
1419
|
+
addHistory: vi.fn(),
|
|
1420
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1421
|
+
};
|
|
1422
|
+
client['chat'] = mockChat;
|
|
1423
|
+
const initialRequest = [{ text: 'Hi' }];
|
|
1424
|
+
const promptId = 'prompt-id-infinite-invalid-stream';
|
|
1425
|
+
const signal = new AbortController().signal;
|
|
1426
|
+
// Act
|
|
1427
|
+
const stream = client.sendMessageStream(initialRequest, signal, promptId);
|
|
1428
|
+
const events = await fromAsync(stream);
|
|
1429
|
+
// Assert
|
|
1430
|
+
// We expect 2 InvalidStream events (original + 1 retry)
|
|
1431
|
+
expect(events.length).toBe(2);
|
|
1432
|
+
expect(events.every((e) => e.type === GeminiEventType.InvalidStream)).toBe(true);
|
|
1433
|
+
// Verify that turn.run was called twice
|
|
1434
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(2);
|
|
1435
|
+
});
|
|
1084
1436
|
describe('Editor context delta', () => {
|
|
1085
1437
|
const mockStream = (async function* () {
|
|
1086
1438
|
yield { type: 'content', value: 'Hello' };
|
|
@@ -1649,7 +2001,7 @@ ${JSON.stringify({
|
|
|
1649
2001
|
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1650
2002
|
config: {
|
|
1651
2003
|
abortSignal,
|
|
1652
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
2004
|
+
systemInstruction: getCoreSystemPrompt({}, ''),
|
|
1653
2005
|
temperature: 0.5,
|
|
1654
2006
|
topP: 1,
|
|
1655
2007
|
},
|