@vybestack/llxprt-code-core 0.1.19-beta → 0.1.19-gamma
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 +49 -2
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/src/auth/anthropic-device-flow.d.ts +53 -0
- package/dist/src/auth/anthropic-device-flow.js +208 -0
- package/dist/src/auth/anthropic-device-flow.js.map +1 -0
- package/dist/src/auth/precedence.d.ts +55 -0
- package/dist/src/auth/precedence.js +211 -0
- package/dist/src/auth/precedence.js.map +1 -0
- package/dist/src/auth/qwen-device-flow.d.ts +45 -0
- package/dist/src/auth/qwen-device-flow.js +179 -0
- package/dist/src/auth/qwen-device-flow.js.map +1 -0
- package/dist/src/auth/token-store.d.ts +66 -0
- package/dist/src/auth/token-store.js +151 -0
- package/dist/src/auth/token-store.js.map +1 -0
- package/dist/src/auth/types.d.ts +130 -0
- package/dist/src/auth/types.js +60 -0
- package/dist/src/auth/types.js.map +1 -0
- package/dist/src/config/config.d.ts +7 -1
- package/dist/src/config/config.js +42 -4
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/endpoints.d.ts +60 -0
- package/dist/src/config/endpoints.js +126 -0
- package/dist/src/config/endpoints.js.map +1 -0
- package/dist/src/config/profileManager.d.ts +14 -4
- package/dist/src/config/profileManager.js +90 -11
- package/dist/src/config/profileManager.js.map +1 -1
- package/dist/src/core/client.js +9 -12
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +4 -1
- package/dist/src/core/contentGenerator.js +3 -0
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/subagent.js +12 -10
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/index.d.ts +9 -0
- package/dist/src/index.js +9 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/providers/BaseProvider.d.ts +149 -0
- package/dist/src/providers/BaseProvider.js +315 -0
- package/dist/src/providers/BaseProvider.js.map +1 -0
- package/dist/src/providers/IProvider.d.ts +1 -0
- package/dist/src/providers/LoggingProviderWrapper.d.ts +1 -0
- package/dist/src/providers/LoggingProviderWrapper.js +3 -0
- package/dist/src/providers/LoggingProviderWrapper.js.map +1 -1
- package/dist/src/providers/ProviderManager.d.ts +0 -1
- package/dist/src/providers/ProviderManager.js +23 -14
- package/dist/src/providers/ProviderManager.js.map +1 -1
- package/dist/src/providers/anthropic/AnthropicProvider.d.ts +20 -6
- package/dist/src/providers/anthropic/AnthropicProvider.js +172 -26
- package/dist/src/providers/anthropic/AnthropicProvider.js.map +1 -1
- package/dist/src/providers/gemini/GeminiProvider.d.ts +16 -7
- package/dist/src/providers/gemini/GeminiProvider.js +163 -148
- package/dist/src/providers/gemini/GeminiProvider.js.map +1 -1
- package/dist/src/providers/openai/OpenAIProvider.d.ts +53 -6
- package/dist/src/providers/openai/OpenAIProvider.js +373 -40
- package/dist/src/providers/openai/OpenAIProvider.js.map +1 -1
- package/dist/src/providers/openai/RESPONSES_API_MODELS.d.ts +1 -1
- package/dist/src/providers/openai/RESPONSES_API_MODELS.js +1 -0
- package/dist/src/providers/openai/RESPONSES_API_MODELS.js.map +1 -1
- package/dist/src/providers/openai/syntheticToolResponses.d.ts +52 -0
- package/dist/src/providers/openai/syntheticToolResponses.js +129 -0
- package/dist/src/providers/openai/syntheticToolResponses.js.map +1 -0
- package/dist/src/settings/SettingsService.d.ts +32 -0
- package/dist/src/settings/SettingsService.js +204 -0
- package/dist/src/settings/SettingsService.js.map +1 -0
- package/dist/src/settings/settingsServiceInstance.d.ts +12 -0
- package/dist/src/settings/settingsServiceInstance.js +24 -0
- package/dist/src/settings/settingsServiceInstance.js.map +1 -0
- package/dist/src/settings/types.d.ts +141 -0
- package/dist/src/settings/types.js +5 -0
- package/dist/src/settings/types.js.map +1 -0
- package/package.json +4 -2
- package/dist/src/code_assist/converter.test.d.ts +0 -6
- package/dist/src/code_assist/converter.test.js +0 -279
- package/dist/src/code_assist/converter.test.js.map +0 -1
- package/dist/src/code_assist/oauth2.test.d.ts +0 -6
- package/dist/src/code_assist/oauth2.test.js +0 -370
- package/dist/src/code_assist/oauth2.test.js.map +0 -1
- package/dist/src/code_assist/server.test.d.ts +0 -6
- package/dist/src/code_assist/server.test.js +0 -134
- package/dist/src/code_assist/server.test.js.map +0 -1
- package/dist/src/code_assist/setup.test.d.ts +0 -6
- package/dist/src/code_assist/setup.test.js +0 -65
- package/dist/src/code_assist/setup.test.js.map +0 -1
- package/dist/src/config/config.alwaysAllow.test.d.ts +0 -6
- package/dist/src/config/config.alwaysAllow.test.js +0 -84
- package/dist/src/config/config.alwaysAllow.test.js.map +0 -1
- package/dist/src/config/config.ephemeral.test.d.ts +0 -6
- package/dist/src/config/config.ephemeral.test.js +0 -152
- package/dist/src/config/config.ephemeral.test.js.map +0 -1
- package/dist/src/config/config.test.d.ts +0 -6
- package/dist/src/config/config.test.js +0 -369
- package/dist/src/config/config.test.js.map +0 -1
- package/dist/src/config/flashFallback.test.d.ts +0 -6
- package/dist/src/config/flashFallback.test.js +0 -91
- package/dist/src/config/flashFallback.test.js.map +0 -1
- package/dist/src/core/client.test.d.ts +0 -6
- package/dist/src/core/client.test.js +0 -1322
- package/dist/src/core/client.test.js.map +0 -1
- package/dist/src/core/contentGenerator.test.d.ts +0 -6
- package/dist/src/core/contentGenerator.test.js +0 -103
- package/dist/src/core/contentGenerator.test.js.map +0 -1
- package/dist/src/core/coreToolScheduler.test.d.ts +0 -6
- package/dist/src/core/coreToolScheduler.test.js +0 -637
- package/dist/src/core/coreToolScheduler.test.js.map +0 -1
- package/dist/src/core/geminiChat.test.d.ts +0 -6
- package/dist/src/core/geminiChat.test.js +0 -425
- package/dist/src/core/geminiChat.test.js.map +0 -1
- package/dist/src/core/googleGenAIWrapper.test.d.ts +0 -6
- package/dist/src/core/googleGenAIWrapper.test.js +0 -104
- package/dist/src/core/googleGenAIWrapper.test.js.map +0 -1
- package/dist/src/core/logger.test.d.ts +0 -6
- package/dist/src/core/logger.test.js +0 -467
- package/dist/src/core/logger.test.js.map +0 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.d.ts +0 -6
- package/dist/src/core/nonInteractiveToolExecutor.test.js +0 -165
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +0 -1
- package/dist/src/core/prompts-async.test.d.ts +0 -6
- package/dist/src/core/prompts-async.test.js +0 -115
- package/dist/src/core/prompts-async.test.js.map +0 -1
- package/dist/src/core/prompts.test.d.ts +0 -6
- package/dist/src/core/prompts.test.js +0 -68
- package/dist/src/core/prompts.test.js.map +0 -1
- package/dist/src/core/subagent.test.d.ts +0 -6
- package/dist/src/core/subagent.test.js +0 -519
- package/dist/src/core/subagent.test.js.map +0 -1
- package/dist/src/core/tokenLimits.test.d.ts +0 -6
- package/dist/src/core/tokenLimits.test.js +0 -66
- package/dist/src/core/tokenLimits.test.js.map +0 -1
- package/dist/src/core/turn.test.d.ts +0 -6
- package/dist/src/core/turn.test.js +0 -366
- package/dist/src/core/turn.test.js.map +0 -1
- package/dist/src/hooks/tool-render-suppression-hook.test.d.ts +0 -6
- package/dist/src/hooks/tool-render-suppression-hook.test.js +0 -59
- package/dist/src/hooks/tool-render-suppression-hook.test.js.map +0 -1
- package/dist/src/ide/ide-installer.test.d.ts +0 -6
- package/dist/src/ide/ide-installer.test.js +0 -55
- package/dist/src/ide/ide-installer.test.js.map +0 -1
- package/dist/src/ide/ideContext.test.d.ts +0 -6
- package/dist/src/ide/ideContext.test.js +0 -265
- package/dist/src/ide/ideContext.test.js.map +0 -1
- package/dist/src/index.test.d.ts +0 -6
- package/dist/src/index.test.js +0 -12
- package/dist/src/index.test.js.map +0 -1
- package/dist/src/integration-tests/todo-system.test.d.ts +0 -6
- package/dist/src/integration-tests/todo-system.test.js +0 -46
- package/dist/src/integration-tests/todo-system.test.js.map +0 -1
- package/dist/src/mcp/google-auth-provider.test.d.ts +0 -6
- package/dist/src/mcp/google-auth-provider.test.js +0 -54
- package/dist/src/mcp/google-auth-provider.test.js.map +0 -1
- package/dist/src/mcp/oauth-provider.test.d.ts +0 -6
- package/dist/src/mcp/oauth-provider.test.js +0 -602
- package/dist/src/mcp/oauth-provider.test.js.map +0 -1
- package/dist/src/mcp/oauth-token-storage.test.d.ts +0 -6
- package/dist/src/mcp/oauth-token-storage.test.js +0 -205
- package/dist/src/mcp/oauth-token-storage.test.js.map +0 -1
- package/dist/src/mcp/oauth-utils.test.d.ts +0 -6
- package/dist/src/mcp/oauth-utils.test.js +0 -144
- package/dist/src/mcp/oauth-utils.test.js.map +0 -1
- package/dist/src/parsers/TextToolCallParser.multibyte.test.d.ts +0 -1
- package/dist/src/parsers/TextToolCallParser.multibyte.test.js +0 -42
- package/dist/src/parsers/TextToolCallParser.multibyte.test.js.map +0 -1
- package/dist/src/parsers/TextToolCallParser.test.d.ts +0 -1
- package/dist/src/parsers/TextToolCallParser.test.js +0 -225
- package/dist/src/parsers/TextToolCallParser.test.js.map +0 -1
- package/dist/src/prompt-config/TemplateEngine.test.d.ts +0 -1
- package/dist/src/prompt-config/TemplateEngine.test.js +0 -494
- package/dist/src/prompt-config/TemplateEngine.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-cache.test.d.ts +0 -6
- package/dist/src/prompt-config/prompt-cache.test.js +0 -437
- package/dist/src/prompt-config/prompt-cache.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-installer.test.d.ts +0 -7
- package/dist/src/prompt-config/prompt-installer.test.js +0 -503
- package/dist/src/prompt-config/prompt-installer.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-loader.test.d.ts +0 -5
- package/dist/src/prompt-config/prompt-loader.test.js +0 -413
- package/dist/src/prompt-config/prompt-loader.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-resolver.test.d.ts +0 -1
- package/dist/src/prompt-config/prompt-resolver.test.js +0 -529
- package/dist/src/prompt-config/prompt-resolver.test.js.map +0 -1
- package/dist/src/prompt-config/prompt-service.test.d.ts +0 -1
- package/dist/src/prompt-config/prompt-service.test.js +0 -811
- package/dist/src/prompt-config/prompt-service.test.js.map +0 -1
- package/dist/src/providers/ProviderManager.gemini-switch.test.d.ts +0 -6
- package/dist/src/providers/ProviderManager.gemini-switch.test.js +0 -57
- package/dist/src/providers/ProviderManager.gemini-switch.test.js.map +0 -1
- package/dist/src/providers/ProviderManager.test.d.ts +0 -6
- package/dist/src/providers/ProviderManager.test.js +0 -284
- package/dist/src/providers/ProviderManager.test.js.map +0 -1
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.d.ts +0 -6
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js +0 -273
- package/dist/src/providers/adapters/GeminiCompatibleWrapper.test.js.map +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.modelParams.test.d.ts +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.modelParams.test.js +0 -48
- package/dist/src/providers/anthropic/AnthropicProvider.modelParams.test.js.map +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.test.d.ts +0 -1
- package/dist/src/providers/anthropic/AnthropicProvider.test.js +0 -487
- package/dist/src/providers/anthropic/AnthropicProvider.test.js.map +0 -1
- package/dist/src/providers/gemini/GeminiProvider.integration.test.d.ts +0 -6
- package/dist/src/providers/gemini/GeminiProvider.integration.test.js +0 -126
- package/dist/src/providers/gemini/GeminiProvider.integration.test.js.map +0 -1
- package/dist/src/providers/gemini/GeminiProvider.test.d.ts +0 -6
- package/dist/src/providers/gemini/GeminiProvider.test.js +0 -136
- package/dist/src/providers/gemini/GeminiProvider.test.js.map +0 -1
- package/dist/src/providers/integration/multi-provider.integration.test.d.ts +0 -6
- package/dist/src/providers/integration/multi-provider.integration.test.js +0 -292
- package/dist/src/providers/integration/multi-provider.integration.test.js.map +0 -1
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.d.ts +0 -1
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.js +0 -97
- package/dist/src/providers/openai/ConversationCache.accumTokens.test.js.map +0 -1
- package/dist/src/providers/openai/ConversationCache.test.d.ts +0 -1
- package/dist/src/providers/openai/ConversationCache.test.js +0 -113
- package/dist/src/providers/openai/ConversationCache.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.js +0 -189
- package/dist/src/providers/openai/OpenAIProvider.callResponses.stateless.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.integration.test.d.ts +0 -6
- package/dist/src/providers/openai/OpenAIProvider.integration.test.js +0 -125
- package/dist/src/providers/openai/OpenAIProvider.integration.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responses.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responses.test.js +0 -350
- package/dist/src/providers/openai/OpenAIProvider.responses.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.js +0 -213
- package/dist/src/providers/openai/OpenAIProvider.responsesIntegration.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.js +0 -59
- package/dist/src/providers/openai/OpenAIProvider.shouldUseResponses.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.d.ts +0 -6
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.js +0 -105
- package/dist/src/providers/openai/OpenAIProvider.stateful.integration.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.switch.test.d.ts +0 -1
- package/dist/src/providers/openai/OpenAIProvider.switch.test.js +0 -256
- package/dist/src/providers/openai/OpenAIProvider.switch.test.js.map +0 -1
- package/dist/src/providers/openai/OpenAIProvider.test.d.ts +0 -16
- package/dist/src/providers/openai/OpenAIProvider.test.js +0 -620
- package/dist/src/providers/openai/OpenAIProvider.test.js.map +0 -1
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.d.ts +0 -1
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.js +0 -210
- package/dist/src/providers/openai/ResponsesContextTrim.integration.test.js.map +0 -1
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.d.ts +0 -1
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.js +0 -65
- package/dist/src/providers/openai/__tests__/formatArrayResponse.test.js.map +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.d.ts +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.js +0 -129
- package/dist/src/providers/openai/buildResponsesRequest.stripToolCalls.test.js.map +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.test.d.ts +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.test.js +0 -406
- package/dist/src/providers/openai/buildResponsesRequest.test.js.map +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.d.ts +0 -1
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.js +0 -50
- package/dist/src/providers/openai/buildResponsesRequest.undefined.test.js.map +0 -1
- package/dist/src/providers/openai/estimateRemoteTokens.test.d.ts +0 -1
- package/dist/src/providers/openai/estimateRemoteTokens.test.js +0 -125
- package/dist/src/providers/openai/estimateRemoteTokens.test.js.map +0 -1
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.d.ts +0 -1
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.js +0 -192
- package/dist/src/providers/openai/parseResponsesStream.responsesToolCalls.test.js.map +0 -1
- package/dist/src/providers/openai/parseResponsesStream.test.d.ts +0 -1
- package/dist/src/providers/openai/parseResponsesStream.test.js +0 -151
- package/dist/src/providers/openai/parseResponsesStream.test.js.map +0 -1
- package/dist/src/services/fileDiscoveryService.test.d.ts +0 -6
- package/dist/src/services/fileDiscoveryService.test.js +0 -143
- package/dist/src/services/fileDiscoveryService.test.js.map +0 -1
- package/dist/src/services/gitService.test.d.ts +0 -6
- package/dist/src/services/gitService.test.js +0 -209
- package/dist/src/services/gitService.test.js.map +0 -1
- package/dist/src/services/loopDetectionService.test.d.ts +0 -6
- package/dist/src/services/loopDetectionService.test.js +0 -484
- package/dist/src/services/loopDetectionService.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.multibyte.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.multibyte.test.js +0 -72
- package/dist/src/services/shellExecutionService.multibyte.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.test.js +0 -272
- package/dist/src/services/shellExecutionService.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.windows.multibyte.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.windows.multibyte.test.js +0 -98
- package/dist/src/services/shellExecutionService.windows.multibyte.test.js.map +0 -1
- package/dist/src/services/shellExecutionService.windows.test.d.ts +0 -6
- package/dist/src/services/shellExecutionService.windows.test.js +0 -79
- package/dist/src/services/shellExecutionService.windows.test.js.map +0 -1
- package/dist/src/services/tool-call-tracker-service.test.d.ts +0 -6
- package/dist/src/services/tool-call-tracker-service.test.js +0 -99
- package/dist/src/services/tool-call-tracker-service.test.js.map +0 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +0 -6
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +0 -187
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +0 -1
- package/dist/src/telemetry/loggers.test.d.ts +0 -6
- package/dist/src/telemetry/loggers.test.js +0 -573
- package/dist/src/telemetry/loggers.test.js.map +0 -1
- package/dist/src/telemetry/metrics.test.d.ts +0 -6
- package/dist/src/telemetry/metrics.test.js +0 -212
- package/dist/src/telemetry/metrics.test.js.map +0 -1
- package/dist/src/telemetry/telemetry.test.d.ts +0 -6
- package/dist/src/telemetry/telemetry.test.js +0 -54
- package/dist/src/telemetry/telemetry.test.js.map +0 -1
- package/dist/src/telemetry/uiTelemetry.test.d.ts +0 -6
- package/dist/src/telemetry/uiTelemetry.test.js +0 -518
- package/dist/src/telemetry/uiTelemetry.test.js.map +0 -1
- package/dist/src/tools/ToolFormatter.test.d.ts +0 -16
- package/dist/src/tools/ToolFormatter.test.js +0 -349
- package/dist/src/tools/ToolFormatter.test.js.map +0 -1
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.d.ts +0 -1
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.js +0 -241
- package/dist/src/tools/ToolFormatter.toResponsesTool.test.js.map +0 -1
- package/dist/src/tools/diffOptions.test.d.ts +0 -6
- package/dist/src/tools/diffOptions.test.js +0 -119
- package/dist/src/tools/diffOptions.test.js.map +0 -1
- package/dist/src/tools/edit.test.d.ts +0 -6
- package/dist/src/tools/edit.test.js +0 -689
- package/dist/src/tools/edit.test.js.map +0 -1
- package/dist/src/tools/glob.test.d.ts +0 -6
- package/dist/src/tools/glob.test.js +0 -332
- package/dist/src/tools/glob.test.js.map +0 -1
- package/dist/src/tools/grep.test.d.ts +0 -6
- package/dist/src/tools/grep.test.js +0 -272
- package/dist/src/tools/grep.test.js.map +0 -1
- package/dist/src/tools/ls.test.d.ts +0 -6
- package/dist/src/tools/ls.test.js +0 -357
- package/dist/src/tools/ls.test.js.map +0 -1
- package/dist/src/tools/mcp-client.test.d.ts +0 -6
- package/dist/src/tools/mcp-client.test.js +0 -617
- package/dist/src/tools/mcp-client.test.js.map +0 -1
- package/dist/src/tools/mcp-tool.test.d.ts +0 -6
- package/dist/src/tools/mcp-tool.test.js +0 -501
- package/dist/src/tools/mcp-tool.test.js.map +0 -1
- package/dist/src/tools/memoryTool.test.d.ts +0 -6
- package/dist/src/tools/memoryTool.test.js +0 -266
- package/dist/src/tools/memoryTool.test.js.map +0 -1
- package/dist/src/tools/modifiable-tool.test.d.ts +0 -6
- package/dist/src/tools/modifiable-tool.test.js +0 -193
- package/dist/src/tools/modifiable-tool.test.js.map +0 -1
- package/dist/src/tools/read-file.test.d.ts +0 -6
- package/dist/src/tools/read-file.test.js +0 -319
- package/dist/src/tools/read-file.test.js.map +0 -1
- package/dist/src/tools/read-many-files.test.d.ts +0 -6
- package/dist/src/tools/read-many-files.test.js +0 -644
- package/dist/src/tools/read-many-files.test.js.map +0 -1
- package/dist/src/tools/shell.multibyte.test.d.ts +0 -6
- package/dist/src/tools/shell.multibyte.test.js +0 -75
- package/dist/src/tools/shell.multibyte.test.js.map +0 -1
- package/dist/src/tools/shell.test.d.ts +0 -6
- package/dist/src/tools/shell.test.js +0 -367
- package/dist/src/tools/shell.test.js.map +0 -1
- package/dist/src/tools/todo-pause.spec.d.ts +0 -6
- package/dist/src/tools/todo-pause.spec.js +0 -287
- package/dist/src/tools/todo-pause.spec.js.map +0 -1
- package/dist/src/tools/todo-read.test.d.ts +0 -6
- package/dist/src/tools/todo-read.test.js +0 -162
- package/dist/src/tools/todo-read.test.js.map +0 -1
- package/dist/src/tools/todo-schemas.test.d.ts +0 -6
- package/dist/src/tools/todo-schemas.test.js +0 -341
- package/dist/src/tools/todo-schemas.test.js.map +0 -1
- package/dist/src/tools/todo-store.test.d.ts +0 -6
- package/dist/src/tools/todo-store.test.js +0 -169
- package/dist/src/tools/todo-store.test.js.map +0 -1
- package/dist/src/tools/todo-write.test.d.ts +0 -6
- package/dist/src/tools/todo-write.test.js +0 -226
- package/dist/src/tools/todo-write.test.js.map +0 -1
- package/dist/src/tools/tool-registry.test.d.ts +0 -6
- package/dist/src/tools/tool-registry.test.js +0 -468
- package/dist/src/tools/tool-registry.test.js.map +0 -1
- package/dist/src/tools/tools.test.d.ts +0 -6
- package/dist/src/tools/tools.test.js +0 -117
- package/dist/src/tools/tools.test.js.map +0 -1
- package/dist/src/tools/web-fetch.integration.test.d.ts +0 -6
- package/dist/src/tools/web-fetch.integration.test.js +0 -532
- package/dist/src/tools/web-fetch.integration.test.js.map +0 -1
- package/dist/src/tools/web-search.test.d.ts +0 -6
- package/dist/src/tools/web-search.test.js +0 -230
- package/dist/src/tools/web-search.test.js.map +0 -1
- package/dist/src/tools/write-file.test.d.ts +0 -6
- package/dist/src/tools/write-file.test.js +0 -465
- package/dist/src/tools/write-file.test.js.map +0 -1
- package/dist/src/utils/bfsFileSearch.test.d.ts +0 -6
- package/dist/src/utils/bfsFileSearch.test.js +0 -191
- package/dist/src/utils/bfsFileSearch.test.js.map +0 -1
- package/dist/src/utils/editCorrector.test.d.ts +0 -6
- package/dist/src/utils/editCorrector.test.js +0 -564
- package/dist/src/utils/editCorrector.test.js.map +0 -1
- package/dist/src/utils/editor.test.d.ts +0 -6
- package/dist/src/utils/editor.test.js +0 -445
- package/dist/src/utils/editor.test.js.map +0 -1
- package/dist/src/utils/environmentContext.test.d.ts +0 -6
- package/dist/src/utils/environmentContext.test.js +0 -139
- package/dist/src/utils/environmentContext.test.js.map +0 -1
- package/dist/src/utils/errorReporting.test.d.ts +0 -6
- package/dist/src/utils/errorReporting.test.js +0 -130
- package/dist/src/utils/errorReporting.test.js.map +0 -1
- package/dist/src/utils/fileUtils.test.d.ts +0 -6
- package/dist/src/utils/fileUtils.test.js +0 -363
- package/dist/src/utils/fileUtils.test.js.map +0 -1
- package/dist/src/utils/filesearch/crawlCache.test.d.ts +0 -6
- package/dist/src/utils/filesearch/crawlCache.test.js +0 -103
- package/dist/src/utils/filesearch/crawlCache.test.js.map +0 -1
- package/dist/src/utils/filesearch/fileSearch.test.d.ts +0 -6
- package/dist/src/utils/filesearch/fileSearch.test.js +0 -654
- package/dist/src/utils/filesearch/fileSearch.test.js.map +0 -1
- package/dist/src/utils/filesearch/ignore.test.d.ts +0 -6
- package/dist/src/utils/filesearch/ignore.test.js +0 -57
- package/dist/src/utils/filesearch/ignore.test.js.map +0 -1
- package/dist/src/utils/filesearch/result-cache.test.d.ts +0 -6
- package/dist/src/utils/filesearch/result-cache.test.js +0 -47
- package/dist/src/utils/filesearch/result-cache.test.js.map +0 -1
- package/dist/src/utils/flashFallback.integration.test.d.ts +0 -6
- package/dist/src/utils/flashFallback.integration.test.js +0 -120
- package/dist/src/utils/flashFallback.integration.test.js.map +0 -1
- package/dist/src/utils/generateContentResponseUtilities.test.d.ts +0 -6
- package/dist/src/utils/generateContentResponseUtilities.test.js +0 -273
- package/dist/src/utils/generateContentResponseUtilities.test.js.map +0 -1
- package/dist/src/utils/getFolderStructure.test.d.ts +0 -6
- package/dist/src/utils/getFolderStructure.test.js +0 -282
- package/dist/src/utils/getFolderStructure.test.js.map +0 -1
- package/dist/src/utils/gitIgnoreParser.test.d.ts +0 -6
- package/dist/src/utils/gitIgnoreParser.test.js +0 -154
- package/dist/src/utils/gitIgnoreParser.test.js.map +0 -1
- package/dist/src/utils/memoryDiscovery.test.d.ts +0 -6
- package/dist/src/utils/memoryDiscovery.test.js +0 -181
- package/dist/src/utils/memoryDiscovery.test.js.map +0 -1
- package/dist/src/utils/memoryImportProcessor.test.d.ts +0 -6
- package/dist/src/utils/memoryImportProcessor.test.js +0 -715
- package/dist/src/utils/memoryImportProcessor.test.js.map +0 -1
- package/dist/src/utils/nextSpeakerChecker.test.d.ts +0 -6
- package/dist/src/utils/nextSpeakerChecker.test.js +0 -172
- package/dist/src/utils/nextSpeakerChecker.test.js.map +0 -1
- package/dist/src/utils/partUtils.test.d.ts +0 -6
- package/dist/src/utils/partUtils.test.js +0 -130
- package/dist/src/utils/partUtils.test.js.map +0 -1
- package/dist/src/utils/paths.test.d.ts +0 -6
- package/dist/src/utils/paths.test.js +0 -153
- package/dist/src/utils/paths.test.js.map +0 -1
- package/dist/src/utils/retry.test.d.ts +0 -6
- package/dist/src/utils/retry.test.js +0 -322
- package/dist/src/utils/retry.test.js.map +0 -1
- package/dist/src/utils/safeJsonStringify.test.d.ts +0 -6
- package/dist/src/utils/safeJsonStringify.test.js +0 -61
- package/dist/src/utils/safeJsonStringify.test.js.map +0 -1
- package/dist/src/utils/sanitization.test.d.ts +0 -6
- package/dist/src/utils/sanitization.test.js +0 -81
- package/dist/src/utils/sanitization.test.js.map +0 -1
- package/dist/src/utils/schemaValidator.test.d.ts +0 -6
- package/dist/src/utils/schemaValidator.test.js +0 -146
- package/dist/src/utils/schemaValidator.test.js.map +0 -1
- package/dist/src/utils/secure-browser-launcher.test.d.ts +0 -6
- package/dist/src/utils/secure-browser-launcher.test.js +0 -149
- package/dist/src/utils/secure-browser-launcher.test.js.map +0 -1
- package/dist/src/utils/shell-utils.shellReplacement.test.d.ts +0 -6
- package/dist/src/utils/shell-utils.shellReplacement.test.js +0 -149
- package/dist/src/utils/shell-utils.shellReplacement.test.js.map +0 -1
- package/dist/src/utils/shell-utils.test.d.ts +0 -6
- package/dist/src/utils/shell-utils.test.js +0 -200
- package/dist/src/utils/shell-utils.test.js.map +0 -1
- package/dist/src/utils/summarizer.test.d.ts +0 -6
- package/dist/src/utils/summarizer.test.js +0 -131
- package/dist/src/utils/summarizer.test.js.map +0 -1
- package/dist/src/utils/systemEncoding.test.d.ts +0 -6
- package/dist/src/utils/systemEncoding.test.js +0 -368
- package/dist/src/utils/systemEncoding.test.js.map +0 -1
- package/dist/src/utils/toolOutputLimiter.test.d.ts +0 -6
- package/dist/src/utils/toolOutputLimiter.test.js +0 -164
- package/dist/src/utils/toolOutputLimiter.test.js.map +0 -1
- package/dist/src/utils/unicodeUtils.test.d.ts +0 -6
- package/dist/src/utils/unicodeUtils.test.js +0 -120
- package/dist/src/utils/unicodeUtils.test.js.map +0 -1
- package/dist/src/utils/user_account.test.d.ts +0 -6
- package/dist/src/utils/user_account.test.js +0 -153
- package/dist/src/utils/user_account.test.js.map +0 -1
- package/dist/src/utils/user_id.test.d.ts +0 -6
- package/dist/src/utils/user_id.test.js +0 -21
- package/dist/src/utils/user_id.test.js.map +0 -1
- package/dist/src/utils/workspaceContext.test.d.ts +0 -6
- package/dist/src/utils/workspaceContext.test.js +0 -209
- package/dist/src/utils/workspaceContext.test.js.map +0 -1
|
@@ -1,1322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @license
|
|
3
|
-
* Copyright 2025 Google LLC
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
*/
|
|
6
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
// Mock prompts module before imports
|
|
8
|
-
vi.mock('./prompts.js', () => ({
|
|
9
|
-
getCoreSystemPromptAsync: vi
|
|
10
|
-
.fn()
|
|
11
|
-
.mockResolvedValue('Test system instruction'),
|
|
12
|
-
getCompressionPrompt: vi.fn().mockReturnValue('Test compression prompt'),
|
|
13
|
-
initializePromptSystem: vi.fn().mockResolvedValue(undefined),
|
|
14
|
-
}));
|
|
15
|
-
import { GoogleGenAI, } from '@google/genai';
|
|
16
|
-
import { findIndexAfterFraction, GeminiClient } from './client.js';
|
|
17
|
-
import { getCoreSystemPromptAsync } from './prompts.js';
|
|
18
|
-
import { AuthType, } from './contentGenerator.js';
|
|
19
|
-
import { Config } from '../config/config.js';
|
|
20
|
-
import { GeminiEventType, Turn } from './turn.js';
|
|
21
|
-
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
|
22
|
-
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
|
23
|
-
import { setSimulate429 } from '../utils/testUtils.js';
|
|
24
|
-
import { tokenLimit } from './tokenLimits.js';
|
|
25
|
-
import { ideContext } from '../ide/ideContext.js';
|
|
26
|
-
import { ComplexityAnalyzer } from '../services/complexity-analyzer.js';
|
|
27
|
-
import { TodoReminderService } from '../services/todo-reminder-service.js';
|
|
28
|
-
// --- Mocks ---
|
|
29
|
-
const mockChatCreateFn = vi.fn();
|
|
30
|
-
const mockGenerateContentFn = vi.fn();
|
|
31
|
-
const mockEmbedContentFn = vi.fn();
|
|
32
|
-
const mockTurnRunFn = vi.fn();
|
|
33
|
-
vi.mock('@google/genai');
|
|
34
|
-
vi.mock('../services/complexity-analyzer.js', () => ({
|
|
35
|
-
ComplexityAnalyzer: vi.fn().mockImplementation(() => ({
|
|
36
|
-
analyzeComplexity: vi.fn().mockReturnValue({
|
|
37
|
-
complexityScore: 0.2,
|
|
38
|
-
isComplex: false,
|
|
39
|
-
detectedTasks: [],
|
|
40
|
-
sequentialIndicators: [],
|
|
41
|
-
questionCount: 0,
|
|
42
|
-
shouldSuggestTodos: false,
|
|
43
|
-
}),
|
|
44
|
-
})),
|
|
45
|
-
}));
|
|
46
|
-
vi.mock('../services/todo-reminder-service.js', () => ({
|
|
47
|
-
TodoReminderService: vi.fn().mockImplementation(() => ({
|
|
48
|
-
getComplexTaskSuggestion: vi.fn(),
|
|
49
|
-
})),
|
|
50
|
-
}));
|
|
51
|
-
vi.mock('./turn', () => {
|
|
52
|
-
// Define a mock class that has the same shape as the real Turn
|
|
53
|
-
class MockTurn {
|
|
54
|
-
pendingToolCalls = [];
|
|
55
|
-
// The run method is a property that holds our mock function
|
|
56
|
-
run = mockTurnRunFn;
|
|
57
|
-
constructor() {
|
|
58
|
-
// The constructor can be empty or do some mock setup
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Export the mock class as 'Turn'
|
|
62
|
-
return {
|
|
63
|
-
Turn: MockTurn,
|
|
64
|
-
GeminiEventType: {
|
|
65
|
-
MaxSessionTurns: 'MaxSessionTurns',
|
|
66
|
-
ChatCompressed: 'ChatCompressed',
|
|
67
|
-
},
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
vi.mock('../config/config.js');
|
|
71
|
-
vi.mock('../utils/getFolderStructure', () => ({
|
|
72
|
-
getFolderStructure: vi.fn().mockResolvedValue('Mock Folder Structure'),
|
|
73
|
-
}));
|
|
74
|
-
vi.mock('../utils/errorReporting', () => ({ reportError: vi.fn() }));
|
|
75
|
-
vi.mock('../utils/generateContentResponseUtilities', () => ({
|
|
76
|
-
getResponseText: (result) => result.candidates?.[0]?.content?.parts?.map((part) => part.text).join('') ||
|
|
77
|
-
undefined,
|
|
78
|
-
}));
|
|
79
|
-
vi.mock('../telemetry/index.js', () => ({
|
|
80
|
-
logApiRequest: vi.fn(),
|
|
81
|
-
logApiResponse: vi.fn(),
|
|
82
|
-
logApiError: vi.fn(),
|
|
83
|
-
}));
|
|
84
|
-
vi.mock('../utils/retry.js', () => ({
|
|
85
|
-
retryWithBackoff: vi.fn((apiCall) => apiCall()),
|
|
86
|
-
}));
|
|
87
|
-
vi.mock('../ide/ideContext.js');
|
|
88
|
-
describe('findIndexAfterFraction', () => {
|
|
89
|
-
const history = [
|
|
90
|
-
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66
|
|
91
|
-
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68
|
|
92
|
-
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66
|
|
93
|
-
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68
|
|
94
|
-
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65
|
|
95
|
-
];
|
|
96
|
-
// Total length: 333
|
|
97
|
-
it('should throw an error for non-positive numbers', () => {
|
|
98
|
-
expect(() => findIndexAfterFraction(history, 0)).toThrow('Fraction must be between 0 and 1');
|
|
99
|
-
});
|
|
100
|
-
it('should throw an error for a fraction greater than or equal to 1', () => {
|
|
101
|
-
expect(() => findIndexAfterFraction(history, 1)).toThrow('Fraction must be between 0 and 1');
|
|
102
|
-
});
|
|
103
|
-
it('should handle a fraction in the middle', () => {
|
|
104
|
-
// 333 * 0.5 = 166.5
|
|
105
|
-
// 0: 66
|
|
106
|
-
// 1: 66 + 68 = 134
|
|
107
|
-
// 2: 134 + 66 = 200
|
|
108
|
-
// 200 >= 166.5, so index is 2
|
|
109
|
-
expect(findIndexAfterFraction(history, 0.5)).toBe(2);
|
|
110
|
-
});
|
|
111
|
-
it('should handle a fraction that results in the last index', () => {
|
|
112
|
-
// 333 * 0.9 = 299.7
|
|
113
|
-
// ...
|
|
114
|
-
// 3: 200 + 68 = 268
|
|
115
|
-
// 4: 268 + 65 = 333
|
|
116
|
-
// 333 >= 299.7, so index is 4
|
|
117
|
-
expect(findIndexAfterFraction(history, 0.9)).toBe(4);
|
|
118
|
-
});
|
|
119
|
-
it('should handle an empty history', () => {
|
|
120
|
-
expect(findIndexAfterFraction([], 0.5)).toBe(0);
|
|
121
|
-
});
|
|
122
|
-
it('should handle a history with only one item', () => {
|
|
123
|
-
expect(findIndexAfterFraction(history.slice(0, 1), 0.5)).toBe(0);
|
|
124
|
-
});
|
|
125
|
-
it('should handle history with weird parts', () => {
|
|
126
|
-
const historyWithEmptyParts = [
|
|
127
|
-
{ role: 'user', parts: [{ text: 'Message 1' }] },
|
|
128
|
-
{ role: 'model', parts: [{ fileData: { fileUri: 'derp' } }] },
|
|
129
|
-
{ role: 'user', parts: [{ text: 'Message 2' }] },
|
|
130
|
-
];
|
|
131
|
-
expect(findIndexAfterFraction(historyWithEmptyParts, 0.5)).toBe(1);
|
|
132
|
-
});
|
|
133
|
-
});
|
|
134
|
-
describe('Gemini Client (client.ts)', () => {
|
|
135
|
-
let client;
|
|
136
|
-
beforeEach(async () => {
|
|
137
|
-
vi.resetAllMocks();
|
|
138
|
-
// Re-setup prompts mocks after reset
|
|
139
|
-
vi.mocked(getCoreSystemPromptAsync).mockResolvedValue('Test system instruction');
|
|
140
|
-
// Re-setup mocks after reset
|
|
141
|
-
vi.mocked(ComplexityAnalyzer).mockImplementation(() => ({
|
|
142
|
-
analyzeComplexity: vi.fn().mockReturnValue({
|
|
143
|
-
complexityScore: 0.2,
|
|
144
|
-
isComplex: false,
|
|
145
|
-
detectedTasks: [],
|
|
146
|
-
sequentialIndicators: [],
|
|
147
|
-
questionCount: 0,
|
|
148
|
-
shouldSuggestTodos: false,
|
|
149
|
-
}),
|
|
150
|
-
}));
|
|
151
|
-
vi.mocked(TodoReminderService).mockImplementation(() => ({
|
|
152
|
-
getComplexTaskSuggestion: vi.fn(),
|
|
153
|
-
}));
|
|
154
|
-
// Disable 429 simulation for tests
|
|
155
|
-
setSimulate429(false);
|
|
156
|
-
// Set up the mock for GoogleGenAI constructor and its methods
|
|
157
|
-
const MockedGoogleGenAI = vi.mocked(GoogleGenAI);
|
|
158
|
-
MockedGoogleGenAI.mockImplementation((..._args) => {
|
|
159
|
-
const mockInstance = {
|
|
160
|
-
chats: { create: mockChatCreateFn },
|
|
161
|
-
models: {
|
|
162
|
-
generateContent: mockGenerateContentFn,
|
|
163
|
-
embedContent: mockEmbedContentFn,
|
|
164
|
-
},
|
|
165
|
-
};
|
|
166
|
-
return mockInstance;
|
|
167
|
-
});
|
|
168
|
-
mockChatCreateFn.mockResolvedValue({});
|
|
169
|
-
mockGenerateContentFn.mockResolvedValue({
|
|
170
|
-
candidates: [
|
|
171
|
-
{
|
|
172
|
-
content: {
|
|
173
|
-
parts: [{ text: '{"key": "value"}' }],
|
|
174
|
-
},
|
|
175
|
-
},
|
|
176
|
-
],
|
|
177
|
-
});
|
|
178
|
-
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
179
|
-
// that depends on a fully-formed Config object, we need to mock the
|
|
180
|
-
// entire implementation of Config for these tests.
|
|
181
|
-
const mockToolRegistry = {
|
|
182
|
-
getFunctionDeclarations: vi.fn().mockReturnValue([]),
|
|
183
|
-
getTool: vi.fn().mockReturnValue(null),
|
|
184
|
-
};
|
|
185
|
-
const fileService = new FileDiscoveryService('/test/dir');
|
|
186
|
-
const MockedConfig = vi.mocked(Config, true);
|
|
187
|
-
const contentGeneratorConfig = {
|
|
188
|
-
model: 'test-model',
|
|
189
|
-
apiKey: 'test-key',
|
|
190
|
-
vertexai: false,
|
|
191
|
-
authType: AuthType.USE_GEMINI,
|
|
192
|
-
};
|
|
193
|
-
const mockConfigObject = {
|
|
194
|
-
getContentGeneratorConfig: vi
|
|
195
|
-
.fn()
|
|
196
|
-
.mockReturnValue(contentGeneratorConfig),
|
|
197
|
-
getToolRegistry: vi.fn().mockResolvedValue(mockToolRegistry),
|
|
198
|
-
getModel: vi.fn().mockReturnValue('test-model'),
|
|
199
|
-
getEmbeddingModel: vi.fn().mockReturnValue('test-embedding-model'),
|
|
200
|
-
getApiKey: vi.fn().mockReturnValue('test-key'),
|
|
201
|
-
getVertexAI: vi.fn().mockReturnValue(false),
|
|
202
|
-
getUserAgent: vi.fn().mockReturnValue('test-agent'),
|
|
203
|
-
getUserMemory: vi.fn().mockReturnValue(''),
|
|
204
|
-
getFullContext: vi.fn().mockReturnValue(false),
|
|
205
|
-
getSessionId: vi.fn().mockReturnValue('test-session-id'),
|
|
206
|
-
getProxy: vi.fn().mockReturnValue(undefined),
|
|
207
|
-
getWorkingDir: vi.fn().mockReturnValue('/test/dir'),
|
|
208
|
-
getFileService: vi.fn().mockReturnValue(fileService),
|
|
209
|
-
getMaxSessionTurns: vi.fn().mockReturnValue(0),
|
|
210
|
-
getQuotaErrorOccurred: vi.fn().mockReturnValue(false),
|
|
211
|
-
setQuotaErrorOccurred: vi.fn(),
|
|
212
|
-
getNoBrowser: vi.fn().mockReturnValue(false),
|
|
213
|
-
getUsageStatisticsEnabled: vi.fn().mockReturnValue(true),
|
|
214
|
-
getIdeModeFeature: vi.fn().mockReturnValue(false),
|
|
215
|
-
getIdeMode: vi.fn().mockReturnValue(true),
|
|
216
|
-
getWorkspaceContext: vi.fn().mockReturnValue({
|
|
217
|
-
getDirectories: vi.fn().mockReturnValue(['/test/dir']),
|
|
218
|
-
}),
|
|
219
|
-
getGeminiClient: vi.fn(),
|
|
220
|
-
getDebugMode: vi.fn().mockReturnValue(false),
|
|
221
|
-
setFallbackMode: vi.fn(),
|
|
222
|
-
getComplexityAnalyzerSettings: vi.fn().mockReturnValue({
|
|
223
|
-
complexityThreshold: 0.6,
|
|
224
|
-
minTasksForSuggestion: 3,
|
|
225
|
-
suggestionCooldownMs: 300000,
|
|
226
|
-
}),
|
|
227
|
-
getChatCompression: vi.fn().mockReturnValue(undefined),
|
|
228
|
-
};
|
|
229
|
-
MockedConfig.mockImplementation(() => mockConfigObject);
|
|
230
|
-
// We can instantiate the client here since Config is mocked
|
|
231
|
-
// and the constructor will use the mocked GoogleGenAI
|
|
232
|
-
const mockConfig = new Config({
|
|
233
|
-
sessionId: 'test-session-id',
|
|
234
|
-
});
|
|
235
|
-
client = new GeminiClient(mockConfig);
|
|
236
|
-
await client.initialize(contentGeneratorConfig);
|
|
237
|
-
// Update the mock to return the client
|
|
238
|
-
mockConfigObject.getGeminiClient.mockReturnValue(client);
|
|
239
|
-
// Add missing methods to the client instance for tests
|
|
240
|
-
client.getHistory = vi.fn().mockReturnValue([]);
|
|
241
|
-
});
|
|
242
|
-
afterEach(() => {
|
|
243
|
-
vi.restoreAllMocks();
|
|
244
|
-
});
|
|
245
|
-
// NOTE: The following tests for startChat were removed due to persistent issues with
|
|
246
|
-
// the @google/genai mock. Specifically, the mockChatCreateFn (representing instance.chats.create)
|
|
247
|
-
// was not being detected as called by the GeminiClient instance.
|
|
248
|
-
// This likely points to a subtle issue in how the GoogleGenerativeAI class constructor
|
|
249
|
-
// and its instance methods are mocked and then used by the class under test.
|
|
250
|
-
// For future debugging, ensure that the `this.client` in `GeminiClient` (which is an
|
|
251
|
-
// instance of the mocked GoogleGenerativeAI) correctly has its `chats.create` method
|
|
252
|
-
// pointing to `mockChatCreateFn`.
|
|
253
|
-
// it('startChat should call getCoreSystemPrompt with userMemory and pass to chats.create', async () => { ... });
|
|
254
|
-
// it('startChat should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
255
|
-
// NOTE: The following tests for generateJson were removed due to persistent issues with
|
|
256
|
-
// the @google/genai mock, similar to the startChat tests. The mockGenerateContentFn
|
|
257
|
-
// (representing instance.models.generateContent) was not being detected as called, or the mock
|
|
258
|
-
// was not preventing an actual API call (leading to API key errors).
|
|
259
|
-
// For future debugging, ensure `this.client.models.generateContent` in `GeminiClient` correctly
|
|
260
|
-
// uses the `mockGenerateContentFn`.
|
|
261
|
-
// it('generateJson should call getCoreSystemPrompt with userMemory and pass to generateContent', async () => { ... });
|
|
262
|
-
// it('generateJson should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
263
|
-
describe('generateEmbedding', () => {
|
|
264
|
-
const texts = ['hello world', 'goodbye world'];
|
|
265
|
-
const testEmbeddingModel = 'test-embedding-model';
|
|
266
|
-
it('should call embedContent with correct parameters and return embeddings', async () => {
|
|
267
|
-
const mockEmbeddings = [
|
|
268
|
-
[0.1, 0.2, 0.3],
|
|
269
|
-
[0.4, 0.5, 0.6],
|
|
270
|
-
];
|
|
271
|
-
const mockResponse = {
|
|
272
|
-
embeddings: [
|
|
273
|
-
{ values: mockEmbeddings[0] },
|
|
274
|
-
{ values: mockEmbeddings[1] },
|
|
275
|
-
],
|
|
276
|
-
};
|
|
277
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
278
|
-
const result = await client.generateEmbedding(texts);
|
|
279
|
-
expect(mockEmbedContentFn).toHaveBeenCalledTimes(1);
|
|
280
|
-
expect(mockEmbedContentFn).toHaveBeenCalledWith({
|
|
281
|
-
model: testEmbeddingModel,
|
|
282
|
-
contents: texts,
|
|
283
|
-
});
|
|
284
|
-
expect(result).toEqual(mockEmbeddings);
|
|
285
|
-
});
|
|
286
|
-
it('should return an empty array if an empty array is passed', async () => {
|
|
287
|
-
const result = await client.generateEmbedding([]);
|
|
288
|
-
expect(result).toEqual([]);
|
|
289
|
-
expect(mockEmbedContentFn).not.toHaveBeenCalled();
|
|
290
|
-
});
|
|
291
|
-
it('should throw an error if API response has no embeddings array', async () => {
|
|
292
|
-
mockEmbedContentFn.mockResolvedValue({}); // No `embeddings` key
|
|
293
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
294
|
-
});
|
|
295
|
-
it('should throw an error if API response has an empty embeddings array', async () => {
|
|
296
|
-
const mockResponse = {
|
|
297
|
-
embeddings: [],
|
|
298
|
-
};
|
|
299
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
300
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
301
|
-
});
|
|
302
|
-
it('should throw an error if API returns a mismatched number of embeddings', async () => {
|
|
303
|
-
const mockResponse = {
|
|
304
|
-
embeddings: [{ values: [1, 2, 3] }], // Only one for two texts
|
|
305
|
-
};
|
|
306
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
307
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned a mismatched number of embeddings. Expected 2, got 1.');
|
|
308
|
-
});
|
|
309
|
-
it('should throw an error if any embedding has nullish values', async () => {
|
|
310
|
-
const mockResponse = {
|
|
311
|
-
embeddings: [{ values: [1, 2, 3] }, { values: undefined }], // Second one is bad
|
|
312
|
-
};
|
|
313
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
314
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 1: "goodbye world"');
|
|
315
|
-
});
|
|
316
|
-
it('should throw an error if any embedding has an empty values array', async () => {
|
|
317
|
-
const mockResponse = {
|
|
318
|
-
embeddings: [{ values: [] }, { values: [1, 2, 3] }], // First one is bad
|
|
319
|
-
};
|
|
320
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
321
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 0: "hello world"');
|
|
322
|
-
});
|
|
323
|
-
it('should propagate errors from the API call', async () => {
|
|
324
|
-
const apiError = new Error('API Failure');
|
|
325
|
-
mockEmbedContentFn.mockRejectedValue(apiError);
|
|
326
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API Failure');
|
|
327
|
-
});
|
|
328
|
-
});
|
|
329
|
-
describe('generateContent', () => {
|
|
330
|
-
it('should call generateContent with the correct parameters', async () => {
|
|
331
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
332
|
-
const generationConfig = { temperature: 0.5 };
|
|
333
|
-
const abortSignal = new AbortController().signal;
|
|
334
|
-
// Mock the retryWithBackoff to directly call the apiCall function
|
|
335
|
-
vi.mock('../utils/retry.js', () => ({
|
|
336
|
-
retryWithBackoff: vi.fn((apiCall) => apiCall()),
|
|
337
|
-
}));
|
|
338
|
-
// Mock countTokens with a fresh mock function
|
|
339
|
-
const mockContentGeneratorGenerateContent = vi.fn().mockResolvedValue({
|
|
340
|
-
candidates: [
|
|
341
|
-
{
|
|
342
|
-
content: {
|
|
343
|
-
parts: [{ text: 'response' }],
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
});
|
|
348
|
-
const mockGenerator = {
|
|
349
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
350
|
-
generateContent: mockContentGeneratorGenerateContent,
|
|
351
|
-
generateContentStream: vi.fn(),
|
|
352
|
-
embedContent: vi.fn(),
|
|
353
|
-
};
|
|
354
|
-
client['contentGenerator'] = mockGenerator;
|
|
355
|
-
client['isInitialized'] = vi.fn().mockReturnValue(true);
|
|
356
|
-
const { retryWithBackoff } = await import('../utils/retry.js');
|
|
357
|
-
vi.mocked(retryWithBackoff).mockImplementation(async (apiCall) => await apiCall());
|
|
358
|
-
await client.generateContent(contents, generationConfig, abortSignal);
|
|
359
|
-
expect(mockContentGeneratorGenerateContent).toHaveBeenCalledWith({
|
|
360
|
-
model: 'test-model',
|
|
361
|
-
config: {
|
|
362
|
-
abortSignal,
|
|
363
|
-
systemInstruction: 'Test system instruction',
|
|
364
|
-
temperature: 0.5,
|
|
365
|
-
topP: 1,
|
|
366
|
-
},
|
|
367
|
-
contents,
|
|
368
|
-
}, 'test-session-id');
|
|
369
|
-
});
|
|
370
|
-
});
|
|
371
|
-
describe('generateJson', () => {
|
|
372
|
-
it('should call generateContent with the correct parameters', async () => {
|
|
373
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
374
|
-
const schema = { type: 'string' };
|
|
375
|
-
const abortSignal = new AbortController().signal;
|
|
376
|
-
// Mock lazyInitialize to prevent it from overriding our mock
|
|
377
|
-
client['lazyInitialize'] = vi.fn().mockResolvedValue(undefined);
|
|
378
|
-
// Track the arguments manually
|
|
379
|
-
let capturedRequest;
|
|
380
|
-
let capturedPromptId;
|
|
381
|
-
const mockGenerator = {
|
|
382
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
383
|
-
generateContent: vi.fn(async (request, promptId) => {
|
|
384
|
-
capturedRequest = request;
|
|
385
|
-
capturedPromptId = promptId;
|
|
386
|
-
return {
|
|
387
|
-
candidates: [
|
|
388
|
-
{
|
|
389
|
-
content: {
|
|
390
|
-
parts: [{ text: '{"key": "value"}' }],
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
],
|
|
394
|
-
};
|
|
395
|
-
}),
|
|
396
|
-
generateContentStream: vi.fn(),
|
|
397
|
-
embedContent: vi.fn(),
|
|
398
|
-
};
|
|
399
|
-
client['contentGenerator'] = mockGenerator;
|
|
400
|
-
try {
|
|
401
|
-
await client.generateJson(contents, schema, abortSignal);
|
|
402
|
-
}
|
|
403
|
-
catch (error) {
|
|
404
|
-
console.error('Error in generateJson:', error);
|
|
405
|
-
throw error;
|
|
406
|
-
}
|
|
407
|
-
// Check the captured arguments
|
|
408
|
-
expect(capturedRequest).toBeDefined();
|
|
409
|
-
expect(capturedPromptId).toBe('test-session-id');
|
|
410
|
-
expect(capturedRequest).toMatchObject({
|
|
411
|
-
model: 'test-model', // Should use current model from config
|
|
412
|
-
config: {
|
|
413
|
-
abortSignal,
|
|
414
|
-
systemInstruction: 'Test system instruction',
|
|
415
|
-
temperature: 0,
|
|
416
|
-
topP: 1,
|
|
417
|
-
responseSchema: schema,
|
|
418
|
-
responseMimeType: 'application/json',
|
|
419
|
-
},
|
|
420
|
-
contents,
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
it('should allow overriding model and config', async () => {
|
|
424
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
425
|
-
const schema = { type: 'string' };
|
|
426
|
-
const abortSignal = new AbortController().signal;
|
|
427
|
-
const customModel = 'custom-json-model';
|
|
428
|
-
const customConfig = { temperature: 0.9, topK: 20 };
|
|
429
|
-
// Mock lazyInitialize to prevent it from overriding our mock
|
|
430
|
-
client['lazyInitialize'] = vi.fn().mockResolvedValue(undefined);
|
|
431
|
-
// Track the arguments manually
|
|
432
|
-
let capturedRequest;
|
|
433
|
-
let capturedPromptId;
|
|
434
|
-
const mockGenerator = {
|
|
435
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
436
|
-
generateContent: vi.fn(async (request, promptId) => {
|
|
437
|
-
capturedRequest = request;
|
|
438
|
-
capturedPromptId = promptId;
|
|
439
|
-
return {
|
|
440
|
-
candidates: [
|
|
441
|
-
{
|
|
442
|
-
content: {
|
|
443
|
-
parts: [{ text: '{"key": "value"}' }],
|
|
444
|
-
},
|
|
445
|
-
},
|
|
446
|
-
],
|
|
447
|
-
};
|
|
448
|
-
}),
|
|
449
|
-
};
|
|
450
|
-
client['contentGenerator'] = mockGenerator;
|
|
451
|
-
await client.generateJson(contents, schema, abortSignal, customModel, customConfig);
|
|
452
|
-
// Check the captured arguments
|
|
453
|
-
expect(capturedRequest).toBeDefined();
|
|
454
|
-
expect(capturedPromptId).toBe('test-session-id');
|
|
455
|
-
expect(capturedRequest).toMatchObject({
|
|
456
|
-
model: customModel,
|
|
457
|
-
config: {
|
|
458
|
-
abortSignal,
|
|
459
|
-
systemInstruction: 'Test system instruction',
|
|
460
|
-
temperature: 0.9,
|
|
461
|
-
topP: 1, // from default
|
|
462
|
-
topK: 20,
|
|
463
|
-
responseSchema: schema,
|
|
464
|
-
responseMimeType: 'application/json',
|
|
465
|
-
},
|
|
466
|
-
contents,
|
|
467
|
-
});
|
|
468
|
-
});
|
|
469
|
-
});
|
|
470
|
-
describe('addHistory', () => {
|
|
471
|
-
it('should call chat.addHistory with the provided content', async () => {
|
|
472
|
-
const mockChat = {
|
|
473
|
-
addHistory: vi.fn(),
|
|
474
|
-
};
|
|
475
|
-
client['chat'] = mockChat;
|
|
476
|
-
const newContent = {
|
|
477
|
-
role: 'user',
|
|
478
|
-
parts: [{ text: 'New history item' }],
|
|
479
|
-
};
|
|
480
|
-
await client.addHistory(newContent);
|
|
481
|
-
expect(mockChat.addHistory).toHaveBeenCalledWith(newContent);
|
|
482
|
-
});
|
|
483
|
-
});
|
|
484
|
-
describe('resetChat', () => {
|
|
485
|
-
it('should create a new chat session, clearing the old history', async () => {
|
|
486
|
-
// Create mock chats with distinct histories
|
|
487
|
-
const initialChatHistory = [
|
|
488
|
-
{ role: 'user', parts: [{ text: 'initial context' }] },
|
|
489
|
-
{ role: 'model', parts: [{ text: 'acknowledged' }] },
|
|
490
|
-
];
|
|
491
|
-
const mockInitialChat = {
|
|
492
|
-
getHistory: vi.fn().mockReturnValue(initialChatHistory),
|
|
493
|
-
addHistory: vi.fn().mockImplementation((content) => {
|
|
494
|
-
// Update the history when addHistory is called
|
|
495
|
-
initialChatHistory.push(content);
|
|
496
|
-
}),
|
|
497
|
-
setHistory: vi.fn(),
|
|
498
|
-
};
|
|
499
|
-
const mockNewChat = {
|
|
500
|
-
getHistory: vi.fn().mockReturnValue([
|
|
501
|
-
{ role: 'user', parts: [{ text: 'fresh start' }] },
|
|
502
|
-
{ role: 'model', parts: [{ text: 'ready' }] },
|
|
503
|
-
]),
|
|
504
|
-
addHistory: vi.fn(),
|
|
505
|
-
setHistory: vi.fn(),
|
|
506
|
-
};
|
|
507
|
-
// Mock startChat to return the new chat when called
|
|
508
|
-
const mockStartChat = vi.fn().mockResolvedValue(mockNewChat);
|
|
509
|
-
client['startChat'] = mockStartChat;
|
|
510
|
-
client['chat'] = mockInitialChat;
|
|
511
|
-
// Mock that client is initialized
|
|
512
|
-
client['contentGenerator'] = {};
|
|
513
|
-
client['isInitialized'] = vi.fn().mockReturnValue(true);
|
|
514
|
-
// Override the global getHistory mock for this test
|
|
515
|
-
const getHistoryMock = vi.mocked(client.getHistory);
|
|
516
|
-
getHistoryMock.mockImplementation(async () => client.getChat().getHistory());
|
|
517
|
-
// 1. Get the initial chat instance and verify initial state
|
|
518
|
-
const initialChat = client.getChat();
|
|
519
|
-
expect(initialChat).toBe(mockInitialChat);
|
|
520
|
-
const initialHistory = await client.getHistory();
|
|
521
|
-
expect(initialHistory).toHaveLength(2); // initial context + acknowledged
|
|
522
|
-
// Add a message to the initial chat
|
|
523
|
-
await client.addHistory({
|
|
524
|
-
role: 'user',
|
|
525
|
-
parts: [{ text: 'some old message' }],
|
|
526
|
-
});
|
|
527
|
-
const historyWithOldMessage = await client.getHistory();
|
|
528
|
-
expect(historyWithOldMessage).toHaveLength(3);
|
|
529
|
-
expect(JSON.stringify(historyWithOldMessage)).toContain('some old message');
|
|
530
|
-
// 2. Call resetChat
|
|
531
|
-
await client.resetChat();
|
|
532
|
-
// 3. Verify the chat was replaced
|
|
533
|
-
const newChat = client.getChat();
|
|
534
|
-
expect(mockStartChat).toHaveBeenCalledTimes(1);
|
|
535
|
-
expect(client['chat']).toBe(mockNewChat);
|
|
536
|
-
expect(newChat).toBe(mockNewChat);
|
|
537
|
-
expect(newChat).not.toBe(initialChat);
|
|
538
|
-
// 4. Verify the history is from the new chat
|
|
539
|
-
const newHistory = await client.getHistory();
|
|
540
|
-
expect(newHistory).toHaveLength(2); // fresh start + ready
|
|
541
|
-
expect(JSON.stringify(newHistory)).not.toContain('some old message');
|
|
542
|
-
expect(JSON.stringify(newHistory)).toContain('fresh start');
|
|
543
|
-
});
|
|
544
|
-
});
|
|
545
|
-
describe('tryCompressChat', () => {
|
|
546
|
-
const mockCountTokens = vi.fn();
|
|
547
|
-
const mockSendMessage = vi.fn();
|
|
548
|
-
const mockGetHistory = vi.fn();
|
|
549
|
-
beforeEach(() => {
|
|
550
|
-
vi.mock('./tokenLimits', () => ({
|
|
551
|
-
tokenLimit: vi.fn(),
|
|
552
|
-
}));
|
|
553
|
-
client['contentGenerator'] = {
|
|
554
|
-
countTokens: mockCountTokens,
|
|
555
|
-
};
|
|
556
|
-
client['chat'] = {
|
|
557
|
-
getHistory: mockGetHistory,
|
|
558
|
-
addHistory: vi.fn(),
|
|
559
|
-
setHistory: vi.fn(),
|
|
560
|
-
sendMessage: mockSendMessage,
|
|
561
|
-
};
|
|
562
|
-
});
|
|
563
|
-
it('should not trigger summarization if token count is below threshold', async () => {
|
|
564
|
-
const MOCKED_TOKEN_LIMIT = 1000;
|
|
565
|
-
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
566
|
-
mockGetHistory.mockReturnValue([
|
|
567
|
-
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
568
|
-
]);
|
|
569
|
-
mockCountTokens.mockResolvedValue({
|
|
570
|
-
totalTokens: MOCKED_TOKEN_LIMIT * 0.699, // TOKEN_THRESHOLD_FOR_SUMMARIZATION = 0.7
|
|
571
|
-
});
|
|
572
|
-
const initialChat = client.getChat();
|
|
573
|
-
const result = await client.tryCompressChat('prompt-id-2');
|
|
574
|
-
const newChat = client.getChat();
|
|
575
|
-
expect(tokenLimit).toHaveBeenCalled();
|
|
576
|
-
expect(result).toBeNull();
|
|
577
|
-
expect(newChat).toBe(initialChat);
|
|
578
|
-
});
|
|
579
|
-
it('should trigger summarization if token count is at threshold with contextPercentageThreshold setting', async () => {
|
|
580
|
-
const MOCKED_TOKEN_LIMIT = 1000;
|
|
581
|
-
const MOCKED_CONTEXT_PERCENTAGE_THRESHOLD = 0.5;
|
|
582
|
-
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
583
|
-
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
584
|
-
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
585
|
-
});
|
|
586
|
-
mockGetHistory.mockReturnValue([
|
|
587
|
-
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
588
|
-
]);
|
|
589
|
-
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
590
|
-
const newTokenCount = 100;
|
|
591
|
-
mockCountTokens
|
|
592
|
-
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
593
|
-
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
594
|
-
// Mock the summary response from the chat
|
|
595
|
-
mockSendMessage.mockResolvedValue({
|
|
596
|
-
role: 'model',
|
|
597
|
-
parts: [{ text: 'This is a summary.' }],
|
|
598
|
-
});
|
|
599
|
-
const initialChat = client.getChat();
|
|
600
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
601
|
-
const newChat = client.getChat();
|
|
602
|
-
expect(tokenLimit).toHaveBeenCalled();
|
|
603
|
-
expect(mockSendMessage).toHaveBeenCalled();
|
|
604
|
-
// Assert that summarization happened and returned the correct stats
|
|
605
|
-
expect(result).toEqual({
|
|
606
|
-
originalTokenCount,
|
|
607
|
-
newTokenCount,
|
|
608
|
-
});
|
|
609
|
-
// Assert that the chat was reset
|
|
610
|
-
expect(newChat).not.toBe(initialChat);
|
|
611
|
-
});
|
|
612
|
-
it('should not compress across a function call response', async () => {
|
|
613
|
-
const MOCKED_TOKEN_LIMIT = 1000;
|
|
614
|
-
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
615
|
-
mockGetHistory.mockReturnValue([
|
|
616
|
-
{ role: 'user', parts: [{ text: '...history 1...' }] },
|
|
617
|
-
{ role: 'model', parts: [{ text: '...history 2...' }] },
|
|
618
|
-
{ role: 'user', parts: [{ text: '...history 3...' }] },
|
|
619
|
-
{ role: 'model', parts: [{ text: '...history 4...' }] },
|
|
620
|
-
{ role: 'user', parts: [{ text: '...history 5...' }] },
|
|
621
|
-
{ role: 'model', parts: [{ text: '...history 6...' }] },
|
|
622
|
-
{ role: 'user', parts: [{ text: '...history 7...' }] },
|
|
623
|
-
{ role: 'model', parts: [{ text: '...history 8...' }] },
|
|
624
|
-
// Normally we would break here, but we have a function response.
|
|
625
|
-
{
|
|
626
|
-
role: 'user',
|
|
627
|
-
parts: [{ functionResponse: { name: '...history 8...' } }],
|
|
628
|
-
},
|
|
629
|
-
{ role: 'model', parts: [{ text: '...history 10...' }] },
|
|
630
|
-
// Instead we will break here.
|
|
631
|
-
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
632
|
-
]);
|
|
633
|
-
const originalTokenCount = 1000 * 0.7;
|
|
634
|
-
const newTokenCount = 100;
|
|
635
|
-
mockCountTokens
|
|
636
|
-
.mockResolvedValueOnce({ totalTokens: originalTokenCount }) // First call for the check
|
|
637
|
-
.mockResolvedValueOnce({ totalTokens: newTokenCount }); // Second call for the new history
|
|
638
|
-
// Mock the summary response from the chat
|
|
639
|
-
mockSendMessage.mockResolvedValue({
|
|
640
|
-
role: 'model',
|
|
641
|
-
parts: [{ text: 'This is a summary.' }],
|
|
642
|
-
});
|
|
643
|
-
const initialChat = client.getChat();
|
|
644
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
645
|
-
const newChat = client.getChat();
|
|
646
|
-
expect(tokenLimit).toHaveBeenCalled();
|
|
647
|
-
expect(mockSendMessage).toHaveBeenCalled();
|
|
648
|
-
// Assert that summarization happened and returned the correct stats
|
|
649
|
-
expect(result).toEqual({
|
|
650
|
-
originalTokenCount,
|
|
651
|
-
newTokenCount,
|
|
652
|
-
});
|
|
653
|
-
// Assert that the chat was reset
|
|
654
|
-
expect(newChat).not.toBe(initialChat);
|
|
655
|
-
// 1. standard start context message
|
|
656
|
-
// 2. standard canned user start message
|
|
657
|
-
// 3. compressed summary message
|
|
658
|
-
// 4. standard canned user summary message
|
|
659
|
-
// 5. The last user message (not the last 3 because that would start with a function response)
|
|
660
|
-
expect(newChat.getHistory().length).toEqual(5);
|
|
661
|
-
});
|
|
662
|
-
it('should always trigger summarization when force is true, regardless of token count', async () => {
|
|
663
|
-
mockGetHistory.mockReturnValue([
|
|
664
|
-
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
665
|
-
]);
|
|
666
|
-
const originalTokenCount = 10; // Well below threshold
|
|
667
|
-
const newTokenCount = 5;
|
|
668
|
-
mockCountTokens
|
|
669
|
-
.mockResolvedValueOnce({ totalTokens: originalTokenCount })
|
|
670
|
-
.mockResolvedValueOnce({ totalTokens: newTokenCount });
|
|
671
|
-
// Mock the summary response from the chat
|
|
672
|
-
mockSendMessage.mockResolvedValue({
|
|
673
|
-
role: 'model',
|
|
674
|
-
parts: [{ text: 'This is a summary.' }],
|
|
675
|
-
});
|
|
676
|
-
const initialChat = client.getChat();
|
|
677
|
-
const result = await client.tryCompressChat('prompt-id-1', true); // force = true
|
|
678
|
-
const newChat = client.getChat();
|
|
679
|
-
expect(mockSendMessage).toHaveBeenCalled();
|
|
680
|
-
expect(result).toEqual({
|
|
681
|
-
originalTokenCount,
|
|
682
|
-
newTokenCount,
|
|
683
|
-
});
|
|
684
|
-
// Assert that the chat was reset
|
|
685
|
-
expect(newChat).not.toBe(initialChat);
|
|
686
|
-
});
|
|
687
|
-
});
|
|
688
|
-
describe('sendMessageStream', () => {
|
|
689
|
-
it('should include IDE context when ideModeFeature is enabled', async () => {
|
|
690
|
-
// Arrange
|
|
691
|
-
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
|
692
|
-
workspaceState: {
|
|
693
|
-
openFiles: [
|
|
694
|
-
{
|
|
695
|
-
path: '/path/to/active/file.ts',
|
|
696
|
-
timestamp: Date.now(),
|
|
697
|
-
isActive: true,
|
|
698
|
-
selectedText: 'hello',
|
|
699
|
-
cursor: { line: 5, character: 10 },
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
path: '/path/to/recent/file1.ts',
|
|
703
|
-
timestamp: Date.now(),
|
|
704
|
-
},
|
|
705
|
-
{
|
|
706
|
-
path: '/path/to/recent/file2.ts',
|
|
707
|
-
timestamp: Date.now(),
|
|
708
|
-
},
|
|
709
|
-
],
|
|
710
|
-
},
|
|
711
|
-
});
|
|
712
|
-
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
|
713
|
-
const mockStream = (async function* () {
|
|
714
|
-
yield { type: 'content', value: 'Hello' };
|
|
715
|
-
})();
|
|
716
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
717
|
-
const mockChat = {
|
|
718
|
-
addHistory: vi.fn(),
|
|
719
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
720
|
-
};
|
|
721
|
-
client['chat'] = mockChat;
|
|
722
|
-
const mockGenerator = {
|
|
723
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
724
|
-
generateContent: mockGenerateContentFn,
|
|
725
|
-
};
|
|
726
|
-
client['contentGenerator'] = mockGenerator;
|
|
727
|
-
const initialRequest = [{ text: 'Hi' }];
|
|
728
|
-
// Act
|
|
729
|
-
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
730
|
-
for await (const _ of stream) {
|
|
731
|
-
// consume stream
|
|
732
|
-
}
|
|
733
|
-
// Assert
|
|
734
|
-
expect(ideContext.getIdeContext).toHaveBeenCalled();
|
|
735
|
-
const expectedContext = `
|
|
736
|
-
This is the file that the user is looking at:
|
|
737
|
-
- Path: /path/to/active/file.ts
|
|
738
|
-
This is the cursor position in the file:
|
|
739
|
-
- Cursor Position: Line 5, Character 10
|
|
740
|
-
This is the selected text in the file:
|
|
741
|
-
- hello
|
|
742
|
-
Here are some other files the user has open, with the most recent at the top:
|
|
743
|
-
- /path/to/recent/file1.ts
|
|
744
|
-
- /path/to/recent/file2.ts
|
|
745
|
-
`.trim();
|
|
746
|
-
const expectedRequest = [{ text: expectedContext }, ...initialRequest];
|
|
747
|
-
expect(mockTurnRunFn).toHaveBeenCalledWith(expectedRequest, expect.any(Object));
|
|
748
|
-
});
|
|
749
|
-
it('should not add context if ideModeFeature is enabled but no open files', async () => {
|
|
750
|
-
// Arrange
|
|
751
|
-
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
|
752
|
-
workspaceState: {
|
|
753
|
-
openFiles: [],
|
|
754
|
-
},
|
|
755
|
-
});
|
|
756
|
-
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
|
757
|
-
const mockStream = (async function* () {
|
|
758
|
-
yield { type: 'content', value: 'Hello' };
|
|
759
|
-
})();
|
|
760
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
761
|
-
const mockChat = {
|
|
762
|
-
addHistory: vi.fn(),
|
|
763
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
764
|
-
};
|
|
765
|
-
client['chat'] = mockChat;
|
|
766
|
-
const mockGenerator = {
|
|
767
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
768
|
-
generateContent: mockGenerateContentFn,
|
|
769
|
-
};
|
|
770
|
-
client['contentGenerator'] = mockGenerator;
|
|
771
|
-
const initialRequest = [{ text: 'Hi' }];
|
|
772
|
-
// Act
|
|
773
|
-
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
774
|
-
for await (const _ of stream) {
|
|
775
|
-
// consume stream
|
|
776
|
-
}
|
|
777
|
-
// Assert
|
|
778
|
-
expect(ideContext.getIdeContext).toHaveBeenCalled();
|
|
779
|
-
expect(mockTurnRunFn).toHaveBeenCalledWith(initialRequest, expect.any(Object));
|
|
780
|
-
});
|
|
781
|
-
it('should add context if ideModeFeature is enabled and there is one active file', async () => {
|
|
782
|
-
// Arrange
|
|
783
|
-
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
|
784
|
-
workspaceState: {
|
|
785
|
-
openFiles: [
|
|
786
|
-
{
|
|
787
|
-
path: '/path/to/active/file.ts',
|
|
788
|
-
timestamp: Date.now(),
|
|
789
|
-
isActive: true,
|
|
790
|
-
selectedText: 'hello',
|
|
791
|
-
cursor: { line: 5, character: 10 },
|
|
792
|
-
},
|
|
793
|
-
],
|
|
794
|
-
},
|
|
795
|
-
});
|
|
796
|
-
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
|
797
|
-
const mockStream = (async function* () {
|
|
798
|
-
yield { type: 'content', value: 'Hello' };
|
|
799
|
-
})();
|
|
800
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
801
|
-
const mockChat = {
|
|
802
|
-
addHistory: vi.fn(),
|
|
803
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
804
|
-
};
|
|
805
|
-
client['chat'] = mockChat;
|
|
806
|
-
const mockGenerator = {
|
|
807
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
808
|
-
generateContent: mockGenerateContentFn,
|
|
809
|
-
};
|
|
810
|
-
client['contentGenerator'] = mockGenerator;
|
|
811
|
-
const initialRequest = [{ text: 'Hi' }];
|
|
812
|
-
// Act
|
|
813
|
-
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
814
|
-
for await (const _ of stream) {
|
|
815
|
-
// consume stream
|
|
816
|
-
}
|
|
817
|
-
// Assert
|
|
818
|
-
expect(ideContext.getIdeContext).toHaveBeenCalled();
|
|
819
|
-
const expectedContext = `
|
|
820
|
-
This is the file that the user is looking at:
|
|
821
|
-
- Path: /path/to/active/file.ts
|
|
822
|
-
This is the cursor position in the file:
|
|
823
|
-
- Cursor Position: Line 5, Character 10
|
|
824
|
-
This is the selected text in the file:
|
|
825
|
-
- hello
|
|
826
|
-
`.trim();
|
|
827
|
-
const expectedRequest = [{ text: expectedContext }, ...initialRequest];
|
|
828
|
-
expect(mockTurnRunFn).toHaveBeenCalledWith(expectedRequest, expect.any(Object));
|
|
829
|
-
});
|
|
830
|
-
it('should add context if ideModeFeature is enabled and there are open files but no active file', async () => {
|
|
831
|
-
// Arrange
|
|
832
|
-
vi.mocked(ideContext.getIdeContext).mockReturnValue({
|
|
833
|
-
workspaceState: {
|
|
834
|
-
openFiles: [
|
|
835
|
-
{
|
|
836
|
-
path: '/path/to/recent/file1.ts',
|
|
837
|
-
timestamp: Date.now(),
|
|
838
|
-
},
|
|
839
|
-
{
|
|
840
|
-
path: '/path/to/recent/file2.ts',
|
|
841
|
-
timestamp: Date.now(),
|
|
842
|
-
},
|
|
843
|
-
],
|
|
844
|
-
},
|
|
845
|
-
});
|
|
846
|
-
vi.spyOn(client['config'], 'getIdeModeFeature').mockReturnValue(true);
|
|
847
|
-
const mockStream = (async function* () {
|
|
848
|
-
yield { type: 'content', value: 'Hello' };
|
|
849
|
-
})();
|
|
850
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
851
|
-
const mockChat = {
|
|
852
|
-
addHistory: vi.fn(),
|
|
853
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
854
|
-
};
|
|
855
|
-
client['chat'] = mockChat;
|
|
856
|
-
const mockGenerator = {
|
|
857
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
858
|
-
generateContent: mockGenerateContentFn,
|
|
859
|
-
};
|
|
860
|
-
client['contentGenerator'] = mockGenerator;
|
|
861
|
-
const initialRequest = [{ text: 'Hi' }];
|
|
862
|
-
// Act
|
|
863
|
-
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
864
|
-
for await (const _ of stream) {
|
|
865
|
-
// consume stream
|
|
866
|
-
}
|
|
867
|
-
// Assert
|
|
868
|
-
expect(ideContext.getIdeContext).toHaveBeenCalled();
|
|
869
|
-
const expectedContext = `
|
|
870
|
-
Here are some files the user has open, with the most recent at the top:
|
|
871
|
-
- /path/to/recent/file1.ts
|
|
872
|
-
- /path/to/recent/file2.ts
|
|
873
|
-
`.trim();
|
|
874
|
-
const expectedRequest = [{ text: expectedContext }, ...initialRequest];
|
|
875
|
-
expect(mockTurnRunFn).toHaveBeenCalledWith(expectedRequest, expect.any(Object));
|
|
876
|
-
});
|
|
877
|
-
it('should return the turn instance after the stream is complete', async () => {
|
|
878
|
-
// Arrange
|
|
879
|
-
const mockStream = (async function* () {
|
|
880
|
-
yield { type: 'content', value: 'Hello' };
|
|
881
|
-
})();
|
|
882
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
883
|
-
const mockChat = {
|
|
884
|
-
addHistory: vi.fn(),
|
|
885
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
886
|
-
};
|
|
887
|
-
client['chat'] = mockChat;
|
|
888
|
-
const mockGenerator = {
|
|
889
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
890
|
-
generateContent: vi.fn(),
|
|
891
|
-
generateContentStream: vi.fn(),
|
|
892
|
-
embedContent: vi.fn(),
|
|
893
|
-
};
|
|
894
|
-
client['contentGenerator'] = mockGenerator;
|
|
895
|
-
// Act
|
|
896
|
-
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
|
|
897
|
-
// Consume the stream manually to get the final return value.
|
|
898
|
-
let finalResult;
|
|
899
|
-
while (true) {
|
|
900
|
-
const result = await stream.next();
|
|
901
|
-
if (result.done) {
|
|
902
|
-
finalResult = result.value;
|
|
903
|
-
break;
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
// Assert
|
|
907
|
-
expect(finalResult).toBeInstanceOf(Turn);
|
|
908
|
-
});
|
|
909
|
-
it.skip('should stop infinite loop after MAX_TURNS when nextSpeaker always returns model', async () => {
|
|
910
|
-
// Get the mocked checkNextSpeaker function and configure it to trigger infinite loop
|
|
911
|
-
const { checkNextSpeaker } = await import('../utils/nextSpeakerChecker.js');
|
|
912
|
-
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
|
|
913
|
-
mockCheckNextSpeaker.mockResolvedValue({
|
|
914
|
-
next_speaker: 'model',
|
|
915
|
-
reasoning: 'Test case - always continue',
|
|
916
|
-
});
|
|
917
|
-
// Mock provider manager to return 'gemini' provider
|
|
918
|
-
const mockProviderManager = {
|
|
919
|
-
getActiveProviderName: vi.fn().mockReturnValue('gemini'),
|
|
920
|
-
getActiveProvider: vi.fn().mockReturnValue(null),
|
|
921
|
-
};
|
|
922
|
-
const mockContentGenConfig = {
|
|
923
|
-
model: 'test-model',
|
|
924
|
-
providerManager: mockProviderManager,
|
|
925
|
-
};
|
|
926
|
-
vi.spyOn(client['config'], 'getContentGeneratorConfig').mockReturnValue(mockContentGenConfig);
|
|
927
|
-
// Mock Turn to have no pending tool calls (which would allow nextSpeaker check)
|
|
928
|
-
const mockStream = (async function* () {
|
|
929
|
-
yield { type: 'content', value: 'Continue...' };
|
|
930
|
-
})();
|
|
931
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
932
|
-
const mockChat = {
|
|
933
|
-
addHistory: vi.fn(),
|
|
934
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
935
|
-
};
|
|
936
|
-
client['chat'] = mockChat;
|
|
937
|
-
const mockGenerator = {
|
|
938
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
939
|
-
};
|
|
940
|
-
client['contentGenerator'] = mockGenerator;
|
|
941
|
-
// Use a signal that never gets aborted
|
|
942
|
-
const abortController = new AbortController();
|
|
943
|
-
const signal = abortController.signal;
|
|
944
|
-
// Act - Start the stream that should loop
|
|
945
|
-
const stream = client.sendMessageStream([{ text: 'Start conversation' }], signal, 'prompt-id-2');
|
|
946
|
-
// Count how many stream events we get
|
|
947
|
-
let eventCount = 0;
|
|
948
|
-
let finalResult;
|
|
949
|
-
// Consume the stream and count iterations
|
|
950
|
-
while (true) {
|
|
951
|
-
const result = await stream.next();
|
|
952
|
-
if (result.done) {
|
|
953
|
-
finalResult = result.value;
|
|
954
|
-
break;
|
|
955
|
-
}
|
|
956
|
-
eventCount++;
|
|
957
|
-
// Safety check to prevent actual infinite loop in test
|
|
958
|
-
if (eventCount > 200) {
|
|
959
|
-
abortController.abort();
|
|
960
|
-
throw new Error('Test exceeded expected event limit - possible actual infinite loop');
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
// Assert
|
|
964
|
-
expect(finalResult).toBeInstanceOf(Turn);
|
|
965
|
-
// Debug: Check how many times checkNextSpeaker was called
|
|
966
|
-
const callCount = mockCheckNextSpeaker.mock.calls.length;
|
|
967
|
-
// If infinite loop protection is working, checkNextSpeaker should be called many times
|
|
968
|
-
// but stop at MAX_TURNS (100). Since each recursive call should trigger checkNextSpeaker,
|
|
969
|
-
// we expect it to be called multiple times before hitting the limit
|
|
970
|
-
expect(mockCheckNextSpeaker).toHaveBeenCalled();
|
|
971
|
-
// The test should demonstrate that the infinite loop protection works:
|
|
972
|
-
// - If checkNextSpeaker is called many times (close to MAX_TURNS), it shows the loop was happening
|
|
973
|
-
// - If it's only called once, the recursive behavior might not be triggered
|
|
974
|
-
if (callCount === 0) {
|
|
975
|
-
throw new Error('checkNextSpeaker was never called - the recursive condition was not met');
|
|
976
|
-
}
|
|
977
|
-
else if (callCount === 1) {
|
|
978
|
-
// This might be expected behavior if the turn has pending tool calls or other conditions prevent recursion
|
|
979
|
-
console.log('checkNextSpeaker called only once - no infinite loop occurred');
|
|
980
|
-
}
|
|
981
|
-
else {
|
|
982
|
-
console.log(`checkNextSpeaker called ${callCount} times - infinite loop protection worked`);
|
|
983
|
-
// If called multiple times, we expect it to be stopped before MAX_TURNS
|
|
984
|
-
expect(callCount).toBeLessThanOrEqual(100); // Should not exceed MAX_TURNS
|
|
985
|
-
}
|
|
986
|
-
// The stream should produce events and eventually terminate
|
|
987
|
-
expect(eventCount).toBeGreaterThanOrEqual(1);
|
|
988
|
-
expect(eventCount).toBeLessThan(200); // Should not exceed our safety limit
|
|
989
|
-
});
|
|
990
|
-
it('should yield MaxSessionTurns and stop when session turn limit is reached', async () => {
|
|
991
|
-
// Arrange
|
|
992
|
-
const MAX_SESSION_TURNS = 5;
|
|
993
|
-
vi.spyOn(client['config'], 'getMaxSessionTurns').mockReturnValue(MAX_SESSION_TURNS);
|
|
994
|
-
const mockStream = (async function* () {
|
|
995
|
-
yield { type: 'content', value: 'Hello' };
|
|
996
|
-
})();
|
|
997
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
998
|
-
const mockChat = {
|
|
999
|
-
addHistory: vi.fn(),
|
|
1000
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
1001
|
-
};
|
|
1002
|
-
client['chat'] = mockChat;
|
|
1003
|
-
const mockGenerator = {
|
|
1004
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1005
|
-
};
|
|
1006
|
-
client['contentGenerator'] = mockGenerator;
|
|
1007
|
-
// Act & Assert
|
|
1008
|
-
// Run up to the limit
|
|
1009
|
-
for (let i = 0; i < MAX_SESSION_TURNS; i++) {
|
|
1010
|
-
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-4');
|
|
1011
|
-
// consume stream
|
|
1012
|
-
for await (const _event of stream) {
|
|
1013
|
-
// do nothing
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
// This call should exceed the limit
|
|
1017
|
-
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-5');
|
|
1018
|
-
const events = [];
|
|
1019
|
-
for await (const event of stream) {
|
|
1020
|
-
events.push(event);
|
|
1021
|
-
}
|
|
1022
|
-
expect(events).toEqual([{ type: GeminiEventType.MaxSessionTurns }]);
|
|
1023
|
-
expect(mockTurnRunFn).toHaveBeenCalledTimes(MAX_SESSION_TURNS);
|
|
1024
|
-
});
|
|
1025
|
-
it.skip('should respect MAX_TURNS limit even when turns parameter is set to a large value', async () => {
|
|
1026
|
-
// This test verifies that the infinite loop protection works even when
|
|
1027
|
-
// someone tries to bypass it by calling with a very large turns value
|
|
1028
|
-
// Get the mocked checkNextSpeaker function and configure it to trigger infinite loop
|
|
1029
|
-
const { checkNextSpeaker } = await import('../utils/nextSpeakerChecker.js');
|
|
1030
|
-
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
|
|
1031
|
-
mockCheckNextSpeaker.mockResolvedValue({
|
|
1032
|
-
next_speaker: 'model',
|
|
1033
|
-
reasoning: 'Test case - always continue',
|
|
1034
|
-
});
|
|
1035
|
-
// Mock Turn to have no pending tool calls (which would allow nextSpeaker check)
|
|
1036
|
-
const mockStream = (async function* () {
|
|
1037
|
-
yield { type: 'content', value: 'Continue...' };
|
|
1038
|
-
})();
|
|
1039
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
1040
|
-
const mockChat = {
|
|
1041
|
-
addHistory: vi.fn(),
|
|
1042
|
-
getHistory: vi.fn().mockReturnValue([]),
|
|
1043
|
-
};
|
|
1044
|
-
client['chat'] = mockChat;
|
|
1045
|
-
const mockGenerator = {
|
|
1046
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1047
|
-
};
|
|
1048
|
-
client['contentGenerator'] = mockGenerator;
|
|
1049
|
-
// Use a signal that never gets aborted
|
|
1050
|
-
const abortController = new AbortController();
|
|
1051
|
-
const signal = abortController.signal;
|
|
1052
|
-
// Act - Start the stream with an extremely high turns value
|
|
1053
|
-
// This simulates a case where the turns protection is bypassed
|
|
1054
|
-
const stream = client.sendMessageStream([{ text: 'Start conversation' }], signal, 'prompt-id-3', Number.MAX_SAFE_INTEGER);
|
|
1055
|
-
// Count how many stream events we get
|
|
1056
|
-
let eventCount = 0;
|
|
1057
|
-
const maxTestIterations = 1000; // Higher limit to show the loop continues
|
|
1058
|
-
// Consume the stream and count iterations
|
|
1059
|
-
try {
|
|
1060
|
-
while (true) {
|
|
1061
|
-
const result = await stream.next();
|
|
1062
|
-
if (result.done) {
|
|
1063
|
-
break;
|
|
1064
|
-
}
|
|
1065
|
-
eventCount++;
|
|
1066
|
-
// This test should hit this limit, demonstrating the infinite loop
|
|
1067
|
-
if (eventCount > maxTestIterations) {
|
|
1068
|
-
abortController.abort();
|
|
1069
|
-
// This is the expected behavior - we hit the infinite loop
|
|
1070
|
-
break;
|
|
1071
|
-
}
|
|
1072
|
-
}
|
|
1073
|
-
}
|
|
1074
|
-
catch (error) {
|
|
1075
|
-
// If the test framework times out, that also demonstrates the infinite loop
|
|
1076
|
-
console.error('Test timed out or errored:', error);
|
|
1077
|
-
}
|
|
1078
|
-
// Assert that the fix works - the loop should stop at MAX_TURNS
|
|
1079
|
-
const callCount = mockCheckNextSpeaker.mock.calls.length;
|
|
1080
|
-
// With the fix: even when turns is set to a very high value,
|
|
1081
|
-
// the loop should stop at MAX_TURNS (100)
|
|
1082
|
-
expect(callCount).toBeLessThanOrEqual(100); // Should not exceed MAX_TURNS
|
|
1083
|
-
expect(eventCount).toBeLessThanOrEqual(200); // Should have reasonable number of events
|
|
1084
|
-
console.log(`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
|
|
1085
|
-
`${eventCount} events generated (properly bounded by MAX_TURNS)`);
|
|
1086
|
-
});
|
|
1087
|
-
});
|
|
1088
|
-
describe('generateContent model usage', () => {
|
|
1089
|
-
it('should use current model from config for content generation', async () => {
|
|
1090
|
-
const initialModel = client['config'].getModel();
|
|
1091
|
-
const contents = [{ role: 'user', parts: [{ text: 'test' }] }];
|
|
1092
|
-
const currentModel = initialModel + '-changed';
|
|
1093
|
-
// Mock getModel to return the changed model when called during generateContent
|
|
1094
|
-
vi.spyOn(client['config'], 'getModel').mockReturnValue(currentModel);
|
|
1095
|
-
// Mock the retryWithBackoff to directly call the apiCall function
|
|
1096
|
-
vi.mock('../utils/retry.js', () => ({
|
|
1097
|
-
retryWithBackoff: vi.fn((apiCall) => apiCall()),
|
|
1098
|
-
}));
|
|
1099
|
-
const mockContentGeneratorGenerateContent = vi.fn().mockResolvedValue({
|
|
1100
|
-
candidates: [
|
|
1101
|
-
{
|
|
1102
|
-
content: {
|
|
1103
|
-
parts: [{ text: 'response' }],
|
|
1104
|
-
},
|
|
1105
|
-
},
|
|
1106
|
-
],
|
|
1107
|
-
});
|
|
1108
|
-
const mockGenerator = {
|
|
1109
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
1110
|
-
generateContent: mockContentGeneratorGenerateContent,
|
|
1111
|
-
};
|
|
1112
|
-
client['contentGenerator'] = mockGenerator;
|
|
1113
|
-
client['isInitialized'] = vi.fn().mockReturnValue(true);
|
|
1114
|
-
const { retryWithBackoff } = await import('../utils/retry.js');
|
|
1115
|
-
vi.mocked(retryWithBackoff).mockImplementation(async (apiCall) => await apiCall());
|
|
1116
|
-
await client.generateContent(contents, {}, new AbortController().signal);
|
|
1117
|
-
// Verify the mock was called
|
|
1118
|
-
expect(mockContentGeneratorGenerateContent).toHaveBeenCalledTimes(1);
|
|
1119
|
-
// Get the actual call arguments
|
|
1120
|
-
const actualCall = mockContentGeneratorGenerateContent.mock.calls[0];
|
|
1121
|
-
// Assert on the model specifically
|
|
1122
|
-
expect(actualCall[0].model).toBe(currentModel);
|
|
1123
|
-
expect(actualCall[0].model).not.toBe(initialModel);
|
|
1124
|
-
// Verify other expected properties exist
|
|
1125
|
-
expect(actualCall[0]).toHaveProperty('contents', contents);
|
|
1126
|
-
expect(actualCall[0]).toHaveProperty('config');
|
|
1127
|
-
expect(actualCall[0].config).toHaveProperty('abortSignal');
|
|
1128
|
-
expect(actualCall[0].config).toHaveProperty('systemInstruction');
|
|
1129
|
-
// Verify prompt_id was passed
|
|
1130
|
-
expect(actualCall[1]).toBe('test-session-id');
|
|
1131
|
-
});
|
|
1132
|
-
});
|
|
1133
|
-
describe('tryCompressChat model usage', () => {
|
|
1134
|
-
it('should use current model from config for token counting after sendMessage', async () => {
|
|
1135
|
-
const initialModel = client['config'].getModel();
|
|
1136
|
-
const mockCountTokens = vi
|
|
1137
|
-
.fn()
|
|
1138
|
-
.mockResolvedValueOnce({ totalTokens: 100000 })
|
|
1139
|
-
.mockResolvedValueOnce({ totalTokens: 5000 });
|
|
1140
|
-
const mockSendMessage = vi.fn().mockResolvedValue({ text: 'Summary' });
|
|
1141
|
-
const mockChatHistory = [
|
|
1142
|
-
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
1143
|
-
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
1144
|
-
];
|
|
1145
|
-
const mockChat = {
|
|
1146
|
-
getHistory: vi.fn().mockReturnValue(mockChatHistory),
|
|
1147
|
-
setHistory: vi.fn(),
|
|
1148
|
-
sendMessage: mockSendMessage,
|
|
1149
|
-
};
|
|
1150
|
-
const mockGenerator = {
|
|
1151
|
-
countTokens: mockCountTokens,
|
|
1152
|
-
};
|
|
1153
|
-
// mock the model has been changed between calls of `countTokens`
|
|
1154
|
-
const firstCurrentModel = initialModel + '-changed-1';
|
|
1155
|
-
const secondCurrentModel = initialModel + '-changed-2';
|
|
1156
|
-
vi.spyOn(client['config'], 'getModel')
|
|
1157
|
-
.mockReturnValueOnce(firstCurrentModel)
|
|
1158
|
-
.mockReturnValueOnce(secondCurrentModel);
|
|
1159
|
-
client['chat'] = mockChat;
|
|
1160
|
-
client['contentGenerator'] = mockGenerator;
|
|
1161
|
-
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
|
1162
|
-
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
1163
|
-
expect(mockCountTokens).toHaveBeenCalledTimes(2);
|
|
1164
|
-
expect(mockCountTokens).toHaveBeenNthCalledWith(1, {
|
|
1165
|
-
model: firstCurrentModel,
|
|
1166
|
-
contents: mockChatHistory,
|
|
1167
|
-
});
|
|
1168
|
-
expect(mockCountTokens).toHaveBeenNthCalledWith(2, {
|
|
1169
|
-
model: secondCurrentModel,
|
|
1170
|
-
contents: expect.any(Array),
|
|
1171
|
-
});
|
|
1172
|
-
expect(result).toEqual({
|
|
1173
|
-
originalTokenCount: 100000,
|
|
1174
|
-
newTokenCount: 5000,
|
|
1175
|
-
});
|
|
1176
|
-
});
|
|
1177
|
-
});
|
|
1178
|
-
describe('handleFlashFallback', () => {
|
|
1179
|
-
it('should use current model from config when checking for fallback', async () => {
|
|
1180
|
-
const initialModel = client['config'].getModel();
|
|
1181
|
-
const fallbackModel = DEFAULT_GEMINI_FLASH_MODEL;
|
|
1182
|
-
// mock config been changed
|
|
1183
|
-
const currentModel = initialModel + '-changed';
|
|
1184
|
-
const getModelSpy = vi.spyOn(client['config'], 'getModel');
|
|
1185
|
-
getModelSpy.mockReturnValue(currentModel);
|
|
1186
|
-
const mockFallbackHandler = vi.fn().mockResolvedValue(true);
|
|
1187
|
-
client['config'].flashFallbackHandler = mockFallbackHandler;
|
|
1188
|
-
client['config'].setModel = vi.fn();
|
|
1189
|
-
const result = await client['handleFlashFallback'](AuthType.LOGIN_WITH_GOOGLE);
|
|
1190
|
-
expect(result).toBe(fallbackModel);
|
|
1191
|
-
expect(mockFallbackHandler).toHaveBeenCalledWith(currentModel, fallbackModel, undefined);
|
|
1192
|
-
});
|
|
1193
|
-
});
|
|
1194
|
-
// TODO: Re-enable when updateModel method is implemented
|
|
1195
|
-
describe.skip('updateModel', () => {
|
|
1196
|
-
it('should update model in config and reinitialize chat', async () => {
|
|
1197
|
-
// Arrange
|
|
1198
|
-
const mockSetModel = vi.fn();
|
|
1199
|
-
const mockConfig = {
|
|
1200
|
-
getModel: vi.fn().mockReturnValue('gemini-2.5-pro'),
|
|
1201
|
-
setModel: mockSetModel,
|
|
1202
|
-
getProjectRoot: vi.fn().mockReturnValue('/test'),
|
|
1203
|
-
getWorkingDir: vi.fn().mockReturnValue('/test'),
|
|
1204
|
-
getFullContext: vi.fn().mockReturnValue(false),
|
|
1205
|
-
getUserMemory: vi.fn().mockReturnValue(''),
|
|
1206
|
-
getLlxprtMdFileCount: vi.fn().mockReturnValue(0),
|
|
1207
|
-
getFileService: vi.fn().mockReturnValue(null),
|
|
1208
|
-
getCheckpointingEnabled: vi.fn().mockReturnValue(false),
|
|
1209
|
-
getToolRegistry: vi.fn().mockResolvedValue({
|
|
1210
|
-
generateSchema: vi.fn().mockReturnValue([]),
|
|
1211
|
-
getToolTelemetry: vi.fn().mockReturnValue([]),
|
|
1212
|
-
getFunctionDeclarations: vi.fn().mockReturnValue([]),
|
|
1213
|
-
}),
|
|
1214
|
-
};
|
|
1215
|
-
client['config'] = mockConfig;
|
|
1216
|
-
// Mock the content generator and chat
|
|
1217
|
-
const mockContentGenerator = {
|
|
1218
|
-
generateContent: vi.fn(),
|
|
1219
|
-
generateContentStream: vi.fn(),
|
|
1220
|
-
countTokens: vi.fn(),
|
|
1221
|
-
embedContent: vi.fn(),
|
|
1222
|
-
};
|
|
1223
|
-
client['contentGenerator'] = mockContentGenerator;
|
|
1224
|
-
// const initialChat = client['chat'];
|
|
1225
|
-
// Act
|
|
1226
|
-
// await client.updateModel('gemini-2.5-flash');
|
|
1227
|
-
// Assert
|
|
1228
|
-
// expect(mockSetModel).toHaveBeenCalledWith('gemini-2.5-flash');
|
|
1229
|
-
// expect(client['model']).toBe('gemini-2.5-flash');
|
|
1230
|
-
// expect(client['chat']).not.toBe(initialChat); // Chat should be reinitialized
|
|
1231
|
-
// Skip test - updateModel method not implemented yet
|
|
1232
|
-
expect(true).toBe(true);
|
|
1233
|
-
});
|
|
1234
|
-
});
|
|
1235
|
-
// TODO: Re-enable when listAvailableModels method is implemented
|
|
1236
|
-
describe.skip('listAvailableModels', () => {
|
|
1237
|
-
beforeEach(() => {
|
|
1238
|
-
global.fetch = vi.fn();
|
|
1239
|
-
});
|
|
1240
|
-
afterEach(() => {
|
|
1241
|
-
vi.restoreAllMocks();
|
|
1242
|
-
});
|
|
1243
|
-
it('should fetch models from API for GEMINI auth type', async () => {
|
|
1244
|
-
// Arrange
|
|
1245
|
-
const mockModels = [
|
|
1246
|
-
{ name: 'models/gemini-2.5-pro', displayName: 'Gemini 2.5 Pro' },
|
|
1247
|
-
{ name: 'models/gemini-2.5-flash', displayName: 'Gemini 2.5 Flash' },
|
|
1248
|
-
];
|
|
1249
|
-
const mockConfig = {
|
|
1250
|
-
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
|
1251
|
-
authType: AuthType.USE_GEMINI,
|
|
1252
|
-
apiKey: 'test-api-key',
|
|
1253
|
-
}),
|
|
1254
|
-
};
|
|
1255
|
-
client['config'] = mockConfig;
|
|
1256
|
-
global.fetch.mockResolvedValue({
|
|
1257
|
-
ok: true,
|
|
1258
|
-
json: vi.fn().mockResolvedValue({ models: mockModels }),
|
|
1259
|
-
});
|
|
1260
|
-
// Act
|
|
1261
|
-
// const models = await client.listAvailableModels();
|
|
1262
|
-
const models = []; // Placeholder - listAvailableModels not implemented
|
|
1263
|
-
// Assert
|
|
1264
|
-
expect(global.fetch).toHaveBeenCalledWith('https://generativelanguage.googleapis.com/v1beta/models?key=test-api-key', expect.objectContaining({
|
|
1265
|
-
method: 'GET',
|
|
1266
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1267
|
-
}));
|
|
1268
|
-
expect(models).toEqual(mockModels);
|
|
1269
|
-
});
|
|
1270
|
-
it('should return OAuth marker for OAuth auth types', async () => {
|
|
1271
|
-
// Arrange
|
|
1272
|
-
const mockConfig = {
|
|
1273
|
-
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
|
1274
|
-
authType: AuthType.LOGIN_WITH_GOOGLE,
|
|
1275
|
-
}),
|
|
1276
|
-
};
|
|
1277
|
-
client['config'] = mockConfig;
|
|
1278
|
-
// Act
|
|
1279
|
-
// const models = await client.listAvailableModels();
|
|
1280
|
-
const models = []; // Placeholder - listAvailableModels not implemented
|
|
1281
|
-
// Assert
|
|
1282
|
-
expect(models).toEqual([
|
|
1283
|
-
{
|
|
1284
|
-
name: 'oauth-not-supported',
|
|
1285
|
-
displayName: 'OAuth Authentication',
|
|
1286
|
-
description: 'Model listing is not available with OAuth authentication',
|
|
1287
|
-
},
|
|
1288
|
-
]);
|
|
1289
|
-
});
|
|
1290
|
-
it('should return empty array when API call fails', async () => {
|
|
1291
|
-
// Arrange
|
|
1292
|
-
const mockConfig = {
|
|
1293
|
-
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
|
1294
|
-
authType: AuthType.USE_GEMINI,
|
|
1295
|
-
apiKey: 'test-api-key',
|
|
1296
|
-
}),
|
|
1297
|
-
};
|
|
1298
|
-
client['config'] = mockConfig;
|
|
1299
|
-
global.fetch.mockRejectedValue(new Error('Network error'));
|
|
1300
|
-
// Act
|
|
1301
|
-
// const models = await client.listAvailableModels();
|
|
1302
|
-
const models = []; // Placeholder - listAvailableModels not implemented
|
|
1303
|
-
// Assert
|
|
1304
|
-
expect(models).toEqual([]);
|
|
1305
|
-
});
|
|
1306
|
-
it('should return empty array for unsupported auth type', async () => {
|
|
1307
|
-
// Arrange
|
|
1308
|
-
const mockConfig = {
|
|
1309
|
-
getContentGeneratorConfig: vi.fn().mockReturnValue({
|
|
1310
|
-
authType: undefined,
|
|
1311
|
-
}),
|
|
1312
|
-
};
|
|
1313
|
-
client['config'] = mockConfig;
|
|
1314
|
-
// Act
|
|
1315
|
-
// const models = await client.listAvailableModels();
|
|
1316
|
-
const models = []; // Placeholder - listAvailableModels not implemented
|
|
1317
|
-
// Assert
|
|
1318
|
-
expect(models).toEqual([]);
|
|
1319
|
-
});
|
|
1320
|
-
});
|
|
1321
|
-
});
|
|
1322
|
-
//# sourceMappingURL=client.test.js.map
|