@office-ai/aioncli-core 0.2.2 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +10 -3
- package/dist/index.js +10 -3
- package/dist/index.js.map +1 -1
- package/dist/src/agents/codebase-investigator.d.ts +11 -0
- package/dist/src/agents/codebase-investigator.js +73 -0
- package/dist/src/agents/codebase-investigator.js.map +1 -0
- package/dist/src/agents/executor.d.ts +88 -0
- package/dist/src/agents/executor.js +417 -0
- package/dist/src/agents/executor.js.map +1 -0
- package/dist/src/agents/executor.test.js +419 -0
- package/dist/src/agents/executor.test.js.map +1 -0
- package/dist/src/agents/invocation.d.ts +43 -0
- package/dist/src/agents/invocation.js +100 -0
- package/dist/src/agents/invocation.js.map +1 -0
- package/dist/src/agents/invocation.test.js +206 -0
- package/dist/src/agents/invocation.test.js.map +1 -0
- package/dist/src/agents/registry.d.ts +35 -0
- package/dist/src/agents/registry.js +58 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/agents/registry.test.js +146 -0
- package/dist/src/agents/registry.test.js.map +1 -0
- package/dist/src/agents/schema-utils.d.ts +39 -0
- package/dist/src/agents/schema-utils.js +57 -0
- package/dist/src/agents/schema-utils.js.map +1 -0
- package/dist/src/agents/schema-utils.test.d.ts +6 -0
- package/dist/src/agents/schema-utils.test.js +144 -0
- package/dist/src/agents/schema-utils.test.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.d.ts +36 -0
- package/dist/src/agents/subagent-tool-wrapper.js +47 -0
- package/dist/src/agents/subagent-tool-wrapper.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.test.d.ts +6 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js +105 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -0
- package/dist/src/agents/types.d.ts +116 -0
- package/dist/src/agents/types.js +17 -0
- package/dist/src/agents/types.js.map +1 -0
- package/dist/src/agents/utils.d.ts +15 -0
- package/dist/src/agents/utils.js +29 -0
- package/dist/src/agents/utils.js.map +1 -0
- package/dist/src/agents/utils.test.d.ts +6 -0
- package/dist/src/agents/utils.test.js +87 -0
- package/dist/src/agents/utils.test.js.map +1 -0
- package/dist/src/code_assist/codeAssist.d.ts +6 -3
- package/dist/src/code_assist/codeAssist.js +12 -0
- package/dist/src/code_assist/codeAssist.js.map +1 -1
- package/dist/src/code_assist/converter.d.ts +4 -1
- package/dist/src/code_assist/converter.js +38 -5
- package/dist/src/code_assist/converter.js.map +1 -1
- package/dist/src/code_assist/converter.test.js +93 -0
- package/dist/src/code_assist/converter.test.js.map +1 -1
- package/dist/src/code_assist/oauth-credential-storage.d.ts +25 -0
- package/dist/src/code_assist/oauth-credential-storage.js +109 -0
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -0
- package/dist/src/code_assist/oauth-credential-storage.test.d.ts +6 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js +136 -0
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -0
- package/dist/src/code_assist/oauth2.d.ts +1 -1
- package/dist/src/code_assist/oauth2.js +107 -48
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +735 -343
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/server.d.ts +4 -4
- package/dist/src/code_assist/server.js +25 -2
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +25 -0
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/code_assist/setup.d.ts +1 -1
- package/dist/src/code_assist/setup.js +1 -1
- package/dist/src/code_assist/setup.js.map +1 -1
- package/dist/src/code_assist/setup.test.js.map +1 -1
- package/dist/src/code_assist/types.d.ts +17 -2
- package/dist/src/config/config.d.ts +121 -25
- package/dist/src/config/config.js +298 -87
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +370 -131
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/constants.d.ts +11 -0
- package/dist/src/config/constants.js +16 -0
- package/dist/src/config/constants.js.map +1 -0
- package/dist/src/config/models.d.ts +16 -0
- package/dist/src/config/models.js +29 -0
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.d.ts +6 -0
- package/dist/src/config/models.test.js +55 -0
- package/dist/src/config/models.test.js.map +1 -0
- package/dist/src/config/storage.d.ts +34 -0
- package/dist/src/config/storage.js +95 -0
- package/dist/src/config/storage.js.map +1 -0
- package/dist/src/config/storage.test.d.ts +6 -0
- package/dist/src/config/storage.test.js +47 -0
- package/dist/src/config/storage.test.js.map +1 -0
- package/dist/src/confirmation-bus/index.d.ts +7 -0
- package/dist/src/confirmation-bus/index.js +8 -0
- package/dist/src/confirmation-bus/index.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.d.ts +17 -0
- package/dist/src/confirmation-bus/message-bus.js +81 -0
- package/dist/src/confirmation-bus/message-bus.js.map +1 -0
- package/dist/src/confirmation-bus/message-bus.test.d.ts +6 -0
- package/dist/src/confirmation-bus/message-bus.test.js +164 -0
- package/dist/src/confirmation-bus/message-bus.test.js.map +1 -0
- package/dist/src/confirmation-bus/types.d.ts +38 -0
- package/dist/src/confirmation-bus/types.js +15 -0
- package/dist/src/confirmation-bus/types.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +54 -0
- package/dist/src/core/baseLlmClient.js +190 -0
- package/dist/src/core/baseLlmClient.js.map +1 -0
- package/dist/src/core/baseLlmClient.test.d.ts +6 -0
- package/dist/src/core/baseLlmClient.test.js +316 -0
- package/dist/src/core/baseLlmClient.test.js.map +1 -0
- package/dist/src/core/client.d.ts +28 -28
- package/dist/src/core/client.js +187 -333
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +745 -500
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +4 -4
- package/dist/src/core/contentGenerator.js +6 -7
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +1 -3
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +20 -7
- package/dist/src/core/coreToolScheduler.js +216 -53
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +564 -88
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +54 -43
- package/dist/src/core/geminiChat.js +298 -280
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +1255 -321
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/geminiRequest.js +1 -0
- package/dist/src/core/geminiRequest.js.map +1 -1
- package/dist/src/core/logger.d.ts +4 -2
- package/dist/src/core/logger.js +4 -3
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/logger.test.js +17 -16
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.d.ts +3 -3
- package/dist/src/core/loggingContentGenerator.js +15 -16
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -5
- package/dist/src/core/nonInteractiveToolExecutor.js +14 -122
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +158 -78
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/openaiContentGenerator.d.ts +4 -3
- package/dist/src/core/openaiContentGenerator.js +21 -14
- package/dist/src/core/openaiContentGenerator.js.map +1 -1
- package/dist/src/core/openaiContentGenerator.test.js +1 -0
- package/dist/src/core/openaiContentGenerator.test.js.map +1 -1
- package/dist/src/core/prompts.d.ts +5 -0
- package/dist/src/core/prompts.js +66 -44
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +130 -1
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/subagent.d.ts +24 -18
- package/dist/src/core/subagent.js +125 -90
- package/dist/src/core/subagent.js.map +1 -1
- package/dist/src/core/subagent.test.js +59 -44
- package/dist/src/core/subagent.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +37 -13
- package/dist/src/core/turn.js +63 -28
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +359 -100
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/fallback/handler.d.ts +7 -0
- package/dist/src/fallback/handler.js +129 -0
- package/dist/src/fallback/handler.js.map +1 -0
- package/dist/src/fallback/handler.test.d.ts +6 -0
- package/dist/src/fallback/handler.test.js +130 -0
- package/dist/src/fallback/handler.test.js.map +1 -0
- package/dist/src/fallback/types.d.ts +14 -0
- package/dist/src/fallback/types.js +7 -0
- package/dist/src/fallback/types.js.map +1 -0
- package/dist/src/generated/git-commit.d.ts +1 -1
- package/dist/src/generated/git-commit.js +1 -1
- package/dist/src/ide/constants.d.ts +3 -0
- package/dist/src/ide/constants.js +3 -0
- package/dist/src/ide/constants.js.map +1 -1
- package/dist/src/ide/detect-ide.d.ts +48 -12
- package/dist/src/ide/detect-ide.js +47 -66
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +79 -52
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +69 -23
- package/dist/src/ide/ide-client.js +372 -78
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +375 -30
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.d.ts +2 -2
- package/dist/src/ide/ide-installer.js +37 -24
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +104 -26
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/ide/ideContext.d.ts +35 -365
- package/dist/src/ide/ideContext.js +60 -106
- package/dist/src/ide/ideContext.js.map +1 -1
- package/dist/src/ide/ideContext.test.js +152 -24
- package/dist/src/ide/ideContext.test.js.map +1 -1
- package/dist/src/ide/process-utils.d.ts +7 -5
- package/dist/src/ide/process-utils.js +81 -50
- package/dist/src/ide/process-utils.js.map +1 -1
- package/dist/src/ide/process-utils.test.d.ts +6 -0
- package/dist/src/ide/process-utils.test.js +158 -0
- package/dist/src/ide/process-utils.test.js.map +1 -0
- package/dist/src/ide/types.d.ts +486 -0
- package/dist/src/ide/types.js +138 -0
- package/dist/src/ide/types.js.map +1 -0
- package/dist/src/index.d.ts +20 -2
- package/dist/src/index.js +20 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/google-auth-provider.d.ts +3 -3
- package/dist/src/mcp/google-auth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +17 -13
- package/dist/src/mcp/oauth-provider.js +81 -69
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +212 -37
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.d.ts +14 -32
- package/dist/src/mcp/oauth-token-storage.js +54 -25
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.test.js +256 -162
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
- package/dist/src/mcp/oauth-utils.d.ts +9 -1
- package/dist/src/mcp/oauth-utils.js +42 -27
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/oauth-utils.test.js +41 -1
- package/dist/src/mcp/oauth-utils.test.js.map +1 -1
- package/dist/src/mcp/sa-impersonation-provider.d.ts +33 -0
- package/dist/src/mcp/sa-impersonation-provider.js +130 -0
- package/dist/src/mcp/sa-impersonation-provider.js.map +1 -0
- package/dist/src/mcp/sa-impersonation-provider.test.d.ts +6 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js +117 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js.map +1 -0
- package/dist/src/mcp/token-storage/base-token-storage.d.ts +19 -0
- package/dist/src/mcp/token-storage/base-token-storage.js +36 -0
- package/dist/src/mcp/token-storage/base-token-storage.js.map +1 -0
- package/dist/src/mcp/token-storage/base-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/token-storage/base-token-storage.test.js +160 -0
- package/dist/src/mcp/token-storage/base-token-storage.test.js.map +1 -0
- package/dist/src/mcp/token-storage/file-token-storage.d.ts +24 -0
- package/dist/src/mcp/token-storage/file-token-storage.js +144 -0
- package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -0
- package/dist/src/mcp/token-storage/file-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/token-storage/file-token-storage.test.js +235 -0
- package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.d.ts +23 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.js +78 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.js.map +1 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.js +193 -0
- package/dist/src/mcp/token-storage/hybrid-token-storage.test.js.map +1 -0
- package/dist/src/mcp/token-storage/index.d.ts +11 -0
- package/dist/src/mcp/token-storage/index.js +12 -0
- package/dist/src/mcp/token-storage/index.js.map +1 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.d.ts +31 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.js +190 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.js.map +1 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.test.d.ts +6 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.test.js +254 -0
- package/dist/src/mcp/token-storage/keychain-token-storage.test.js.map +1 -0
- package/dist/src/mcp/token-storage/types.d.ts +38 -0
- package/dist/src/mcp/token-storage/types.js +11 -0
- package/dist/src/mcp/token-storage/types.js.map +1 -0
- package/dist/src/output/json-formatter.d.ts +11 -0
- package/dist/src/output/json-formatter.js +30 -0
- package/dist/src/output/json-formatter.js.map +1 -0
- package/dist/src/output/json-formatter.test.d.ts +6 -0
- package/dist/src/output/json-formatter.test.js +266 -0
- package/dist/src/output/json-formatter.test.js.map +1 -0
- package/dist/src/output/types.d.ts +20 -0
- package/dist/src/output/types.js +11 -0
- package/dist/src/output/types.js.map +1 -0
- package/dist/src/policy/index.d.ts +7 -0
- package/dist/src/policy/index.js +8 -0
- package/dist/src/policy/index.js.map +1 -0
- package/dist/src/policy/policy-engine.d.ts +30 -0
- package/dist/src/policy/policy-engine.js +92 -0
- package/dist/src/policy/policy-engine.js.map +1 -0
- package/dist/src/policy/policy-engine.test.d.ts +6 -0
- package/dist/src/policy/policy-engine.test.js +515 -0
- package/dist/src/policy/policy-engine.test.js.map +1 -0
- package/dist/src/policy/stable-stringify.d.ts +58 -0
- package/dist/src/policy/stable-stringify.js +122 -0
- package/dist/src/policy/stable-stringify.js.map +1 -0
- package/dist/src/policy/types.d.ts +47 -0
- package/dist/src/policy/types.js +12 -0
- package/dist/src/policy/types.js.map +1 -0
- package/dist/src/prompts/mcp-prompts.d.ts +2 -2
- package/dist/src/prompts/prompt-registry.d.ts +1 -1
- package/dist/src/routing/modelRouterService.d.ts +23 -0
- package/dist/src/routing/modelRouterService.js +70 -0
- package/dist/src/routing/modelRouterService.js.map +1 -0
- package/dist/src/routing/modelRouterService.test.d.ts +6 -0
- package/dist/src/routing/modelRouterService.test.js +98 -0
- package/dist/src/routing/modelRouterService.test.js.map +1 -0
- package/dist/src/routing/routingStrategy.d.ts +62 -0
- package/dist/src/routing/routingStrategy.js +7 -0
- package/dist/src/routing/routingStrategy.js.map +1 -0
- package/dist/src/routing/strategies/classifierStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/classifierStrategy.js +173 -0
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -0
- package/dist/src/routing/strategies/classifierStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js +192 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/compositeStrategy.d.ts +26 -0
- package/dist/src/routing/strategies/compositeStrategy.js +68 -0
- package/dist/src/routing/strategies/compositeStrategy.js.map +1 -0
- package/dist/src/routing/strategies/compositeStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/compositeStrategy.test.js +123 -0
- package/dist/src/routing/strategies/compositeStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/defaultStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/defaultStrategy.js +20 -0
- package/dist/src/routing/strategies/defaultStrategy.js.map +1 -0
- package/dist/src/routing/strategies/defaultStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/defaultStrategy.test.js +26 -0
- package/dist/src/routing/strategies/defaultStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/fallbackStrategy.d.ts +12 -0
- package/dist/src/routing/strategies/fallbackStrategy.js +25 -0
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js +55 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -0
- package/dist/src/routing/strategies/overrideStrategy.d.ts +15 -0
- package/dist/src/routing/strategies/overrideStrategy.js +28 -0
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -0
- package/dist/src/routing/strategies/overrideStrategy.test.d.ts +6 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js +42 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -0
- package/dist/src/services/chatRecordingService.d.ts +8 -14
- package/dist/src/services/chatRecordingService.js +33 -21
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/chatRecordingService.test.js +69 -25
- package/dist/src/services/chatRecordingService.test.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.d.ts +10 -0
- package/dist/src/services/fileDiscoveryService.js +32 -18
- package/dist/src/services/fileDiscoveryService.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.test.js +3 -3
- package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
- package/dist/src/services/fileSystemService.d.ts +9 -0
- package/dist/src/services/fileSystemService.js +12 -1
- package/dist/src/services/fileSystemService.js.map +1 -1
- package/dist/src/services/fileSystemService.test.js +1 -1
- package/dist/src/services/fileSystemService.test.js.map +1 -1
- package/dist/src/services/gitService.d.ts +3 -1
- package/dist/src/services/gitService.js +30 -24
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +30 -37
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.d.ts +8 -2
- package/dist/src/services/loopDetectionService.js +64 -24
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +64 -13
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +36 -2
- package/dist/src/services/shellExecutionService.js +238 -47
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +197 -58
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/activity-detector.d.ts +41 -0
- package/dist/src/telemetry/activity-detector.js +61 -0
- package/dist/src/telemetry/activity-detector.js.map +1 -0
- package/dist/src/telemetry/activity-detector.test.d.ts +6 -0
- package/dist/src/telemetry/activity-detector.test.js +136 -0
- package/dist/src/telemetry/activity-detector.test.js.map +1 -0
- package/dist/src/telemetry/activity-types.d.ts +19 -0
- package/dist/src/telemetry/activity-types.js +21 -0
- package/dist/src/telemetry/activity-types.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +34 -4
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +322 -15
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +321 -11
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +51 -2
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +124 -2
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/config.d.ts +31 -0
- package/dist/src/telemetry/config.js +76 -0
- package/dist/src/telemetry/config.js.map +1 -0
- package/dist/src/telemetry/config.test.d.ts +6 -0
- package/dist/src/telemetry/config.test.js +124 -0
- package/dist/src/telemetry/config.test.js.map +1 -0
- package/dist/src/telemetry/constants.d.ts +17 -7
- package/dist/src/telemetry/constants.js +18 -7
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/file-exporters.d.ts +5 -4
- package/dist/src/telemetry/file-exporters.js +1 -1
- package/dist/src/telemetry/file-exporters.js.map +1 -1
- package/dist/src/telemetry/gcp-exporters.d.ts +34 -0
- package/dist/src/telemetry/gcp-exporters.js +117 -0
- package/dist/src/telemetry/gcp-exporters.js.map +1 -0
- package/dist/src/telemetry/gcp-exporters.test.d.ts +6 -0
- package/dist/src/telemetry/gcp-exporters.test.js +318 -0
- package/dist/src/telemetry/gcp-exporters.test.js.map +1 -0
- package/dist/src/telemetry/high-water-mark-tracker.d.ts +43 -0
- package/dist/src/telemetry/high-water-mark-tracker.js +88 -0
- package/dist/src/telemetry/high-water-mark-tracker.js.map +1 -0
- package/dist/src/telemetry/high-water-mark-tracker.test.d.ts +6 -0
- package/dist/src/telemetry/high-water-mark-tracker.test.js +152 -0
- package/dist/src/telemetry/high-water-mark-tracker.test.js.map +1 -0
- package/dist/src/telemetry/index.d.ts +12 -2
- package/dist/src/telemetry/index.js +16 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +17 -2
- package/dist/src/telemetry/loggers.js +316 -14
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +3 -3
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +452 -48
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/metrics.d.ts +323 -12
- package/dist/src/telemetry/metrics.js +464 -83
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +583 -38
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/rate-limiter.d.ts +48 -0
- package/dist/src/telemetry/rate-limiter.js +100 -0
- package/dist/src/telemetry/rate-limiter.js.map +1 -0
- package/dist/src/telemetry/rate-limiter.test.d.ts +6 -0
- package/dist/src/telemetry/rate-limiter.test.js +207 -0
- package/dist/src/telemetry/rate-limiter.test.js.map +1 -0
- package/dist/src/telemetry/sdk.d.ts +1 -1
- package/dist/src/telemetry/sdk.js +20 -2
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +108 -0
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/telemetry-utils.d.ts +6 -0
- package/dist/src/telemetry/telemetry-utils.js +14 -0
- package/dist/src/telemetry/telemetry-utils.js.map +1 -0
- package/dist/src/telemetry/telemetry-utils.test.d.ts +6 -0
- package/dist/src/telemetry/telemetry-utils.test.js +40 -0
- package/dist/src/telemetry/telemetry-utils.test.js.map +1 -0
- package/dist/src/telemetry/types.d.ts +136 -8
- package/dist/src/telemetry/types.js +233 -11
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +3 -3
- package/dist/src/telemetry/uiTelemetry.js +7 -8
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +33 -29
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/config.d.ts +2 -1
- package/dist/src/test-utils/config.js.map +1 -1
- package/dist/src/test-utils/index.d.ts +6 -0
- package/dist/src/test-utils/index.js +7 -0
- package/dist/src/test-utils/index.js.map +1 -0
- package/dist/src/test-utils/mock-tool.d.ts +66 -0
- package/dist/src/test-utils/{tools.js → mock-tool.js} +45 -29
- package/dist/src/test-utils/mock-tool.js.map +1 -0
- package/dist/src/test-utils/mockWorkspaceContext.d.ts +1 -1
- package/dist/src/tools/diffOptions.d.ts +1 -1
- package/dist/src/tools/diffOptions.js +21 -13
- package/dist/src/tools/diffOptions.js.map +1 -1
- package/dist/src/tools/diffOptions.test.js +58 -22
- package/dist/src/tools/diffOptions.test.js.map +1 -1
- package/dist/src/tools/edit.d.ts +6 -5
- package/dist/src/tools/edit.js +58 -40
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +192 -16
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +7 -2
- package/dist/src/tools/glob.js +42 -23
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +80 -4
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/grep.d.ts +3 -2
- package/dist/src/tools/grep.js +35 -15
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/grep.test.js +26 -3
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/ls.d.ts +3 -2
- package/dist/src/tools/ls.js +31 -39
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/ls.test.js +145 -280
- package/dist/src/tools/ls.test.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.d.ts +8 -6
- package/dist/src/tools/mcp-client-manager.js +13 -4
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.test.js +20 -1
- package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +18 -21
- package/dist/src/tools/mcp-client.js +87 -120
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +32 -152
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +6 -4
- package/dist/src/tools/mcp-tool.js +51 -13
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +166 -12
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +3 -2
- package/dist/src/tools/memoryTool.js +14 -37
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/memoryTool.test.js +16 -4
- package/dist/src/tools/memoryTool.test.js.map +1 -1
- package/dist/src/tools/message-bus-integration.test.d.ts +6 -0
- package/dist/src/tools/message-bus-integration.test.js +183 -0
- package/dist/src/tools/message-bus-integration.test.js.map +1 -0
- package/dist/src/tools/modifiable-tool.d.ts +2 -2
- package/dist/src/tools/modifiable-tool.js +3 -3
- package/dist/src/tools/modifiable-tool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.test.js +4 -4
- package/dist/src/tools/modifiable-tool.test.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +3 -2
- package/dist/src/tools/read-file.js +33 -43
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +39 -7
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +3 -2
- package/dist/src/tools/read-many-files.js +52 -107
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +64 -11
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +55 -0
- package/dist/src/tools/ripGrep.js +393 -0
- package/dist/src/tools/ripGrep.js.map +1 -0
- package/dist/src/tools/ripGrep.test.d.ts +6 -0
- package/dist/src/tools/ripGrep.test.js +976 -0
- package/dist/src/tools/ripGrep.test.js.map +1 -0
- package/dist/src/tools/shell.d.ts +13 -2
- package/dist/src/tools/shell.js +42 -32
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +57 -75
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +91 -0
- package/dist/src/tools/smart-edit.js +702 -0
- package/dist/src/tools/smart-edit.js.map +1 -0
- package/dist/src/tools/smart-edit.test.d.ts +6 -0
- package/dist/src/tools/smart-edit.test.js +542 -0
- package/dist/src/tools/smart-edit.test.js.map +1 -0
- package/dist/src/tools/tool-error.d.ts +18 -1
- package/dist/src/tools/tool-error.js +27 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-registry.d.ts +10 -4
- package/dist/src/tools/tool-registry.js +20 -7
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +93 -10
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +33 -16
- package/dist/src/tools/tools.js +115 -5
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/tools.test.js +1 -2
- package/dist/src/tools/tools.test.js.map +1 -1
- package/dist/src/tools/web-fetch.d.ts +3 -2
- package/dist/src/tools/web-fetch.js +14 -10
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +55 -16
- package/dist/src/tools/web-fetch.test.js.map +1 -1
- package/dist/src/tools/web-search.d.ts +4 -3
- package/dist/src/tools/web-search.js +31 -8
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/web-search.test.js +69 -1
- package/dist/src/tools/web-search.test.js.map +1 -1
- package/dist/src/tools/write-file.d.ts +4 -3
- package/dist/src/tools/write-file.js +17 -18
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +108 -24
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +25 -0
- package/dist/src/tools/write-todos.js +150 -0
- package/dist/src/tools/write-todos.js.map +1 -0
- package/dist/src/tools/write-todos.test.d.ts +6 -0
- package/dist/src/tools/write-todos.test.js +89 -0
- package/dist/src/tools/write-todos.test.js.map +1 -0
- package/dist/src/utils/bfsFileSearch.d.ts +2 -2
- package/dist/src/utils/bfsFileSearch.js +13 -7
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.test.js +3 -3
- package/dist/src/utils/bfsFileSearch.test.js.map +1 -1
- package/dist/src/utils/editCorrector.d.ts +9 -8
- package/dist/src/utils/editCorrector.js +62 -19
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +33 -82
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/editor.js +32 -45
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +62 -76
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/environmentContext.d.ts +2 -2
- package/dist/src/utils/errorParsing.js +2 -2
- package/dist/src/utils/errorParsing.js.map +1 -1
- package/dist/src/utils/errorParsing.test.js +7 -7
- package/dist/src/utils/errorParsing.test.js.map +1 -1
- package/dist/src/utils/errorReporting.d.ts +1 -1
- package/dist/src/utils/errors.d.ts +25 -0
- package/dist/src/utils/errors.js +42 -0
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/fetch.js +1 -1
- package/dist/src/utils/fetch.js.map +1 -1
- package/dist/src/utils/fileUtils.d.ts +24 -12
- package/dist/src/utils/fileUtils.js +170 -79
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +347 -29
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/filesearch/crawler.d.ts +1 -1
- package/dist/src/utils/filesearch/crawler.test.js +2 -2
- package/dist/src/utils/filesearch/crawler.test.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.d.ts +1 -0
- package/dist/src/utils/filesearch/fileSearch.js +14 -9
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.test.js +90 -0
- package/dist/src/utils/filesearch/fileSearch.test.js.map +1 -1
- package/dist/src/utils/flashFallback.test.d.ts +6 -0
- package/dist/src/utils/{flashFallback.integration.test.js → flashFallback.test.js} +33 -29
- package/dist/src/utils/flashFallback.test.js.map +1 -0
- package/dist/src/utils/geminiIgnoreParser.d.ts +18 -0
- package/dist/src/utils/geminiIgnoreParser.js +61 -0
- package/dist/src/utils/geminiIgnoreParser.js.map +1 -0
- package/dist/src/utils/geminiIgnoreParser.test.d.ts +6 -0
- package/dist/src/utils/geminiIgnoreParser.test.js +50 -0
- package/dist/src/utils/geminiIgnoreParser.test.js.map +1 -0
- package/dist/src/utils/generateContentResponseUtilities.d.ts +1 -2
- package/dist/src/utils/generateContentResponseUtilities.js +1 -13
- package/dist/src/utils/generateContentResponseUtilities.js.map +1 -1
- package/dist/src/utils/generateContentResponseUtilities.test.js +2 -40
- package/dist/src/utils/generateContentResponseUtilities.test.js.map +1 -1
- package/dist/src/utils/getFolderStructure.d.ts +2 -2
- package/dist/src/utils/getFolderStructure.js +3 -3
- package/dist/src/utils/getFolderStructure.js.map +1 -1
- package/dist/src/utils/getFolderStructure.test.js +4 -4
- package/dist/src/utils/getFolderStructure.test.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.d.ts +3 -7
- package/dist/src/utils/gitIgnoreParser.js +126 -35
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +69 -38
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
- package/dist/src/utils/gitUtils.js +2 -2
- package/dist/src/utils/gitUtils.js.map +1 -1
- package/dist/src/utils/ignorePatterns.d.ts +103 -0
- package/dist/src/utils/ignorePatterns.js +220 -0
- package/dist/src/utils/ignorePatterns.js.map +1 -0
- package/dist/src/utils/ignorePatterns.test.d.ts +6 -0
- package/dist/src/utils/ignorePatterns.test.js +250 -0
- package/dist/src/utils/ignorePatterns.test.js.map +1 -0
- package/dist/src/utils/installationManager.d.ts +16 -0
- package/dist/src/utils/installationManager.js +50 -0
- package/dist/src/utils/installationManager.js.map +1 -0
- package/dist/src/utils/installationManager.test.d.ts +6 -0
- package/dist/src/utils/installationManager.test.js +83 -0
- package/dist/src/utils/installationManager.test.js.map +1 -0
- package/dist/src/utils/language-detection.d.ts +6 -0
- package/dist/src/utils/language-detection.js +101 -0
- package/dist/src/utils/language-detection.js.map +1 -0
- package/dist/src/utils/llm-edit-fixer.d.ts +26 -0
- package/dist/src/utils/llm-edit-fixer.js +131 -0
- package/dist/src/utils/llm-edit-fixer.js.map +1 -0
- package/dist/src/utils/llm-edit-fixer.test.d.ts +6 -0
- package/dist/src/utils/llm-edit-fixer.test.js +186 -0
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -0
- package/dist/src/utils/memoryDiscovery.d.ts +7 -6
- package/dist/src/utils/memoryDiscovery.js +68 -33
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +88 -26
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +15 -22
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.test.js +16 -141
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
- package/dist/src/utils/messageInspectors.d.ts +1 -1
- package/dist/src/utils/nextSpeakerChecker.d.ts +3 -3
- package/dist/src/utils/nextSpeakerChecker.js +8 -2
- package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +75 -64
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/partUtils.d.ts +22 -1
- package/dist/src/utils/partUtils.js +68 -0
- package/dist/src/utils/partUtils.js.map +1 -1
- package/dist/src/utils/partUtils.test.js +112 -1
- package/dist/src/utils/partUtils.test.js.map +1 -1
- package/dist/src/utils/pathReader.d.ts +17 -0
- package/dist/src/utils/pathReader.js +92 -0
- package/dist/src/utils/pathReader.js.map +1 -0
- package/dist/src/utils/pathReader.test.d.ts +6 -0
- package/dist/src/utils/pathReader.test.js +363 -0
- package/dist/src/utils/pathReader.test.js.map +1 -0
- package/dist/src/utils/paths.d.ts +0 -17
- package/dist/src/utils/paths.js +2 -28
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/promptIdContext.d.ts +7 -0
- package/dist/src/utils/promptIdContext.js +8 -0
- package/dist/src/utils/promptIdContext.js.map +1 -0
- package/dist/src/utils/quotaErrorDetection.d.ts +1 -1
- package/dist/src/utils/retry.d.ts +3 -1
- package/dist/src/utils/retry.js +60 -5
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +35 -3
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/schemaValidator.js +15 -1
- package/dist/src/utils/schemaValidator.js.map +1 -1
- package/dist/src/utils/schemaValidator.test.d.ts +6 -0
- package/dist/src/utils/schemaValidator.test.js +113 -0
- package/dist/src/utils/schemaValidator.test.js.map +1 -0
- package/dist/src/utils/session.js +1 -1
- package/dist/src/utils/session.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +6 -1
- package/dist/src/utils/shell-utils.js +51 -30
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +9 -0
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/summarizer.d.ts +2 -2
- package/dist/src/utils/summarizer.test.js.map +1 -1
- package/dist/src/utils/systemEncoding.js +2 -2
- package/dist/src/utils/systemEncoding.js.map +1 -1
- package/dist/src/utils/systemEncoding.test.js +2 -2
- package/dist/src/utils/systemEncoding.test.js.map +1 -1
- package/dist/src/utils/terminalSerializer.d.ts +25 -0
- package/dist/src/utils/terminalSerializer.js +432 -0
- package/dist/src/utils/terminalSerializer.js.map +1 -0
- package/dist/src/utils/terminalSerializer.test.d.ts +6 -0
- package/dist/src/utils/terminalSerializer.test.js +176 -0
- package/dist/src/utils/terminalSerializer.test.js.map +1 -0
- package/dist/src/utils/textUtils.d.ts +5 -0
- package/dist/src/utils/textUtils.js +14 -0
- package/dist/src/utils/textUtils.js.map +1 -1
- package/dist/src/utils/textUtils.test.d.ts +6 -0
- package/dist/src/utils/textUtils.test.js +59 -0
- package/dist/src/utils/textUtils.test.js.map +1 -0
- package/dist/src/utils/thoughtUtils.d.ts +21 -0
- package/dist/src/utils/thoughtUtils.js +39 -0
- package/dist/src/utils/thoughtUtils.js.map +1 -0
- package/dist/src/utils/thoughtUtils.test.d.ts +6 -0
- package/dist/src/utils/thoughtUtils.test.js +78 -0
- package/dist/src/utils/thoughtUtils.test.js.map +1 -0
- package/dist/src/utils/tool-utils.d.ts +19 -0
- package/dist/src/utils/tool-utils.js +58 -0
- package/dist/src/utils/tool-utils.js.map +1 -0
- package/dist/src/utils/tool-utils.test.d.ts +6 -0
- package/dist/src/utils/tool-utils.test.js +61 -0
- package/dist/src/utils/tool-utils.test.js.map +1 -0
- package/dist/src/utils/userAccountManager.d.ts +20 -0
- package/dist/src/utils/userAccountManager.js +114 -0
- package/dist/src/utils/userAccountManager.js.map +1 -0
- package/dist/src/utils/userAccountManager.test.d.ts +6 -0
- package/dist/src/utils/{user_account.test.js → userAccountManager.test.js} +33 -30
- package/dist/src/utils/userAccountManager.test.js.map +1 -0
- package/dist/src/utils/workspaceContext.js +13 -7
- package/dist/src/utils/workspaceContext.js.map +1 -1
- package/dist/src/utils/workspaceContext.test.js +41 -16
- package/dist/src/utils/workspaceContext.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -9
- package/dist/src/core/modelCheck.d.ts +0 -14
- package/dist/src/core/modelCheck.js +0 -62
- package/dist/src/core/modelCheck.js.map +0 -1
- package/dist/src/test-utils/tools.d.ts +0 -44
- package/dist/src/test-utils/tools.js.map +0 -1
- package/dist/src/utils/flashFallback.integration.test.js.map +0 -1
- package/dist/src/utils/user_account.d.ts +0 -9
- package/dist/src/utils/user_account.js +0 -109
- package/dist/src/utils/user_account.js.map +0 -1
- package/dist/src/utils/user_account.test.js.map +0 -1
- package/dist/src/utils/user_id.d.ts +0 -11
- package/dist/src/utils/user_id.js +0 -49
- package/dist/src/utils/user_id.js.map +0 -1
- 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/flashFallback.integration.test.d.ts → agents/executor.test.d.ts} +0 -0
- /package/dist/src/{utils/user_account.test.d.ts → agents/invocation.test.d.ts} +0 -0
- /package/dist/src/{utils/user_id.test.d.ts → agents/registry.test.d.ts} +0 -0
|
@@ -3,26 +3,46 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { GeminiEventType, Turn } from './turn.js';
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
+
import { findCompressSplitPoint, isThinkingDefault, isThinkingSupported, GeminiClient, } from './client.js';
|
|
8
|
+
import { AuthType, } from './contentGenerator.js';
|
|
9
|
+
import {} from './geminiChat.js';
|
|
10
|
+
import { CompressionStatus, GeminiEventType, Turn, } from './turn.js';
|
|
12
11
|
import { getCoreSystemPrompt } from './prompts.js';
|
|
13
12
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
|
14
13
|
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
|
|
15
14
|
import { setSimulate429 } from '../utils/testUtils.js';
|
|
16
15
|
import { tokenLimit } from './tokenLimits.js';
|
|
17
|
-
import {
|
|
16
|
+
import { ideContextStore } from '../ide/ideContext.js';
|
|
18
17
|
import { ClearcutLogger } from '../telemetry/clearcut-logger/clearcut-logger.js';
|
|
18
|
+
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
|
|
19
|
+
// Mock fs module to prevent actual file system operations during tests
|
|
20
|
+
const mockFileSystem = new Map();
|
|
21
|
+
vi.mock('node:fs', () => {
|
|
22
|
+
const fsModule = {
|
|
23
|
+
mkdirSync: vi.fn(),
|
|
24
|
+
writeFileSync: vi.fn((path, data) => {
|
|
25
|
+
mockFileSystem.set(path, data);
|
|
26
|
+
}),
|
|
27
|
+
readFileSync: vi.fn((path) => {
|
|
28
|
+
if (mockFileSystem.has(path)) {
|
|
29
|
+
return mockFileSystem.get(path);
|
|
30
|
+
}
|
|
31
|
+
throw Object.assign(new Error('ENOENT: no such file or directory'), {
|
|
32
|
+
code: 'ENOENT',
|
|
33
|
+
});
|
|
34
|
+
}),
|
|
35
|
+
existsSync: vi.fn((path) => mockFileSystem.has(path)),
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
default: fsModule,
|
|
39
|
+
...fsModule,
|
|
40
|
+
};
|
|
41
|
+
});
|
|
19
42
|
// --- Mocks ---
|
|
20
|
-
const mockChatCreateFn = vi.fn();
|
|
21
|
-
const mockGenerateContentFn = vi.fn();
|
|
22
|
-
const mockEmbedContentFn = vi.fn();
|
|
23
43
|
const mockTurnRunFn = vi.fn();
|
|
24
|
-
vi.mock('
|
|
25
|
-
|
|
44
|
+
vi.mock('./turn', async (importOriginal) => {
|
|
45
|
+
const actual = await importOriginal();
|
|
26
46
|
// Define a mock class that has the same shape as the real Turn
|
|
27
47
|
class MockTurn {
|
|
28
48
|
pendingToolCalls = [];
|
|
@@ -34,11 +54,8 @@ vi.mock('./turn', () => {
|
|
|
34
54
|
}
|
|
35
55
|
// Export the mock class as 'Turn'
|
|
36
56
|
return {
|
|
57
|
+
...actual,
|
|
37
58
|
Turn: MockTurn,
|
|
38
|
-
GeminiEventType: {
|
|
39
|
-
MaxSessionTurns: 'MaxSessionTurns',
|
|
40
|
-
ChatCompressed: 'ChatCompressed',
|
|
41
|
-
},
|
|
42
59
|
};
|
|
43
60
|
});
|
|
44
61
|
vi.mock('../config/config.js');
|
|
@@ -60,42 +77,78 @@ vi.mock('../telemetry/index.js', () => ({
|
|
|
60
77
|
logApiError: vi.fn(),
|
|
61
78
|
}));
|
|
62
79
|
vi.mock('../ide/ideContext.js');
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
80
|
+
vi.mock('../telemetry/uiTelemetry.js', () => ({
|
|
81
|
+
uiTelemetryService: {
|
|
82
|
+
setLastPromptTokenCount: vi.fn(),
|
|
83
|
+
getLastPromptTokenCount: vi.fn(),
|
|
84
|
+
},
|
|
85
|
+
}));
|
|
86
|
+
/**
|
|
87
|
+
* Array.fromAsync ponyfill, which will be available in es 2024.
|
|
88
|
+
*
|
|
89
|
+
* Buffers an async generator into an array and returns the result.
|
|
90
|
+
*/
|
|
91
|
+
async function fromAsync(promise) {
|
|
92
|
+
const results = [];
|
|
93
|
+
for await (const result of promise) {
|
|
94
|
+
results.push(result);
|
|
95
|
+
}
|
|
96
|
+
return results;
|
|
97
|
+
}
|
|
98
|
+
describe('findCompressSplitPoint', () => {
|
|
72
99
|
it('should throw an error for non-positive numbers', () => {
|
|
73
|
-
expect(() =>
|
|
100
|
+
expect(() => findCompressSplitPoint([], 0)).toThrow('Fraction must be between 0 and 1');
|
|
74
101
|
});
|
|
75
102
|
it('should throw an error for a fraction greater than or equal to 1', () => {
|
|
76
|
-
expect(() =>
|
|
103
|
+
expect(() => findCompressSplitPoint([], 1)).toThrow('Fraction must be between 0 and 1');
|
|
104
|
+
});
|
|
105
|
+
it('should handle an empty history', () => {
|
|
106
|
+
expect(findCompressSplitPoint([], 0.5)).toBe(0);
|
|
77
107
|
});
|
|
78
108
|
it('should handle a fraction in the middle', () => {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
109
|
+
const history = [
|
|
110
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (19%)
|
|
111
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (40%)
|
|
112
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (60%)
|
|
113
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
114
|
+
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
115
|
+
];
|
|
116
|
+
expect(findCompressSplitPoint(history, 0.5)).toBe(4);
|
|
85
117
|
});
|
|
86
|
-
it('should handle a fraction
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
118
|
+
it('should handle a fraction of last index', () => {
|
|
119
|
+
const history = [
|
|
120
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (19%)
|
|
121
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (40%)
|
|
122
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (60%)
|
|
123
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (80%)
|
|
124
|
+
{ role: 'user', parts: [{ text: 'This is the fifth message.' }] }, // JSON length: 65 (100%)
|
|
125
|
+
];
|
|
126
|
+
expect(findCompressSplitPoint(history, 0.9)).toBe(4);
|
|
93
127
|
});
|
|
94
|
-
it('should handle
|
|
95
|
-
|
|
128
|
+
it('should handle a fraction of after last index', () => {
|
|
129
|
+
const history = [
|
|
130
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] }, // JSON length: 66 (24%%)
|
|
131
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] }, // JSON length: 68 (50%)
|
|
132
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] }, // JSON length: 66 (74%)
|
|
133
|
+
{ role: 'model', parts: [{ text: 'This is the fourth message.' }] }, // JSON length: 68 (100%)
|
|
134
|
+
];
|
|
135
|
+
expect(findCompressSplitPoint(history, 0.8)).toBe(4);
|
|
136
|
+
});
|
|
137
|
+
it('should return earlier splitpoint if no valid ones are after threshhold', () => {
|
|
138
|
+
const history = [
|
|
139
|
+
{ role: 'user', parts: [{ text: 'This is the first message.' }] },
|
|
140
|
+
{ role: 'model', parts: [{ text: 'This is the second message.' }] },
|
|
141
|
+
{ role: 'user', parts: [{ text: 'This is the third message.' }] },
|
|
142
|
+
{ role: 'model', parts: [{ functionCall: {} }] },
|
|
143
|
+
];
|
|
144
|
+
// Can't return 4 because the previous item has a function call.
|
|
145
|
+
expect(findCompressSplitPoint(history, 0.99)).toBe(2);
|
|
96
146
|
});
|
|
97
147
|
it('should handle a history with only one item', () => {
|
|
98
|
-
|
|
148
|
+
const historyWithEmptyParts = [
|
|
149
|
+
{ role: 'user', parts: [{ text: 'Message 1' }] },
|
|
150
|
+
];
|
|
151
|
+
expect(findCompressSplitPoint(historyWithEmptyParts, 0.5)).toBe(0);
|
|
99
152
|
});
|
|
100
153
|
it('should handle history with weird parts', () => {
|
|
101
154
|
const historyWithEmptyParts = [
|
|
@@ -103,37 +156,54 @@ describe('findIndexAfterFraction', () => {
|
|
|
103
156
|
{ role: 'model', parts: [{ fileData: { fileUri: 'derp' } }] },
|
|
104
157
|
{ role: 'user', parts: [{ text: 'Message 2' }] },
|
|
105
158
|
];
|
|
106
|
-
expect(
|
|
159
|
+
expect(findCompressSplitPoint(historyWithEmptyParts, 0.5)).toBe(2);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
describe('isThinkingSupported', () => {
|
|
163
|
+
it('should return true for gemini-2.5', () => {
|
|
164
|
+
expect(isThinkingSupported('gemini-2.5')).toBe(true);
|
|
165
|
+
});
|
|
166
|
+
it('should return true for gemini-2.5-pro', () => {
|
|
167
|
+
expect(isThinkingSupported('gemini-2.5-pro')).toBe(true);
|
|
168
|
+
});
|
|
169
|
+
it('should return false for other models', () => {
|
|
170
|
+
expect(isThinkingSupported('gemini-1.5-flash')).toBe(false);
|
|
171
|
+
expect(isThinkingSupported('some-other-model')).toBe(false);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
describe('isThinkingDefault', () => {
|
|
175
|
+
it('should return false for gemini-2.5-flash-lite', () => {
|
|
176
|
+
expect(isThinkingDefault('gemini-2.5-flash-lite')).toBe(false);
|
|
177
|
+
});
|
|
178
|
+
it('should return true for gemini-2.5', () => {
|
|
179
|
+
expect(isThinkingDefault('gemini-2.5')).toBe(true);
|
|
180
|
+
});
|
|
181
|
+
it('should return true for gemini-2.5-pro', () => {
|
|
182
|
+
expect(isThinkingDefault('gemini-2.5-pro')).toBe(true);
|
|
183
|
+
});
|
|
184
|
+
it('should return false for other models', () => {
|
|
185
|
+
expect(isThinkingDefault('gemini-1.5-flash')).toBe(false);
|
|
186
|
+
expect(isThinkingDefault('some-other-model')).toBe(false);
|
|
107
187
|
});
|
|
108
188
|
});
|
|
109
189
|
describe('Gemini Client (client.ts)', () => {
|
|
190
|
+
let mockContentGenerator;
|
|
191
|
+
let mockConfig;
|
|
110
192
|
let client;
|
|
193
|
+
let mockGenerateContentFn;
|
|
111
194
|
beforeEach(async () => {
|
|
112
195
|
vi.resetAllMocks();
|
|
196
|
+
vi.mocked(uiTelemetryService.setLastPromptTokenCount).mockClear();
|
|
197
|
+
mockGenerateContentFn = vi.fn().mockResolvedValue({
|
|
198
|
+
candidates: [{ content: { parts: [{ text: '{"key": "value"}' }] } }],
|
|
199
|
+
});
|
|
113
200
|
// Disable 429 simulation for tests
|
|
114
201
|
setSimulate429(false);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
models: {
|
|
121
|
-
generateContent: mockGenerateContentFn,
|
|
122
|
-
embedContent: mockEmbedContentFn,
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
return mock;
|
|
126
|
-
});
|
|
127
|
-
mockChatCreateFn.mockResolvedValue({});
|
|
128
|
-
mockGenerateContentFn.mockResolvedValue({
|
|
129
|
-
candidates: [
|
|
130
|
-
{
|
|
131
|
-
content: {
|
|
132
|
-
parts: [{ text: '{"key": "value"}' }],
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
});
|
|
202
|
+
mockContentGenerator = {
|
|
203
|
+
generateContent: mockGenerateContentFn,
|
|
204
|
+
generateContentStream: vi.fn(),
|
|
205
|
+
batchEmbedContents: vi.fn(),
|
|
206
|
+
};
|
|
137
207
|
// Because the GeminiClient constructor kicks off an async process (startChat)
|
|
138
208
|
// that depends on a fully-formed Config object, we need to mock the
|
|
139
209
|
// entire implementation of Config for these tests.
|
|
@@ -143,12 +213,11 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
143
213
|
};
|
|
144
214
|
const fileService = new FileDiscoveryService('/test/dir');
|
|
145
215
|
const contentGeneratorConfig = {
|
|
146
|
-
model: 'test-model',
|
|
147
216
|
apiKey: 'test-key',
|
|
148
217
|
vertexai: false,
|
|
149
218
|
authType: AuthType.USE_GEMINI,
|
|
150
219
|
};
|
|
151
|
-
|
|
220
|
+
mockConfig = {
|
|
152
221
|
getContentGeneratorConfig: vi
|
|
153
222
|
.fn()
|
|
154
223
|
.mockReturnValue(contentGeneratorConfig),
|
|
@@ -176,181 +245,34 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
176
245
|
getDirectories: vi.fn().mockReturnValue(['/test/dir']),
|
|
177
246
|
}),
|
|
178
247
|
getGeminiClient: vi.fn(),
|
|
248
|
+
getModelRouterService: vi.fn().mockReturnValue({
|
|
249
|
+
route: vi.fn().mockResolvedValue({ model: 'default-routed-model' }),
|
|
250
|
+
}),
|
|
251
|
+
isInFallbackMode: vi.fn().mockReturnValue(false),
|
|
179
252
|
setFallbackMode: vi.fn(),
|
|
180
253
|
getChatCompression: vi.fn().mockReturnValue(undefined),
|
|
181
254
|
getSkipNextSpeakerCheck: vi.fn().mockReturnValue(false),
|
|
255
|
+
getUseSmartEdit: vi.fn().mockReturnValue(false),
|
|
256
|
+
getUseModelRouter: vi.fn().mockReturnValue(false),
|
|
257
|
+
getProjectRoot: vi.fn().mockReturnValue('/test/project/root'),
|
|
258
|
+
storage: {
|
|
259
|
+
getProjectTempDir: vi.fn().mockReturnValue('/test/temp'),
|
|
260
|
+
},
|
|
261
|
+
getContentGenerator: vi.fn().mockReturnValue(mockContentGenerator),
|
|
262
|
+
getBaseLlmClient: vi.fn().mockReturnValue({
|
|
263
|
+
generateJson: vi.fn().mockResolvedValue({
|
|
264
|
+
next_speaker: 'user',
|
|
265
|
+
reasoning: 'test',
|
|
266
|
+
}),
|
|
267
|
+
}),
|
|
182
268
|
};
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// and the constructor will use the mocked GoogleGenAI
|
|
187
|
-
client = new GeminiClient(new Config({ sessionId: 'test-session-id' }));
|
|
188
|
-
mockConfigObject.getGeminiClient.mockReturnValue(client);
|
|
189
|
-
await client.initialize(contentGeneratorConfig);
|
|
269
|
+
client = new GeminiClient(mockConfig);
|
|
270
|
+
await client.initialize();
|
|
271
|
+
vi.mocked(mockConfig.getGeminiClient).mockReturnValue(client);
|
|
190
272
|
});
|
|
191
273
|
afterEach(() => {
|
|
192
274
|
vi.restoreAllMocks();
|
|
193
275
|
});
|
|
194
|
-
// NOTE: The following tests for startChat were removed due to persistent issues with
|
|
195
|
-
// the @google/genai mock. Specifically, the mockChatCreateFn (representing instance.chats.create)
|
|
196
|
-
// was not being detected as called by the GeminiClient instance.
|
|
197
|
-
// This likely points to a subtle issue in how the GoogleGenerativeAI class constructor
|
|
198
|
-
// and its instance methods are mocked and then used by the class under test.
|
|
199
|
-
// For future debugging, ensure that the `this.client` in `GeminiClient` (which is an
|
|
200
|
-
// instance of the mocked GoogleGenerativeAI) correctly has its `chats.create` method
|
|
201
|
-
// pointing to `mockChatCreateFn`.
|
|
202
|
-
// it('startChat should call getCoreSystemPrompt with userMemory and pass to chats.create', async () => { ... });
|
|
203
|
-
// it('startChat should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
204
|
-
// NOTE: The following tests for generateJson were removed due to persistent issues with
|
|
205
|
-
// the @google/genai mock, similar to the startChat tests. The mockGenerateContentFn
|
|
206
|
-
// (representing instance.models.generateContent) was not being detected as called, or the mock
|
|
207
|
-
// was not preventing an actual API call (leading to API key errors).
|
|
208
|
-
// For future debugging, ensure `this.client.models.generateContent` in `GeminiClient` correctly
|
|
209
|
-
// uses the `mockGenerateContentFn`.
|
|
210
|
-
// it('generateJson should call getCoreSystemPrompt with userMemory and pass to generateContent', async () => { ... });
|
|
211
|
-
// it('generateJson should call getCoreSystemPrompt with empty string if userMemory is empty', async () => { ... });
|
|
212
|
-
describe('generateEmbedding', () => {
|
|
213
|
-
const texts = ['hello world', 'goodbye world'];
|
|
214
|
-
const testEmbeddingModel = 'test-embedding-model';
|
|
215
|
-
it('should call embedContent with correct parameters and return embeddings', async () => {
|
|
216
|
-
const mockEmbeddings = [
|
|
217
|
-
[0.1, 0.2, 0.3],
|
|
218
|
-
[0.4, 0.5, 0.6],
|
|
219
|
-
];
|
|
220
|
-
const mockResponse = {
|
|
221
|
-
embeddings: [
|
|
222
|
-
{ values: mockEmbeddings[0] },
|
|
223
|
-
{ values: mockEmbeddings[1] },
|
|
224
|
-
],
|
|
225
|
-
};
|
|
226
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
227
|
-
const result = await client.generateEmbedding(texts);
|
|
228
|
-
expect(mockEmbedContentFn).toHaveBeenCalledTimes(1);
|
|
229
|
-
expect(mockEmbedContentFn).toHaveBeenCalledWith({
|
|
230
|
-
model: testEmbeddingModel,
|
|
231
|
-
contents: texts,
|
|
232
|
-
});
|
|
233
|
-
expect(result).toEqual(mockEmbeddings);
|
|
234
|
-
});
|
|
235
|
-
it('should return an empty array if an empty array is passed', async () => {
|
|
236
|
-
const result = await client.generateEmbedding([]);
|
|
237
|
-
expect(result).toEqual([]);
|
|
238
|
-
expect(mockEmbedContentFn).not.toHaveBeenCalled();
|
|
239
|
-
});
|
|
240
|
-
it('should throw an error if API response has no embeddings array', async () => {
|
|
241
|
-
mockEmbedContentFn.mockResolvedValue({}); // No `embeddings` key
|
|
242
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
243
|
-
});
|
|
244
|
-
it('should throw an error if API response has an empty embeddings array', async () => {
|
|
245
|
-
const mockResponse = {
|
|
246
|
-
embeddings: [],
|
|
247
|
-
};
|
|
248
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
249
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('No embeddings found in API response.');
|
|
250
|
-
});
|
|
251
|
-
it('should throw an error if API returns a mismatched number of embeddings', async () => {
|
|
252
|
-
const mockResponse = {
|
|
253
|
-
embeddings: [{ values: [1, 2, 3] }], // Only one for two texts
|
|
254
|
-
};
|
|
255
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
256
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned a mismatched number of embeddings. Expected 2, got 1.');
|
|
257
|
-
});
|
|
258
|
-
it('should throw an error if any embedding has nullish values', async () => {
|
|
259
|
-
const mockResponse = {
|
|
260
|
-
embeddings: [{ values: [1, 2, 3] }, { values: undefined }], // Second one is bad
|
|
261
|
-
};
|
|
262
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
263
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 1: "goodbye world"');
|
|
264
|
-
});
|
|
265
|
-
it('should throw an error if any embedding has an empty values array', async () => {
|
|
266
|
-
const mockResponse = {
|
|
267
|
-
embeddings: [{ values: [] }, { values: [1, 2, 3] }], // First one is bad
|
|
268
|
-
};
|
|
269
|
-
mockEmbedContentFn.mockResolvedValue(mockResponse);
|
|
270
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API returned an empty embedding for input text at index 0: "hello world"');
|
|
271
|
-
});
|
|
272
|
-
it('should propagate errors from the API call', async () => {
|
|
273
|
-
const apiError = new Error('API Failure');
|
|
274
|
-
mockEmbedContentFn.mockRejectedValue(apiError);
|
|
275
|
-
await expect(client.generateEmbedding(texts)).rejects.toThrow('API Failure');
|
|
276
|
-
});
|
|
277
|
-
});
|
|
278
|
-
describe('generateContent', () => {
|
|
279
|
-
it('should call generateContent with the correct parameters', async () => {
|
|
280
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
281
|
-
const generationConfig = { temperature: 0.5 };
|
|
282
|
-
const abortSignal = new AbortController().signal;
|
|
283
|
-
// Mock countTokens
|
|
284
|
-
const mockGenerator = {
|
|
285
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
286
|
-
generateContent: mockGenerateContentFn,
|
|
287
|
-
};
|
|
288
|
-
client['contentGenerator'] = mockGenerator;
|
|
289
|
-
await client.generateContent(contents, generationConfig, abortSignal);
|
|
290
|
-
expect(mockGenerateContentFn).toHaveBeenCalledWith({
|
|
291
|
-
model: 'test-model',
|
|
292
|
-
config: {
|
|
293
|
-
abortSignal,
|
|
294
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
295
|
-
temperature: 0.5,
|
|
296
|
-
topP: 1,
|
|
297
|
-
},
|
|
298
|
-
contents,
|
|
299
|
-
}, 'test-session-id');
|
|
300
|
-
});
|
|
301
|
-
});
|
|
302
|
-
describe('generateJson', () => {
|
|
303
|
-
it('should call generateContent with the correct parameters', async () => {
|
|
304
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
305
|
-
const schema = { type: 'string' };
|
|
306
|
-
const abortSignal = new AbortController().signal;
|
|
307
|
-
// Mock countTokens
|
|
308
|
-
const mockGenerator = {
|
|
309
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
310
|
-
generateContent: mockGenerateContentFn,
|
|
311
|
-
};
|
|
312
|
-
client['contentGenerator'] = mockGenerator;
|
|
313
|
-
await client.generateJson(contents, schema, abortSignal);
|
|
314
|
-
expect(mockGenerateContentFn).toHaveBeenCalledWith({
|
|
315
|
-
model: 'test-model', // Should use current model from config
|
|
316
|
-
config: {
|
|
317
|
-
abortSignal,
|
|
318
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
319
|
-
temperature: 0,
|
|
320
|
-
topP: 1,
|
|
321
|
-
responseJsonSchema: schema,
|
|
322
|
-
responseMimeType: 'application/json',
|
|
323
|
-
},
|
|
324
|
-
contents,
|
|
325
|
-
}, 'test-session-id');
|
|
326
|
-
});
|
|
327
|
-
it('should allow overriding model and config', async () => {
|
|
328
|
-
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
329
|
-
const schema = { type: 'string' };
|
|
330
|
-
const abortSignal = new AbortController().signal;
|
|
331
|
-
const customModel = 'custom-json-model';
|
|
332
|
-
const customConfig = { temperature: 0.9, topK: 20 };
|
|
333
|
-
const mockGenerator = {
|
|
334
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 1 }),
|
|
335
|
-
generateContent: mockGenerateContentFn,
|
|
336
|
-
};
|
|
337
|
-
client['contentGenerator'] = mockGenerator;
|
|
338
|
-
await client.generateJson(contents, schema, abortSignal, customModel, customConfig);
|
|
339
|
-
expect(mockGenerateContentFn).toHaveBeenCalledWith({
|
|
340
|
-
model: customModel,
|
|
341
|
-
config: {
|
|
342
|
-
abortSignal,
|
|
343
|
-
systemInstruction: getCoreSystemPrompt(''),
|
|
344
|
-
temperature: 0.9,
|
|
345
|
-
topP: 1, // from default
|
|
346
|
-
topK: 20,
|
|
347
|
-
responseJsonSchema: schema,
|
|
348
|
-
responseMimeType: 'application/json',
|
|
349
|
-
},
|
|
350
|
-
contents,
|
|
351
|
-
}, 'test-session-id');
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
276
|
describe('addHistory', () => {
|
|
355
277
|
it('should call chat.addHistory with the provided content', async () => {
|
|
356
278
|
const mockChat = {
|
|
@@ -388,37 +310,166 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
388
310
|
});
|
|
389
311
|
});
|
|
390
312
|
describe('tryCompressChat', () => {
|
|
391
|
-
const mockCountTokens = vi.fn();
|
|
392
|
-
const mockSendMessage = vi.fn();
|
|
393
313
|
const mockGetHistory = vi.fn();
|
|
394
314
|
beforeEach(() => {
|
|
395
315
|
vi.mock('./tokenLimits', () => ({
|
|
396
316
|
tokenLimit: vi.fn(),
|
|
397
317
|
}));
|
|
398
|
-
client['contentGenerator'] = {
|
|
399
|
-
countTokens: mockCountTokens,
|
|
400
|
-
};
|
|
401
318
|
client['chat'] = {
|
|
402
319
|
getHistory: mockGetHistory,
|
|
403
320
|
addHistory: vi.fn(),
|
|
404
321
|
setHistory: vi.fn(),
|
|
405
|
-
sendMessage: mockSendMessage,
|
|
406
322
|
};
|
|
407
323
|
});
|
|
324
|
+
function setup({ chatHistory = [
|
|
325
|
+
{ role: 'user', parts: [{ text: 'Long conversation' }] },
|
|
326
|
+
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
327
|
+
], originalTokenCount = 1000, summaryText = 'This is a summary.', } = {}) {
|
|
328
|
+
const mockOriginalChat = {
|
|
329
|
+
getHistory: vi.fn((_curated) => chatHistory),
|
|
330
|
+
setHistory: vi.fn(),
|
|
331
|
+
};
|
|
332
|
+
client['chat'] = mockOriginalChat;
|
|
333
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
334
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
335
|
+
candidates: [
|
|
336
|
+
{
|
|
337
|
+
content: {
|
|
338
|
+
role: 'model',
|
|
339
|
+
parts: [{ text: summaryText }],
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
],
|
|
343
|
+
});
|
|
344
|
+
// Calculate what the new history will be
|
|
345
|
+
const splitPoint = findCompressSplitPoint(chatHistory, 0.7); // 1 - 0.3
|
|
346
|
+
const historyToKeep = chatHistory.slice(splitPoint);
|
|
347
|
+
// This is the history that the new chat will have.
|
|
348
|
+
// It includes the default startChat history + the extra history from tryCompressChat
|
|
349
|
+
const newCompressedHistory = [
|
|
350
|
+
// Mocked envParts + canned response from startChat
|
|
351
|
+
{
|
|
352
|
+
role: 'user',
|
|
353
|
+
parts: [{ text: 'Mocked env context' }],
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
role: 'model',
|
|
357
|
+
parts: [{ text: 'Got it. Thanks for the context!' }],
|
|
358
|
+
},
|
|
359
|
+
// extraHistory from tryCompressChat
|
|
360
|
+
{
|
|
361
|
+
role: 'user',
|
|
362
|
+
parts: [{ text: summaryText }],
|
|
363
|
+
},
|
|
364
|
+
{
|
|
365
|
+
role: 'model',
|
|
366
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
367
|
+
},
|
|
368
|
+
...historyToKeep,
|
|
369
|
+
];
|
|
370
|
+
const mockNewChat = {
|
|
371
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
372
|
+
setHistory: vi.fn(),
|
|
373
|
+
};
|
|
374
|
+
client['startChat'] = vi
|
|
375
|
+
.fn()
|
|
376
|
+
.mockResolvedValue(mockNewChat);
|
|
377
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
378
|
+
const estimatedNewTokenCount = Math.floor(totalChars / 4);
|
|
379
|
+
return {
|
|
380
|
+
client,
|
|
381
|
+
mockOriginalChat,
|
|
382
|
+
mockNewChat,
|
|
383
|
+
estimatedNewTokenCount,
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
describe('when compression inflates the token count', () => {
|
|
387
|
+
it('allows compression to be forced/manual after a failure', async () => {
|
|
388
|
+
// Call 1 (Fails): Setup with a long summary to inflate tokens
|
|
389
|
+
const longSummary = 'long summary '.repeat(100);
|
|
390
|
+
const { client, estimatedNewTokenCount: inflatedTokenCount } = setup({
|
|
391
|
+
originalTokenCount: 100,
|
|
392
|
+
summaryText: longSummary,
|
|
393
|
+
});
|
|
394
|
+
expect(inflatedTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
395
|
+
await client.tryCompressChat('prompt-id-4', false); // Fails
|
|
396
|
+
// Call 2 (Forced): Re-setup with a short summary
|
|
397
|
+
const shortSummary = 'short';
|
|
398
|
+
const { estimatedNewTokenCount: compressedTokenCount } = setup({
|
|
399
|
+
originalTokenCount: 100,
|
|
400
|
+
summaryText: shortSummary,
|
|
401
|
+
});
|
|
402
|
+
expect(compressedTokenCount).toBeLessThanOrEqual(100); // Ensure setup is correct
|
|
403
|
+
const result = await client.tryCompressChat('prompt-id-4', true); // Forced
|
|
404
|
+
expect(result).toEqual({
|
|
405
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
406
|
+
newTokenCount: compressedTokenCount,
|
|
407
|
+
originalTokenCount: 100,
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
it('yields the result even if the compression inflated the tokens', async () => {
|
|
411
|
+
const longSummary = 'long summary '.repeat(100);
|
|
412
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
413
|
+
originalTokenCount: 100,
|
|
414
|
+
summaryText: longSummary,
|
|
415
|
+
});
|
|
416
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
417
|
+
const result = await client.tryCompressChat('prompt-id-4', false);
|
|
418
|
+
expect(result).toEqual({
|
|
419
|
+
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
420
|
+
newTokenCount: estimatedNewTokenCount,
|
|
421
|
+
originalTokenCount: 100,
|
|
422
|
+
});
|
|
423
|
+
// IMPORTANT: The change in client.ts means setLastPromptTokenCount is NOT called on failure
|
|
424
|
+
expect(uiTelemetryService.setLastPromptTokenCount).not.toHaveBeenCalled();
|
|
425
|
+
});
|
|
426
|
+
it('does not manipulate the source chat', async () => {
|
|
427
|
+
const longSummary = 'long summary '.repeat(100);
|
|
428
|
+
const { client, mockOriginalChat, estimatedNewTokenCount } = setup({
|
|
429
|
+
originalTokenCount: 100,
|
|
430
|
+
summaryText: longSummary,
|
|
431
|
+
});
|
|
432
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
433
|
+
await client.tryCompressChat('prompt-id-4', false);
|
|
434
|
+
// On failure, the chat should NOT be replaced
|
|
435
|
+
expect(client['chat']).toBe(mockOriginalChat);
|
|
436
|
+
});
|
|
437
|
+
it('will not attempt to compress context after a failure', async () => {
|
|
438
|
+
const longSummary = 'long summary '.repeat(100);
|
|
439
|
+
const { client, estimatedNewTokenCount } = setup({
|
|
440
|
+
originalTokenCount: 100,
|
|
441
|
+
summaryText: longSummary,
|
|
442
|
+
});
|
|
443
|
+
expect(estimatedNewTokenCount).toBeGreaterThan(100); // Ensure setup is correct
|
|
444
|
+
await client.tryCompressChat('prompt-id-4', false); // This fails and sets hasFailedCompressionAttempt = true
|
|
445
|
+
// This call should now be a NOOP
|
|
446
|
+
const result = await client.tryCompressChat('prompt-id-5', false);
|
|
447
|
+
// generateContent (for summary) should only have been called once
|
|
448
|
+
expect(mockGenerateContentFn).toHaveBeenCalledTimes(1);
|
|
449
|
+
expect(result).toEqual({
|
|
450
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
451
|
+
newTokenCount: 0,
|
|
452
|
+
originalTokenCount: 0,
|
|
453
|
+
});
|
|
454
|
+
});
|
|
455
|
+
});
|
|
408
456
|
it('should not trigger summarization if token count is below threshold', async () => {
|
|
409
457
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
410
458
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
411
459
|
mockGetHistory.mockReturnValue([
|
|
412
460
|
{ role: 'user', parts: [{ text: '...history...' }] },
|
|
413
461
|
]);
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
});
|
|
462
|
+
const originalTokenCount = MOCKED_TOKEN_LIMIT * 0.699;
|
|
463
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
417
464
|
const initialChat = client.getChat();
|
|
418
|
-
const result = await client.tryCompressChat('prompt-id-2');
|
|
465
|
+
const result = await client.tryCompressChat('prompt-id-2', false);
|
|
419
466
|
const newChat = client.getChat();
|
|
420
467
|
expect(tokenLimit).toHaveBeenCalled();
|
|
421
|
-
expect(result).
|
|
468
|
+
expect(result).toEqual({
|
|
469
|
+
compressionStatus: CompressionStatus.NOOP,
|
|
470
|
+
newTokenCount: originalTokenCount,
|
|
471
|
+
originalTokenCount,
|
|
472
|
+
});
|
|
422
473
|
expect(newChat).toBe(initialChat);
|
|
423
474
|
});
|
|
424
475
|
it('logs a telemetry event when compressing', async () => {
|
|
@@ -429,24 +480,51 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
429
480
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
430
481
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
431
482
|
});
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
]);
|
|
483
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
484
|
+
mockGetHistory.mockReturnValue(history);
|
|
435
485
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
486
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
487
|
+
// We need to control the estimated new token count.
|
|
488
|
+
// We mock startChat to return a chat with a known history.
|
|
489
|
+
const summaryText = 'This is a summary.';
|
|
490
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
491
|
+
const historyToKeep = history.slice(splitPoint);
|
|
492
|
+
const newCompressedHistory = [
|
|
493
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
494
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
495
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
496
|
+
{
|
|
497
|
+
role: 'model',
|
|
498
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
499
|
+
},
|
|
500
|
+
...historyToKeep,
|
|
501
|
+
];
|
|
502
|
+
const mockNewChat = {
|
|
503
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
504
|
+
};
|
|
505
|
+
client['startChat'] = vi
|
|
506
|
+
.fn()
|
|
507
|
+
.mockResolvedValue(mockNewChat);
|
|
508
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
509
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
440
510
|
// Mock the summary response from the chat
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
511
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
512
|
+
candidates: [
|
|
513
|
+
{
|
|
514
|
+
content: {
|
|
515
|
+
role: 'model',
|
|
516
|
+
parts: [{ text: summaryText }],
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
],
|
|
444
520
|
});
|
|
445
|
-
await client.tryCompressChat('prompt-id-3');
|
|
521
|
+
await client.tryCompressChat('prompt-id-3', false);
|
|
446
522
|
expect(ClearcutLogger.prototype.logChatCompressionEvent).toHaveBeenCalledWith(expect.objectContaining({
|
|
447
523
|
tokens_before: originalTokenCount,
|
|
448
524
|
tokens_after: newTokenCount,
|
|
449
525
|
}));
|
|
526
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledWith(newTokenCount);
|
|
527
|
+
expect(uiTelemetryService.setLastPromptTokenCount).toHaveBeenCalledTimes(1);
|
|
450
528
|
});
|
|
451
529
|
it('should trigger summarization if token count is at threshold with contextPercentageThreshold setting', async () => {
|
|
452
530
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
@@ -455,26 +533,51 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
455
533
|
vi.spyOn(client['config'], 'getChatCompression').mockReturnValue({
|
|
456
534
|
contextPercentageThreshold: MOCKED_CONTEXT_PERCENTAGE_THRESHOLD,
|
|
457
535
|
});
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
]);
|
|
536
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
537
|
+
mockGetHistory.mockReturnValue(history);
|
|
461
538
|
const originalTokenCount = MOCKED_TOKEN_LIMIT * MOCKED_CONTEXT_PERCENTAGE_THRESHOLD;
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
539
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
540
|
+
// Mock summary and new chat
|
|
541
|
+
const summaryText = 'This is a summary.';
|
|
542
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
543
|
+
const historyToKeep = history.slice(splitPoint);
|
|
544
|
+
const newCompressedHistory = [
|
|
545
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
546
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
547
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
548
|
+
{
|
|
549
|
+
role: 'model',
|
|
550
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
551
|
+
},
|
|
552
|
+
...historyToKeep,
|
|
553
|
+
];
|
|
554
|
+
const mockNewChat = {
|
|
555
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
556
|
+
};
|
|
557
|
+
client['startChat'] = vi
|
|
558
|
+
.fn()
|
|
559
|
+
.mockResolvedValue(mockNewChat);
|
|
560
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
561
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
466
562
|
// Mock the summary response from the chat
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
563
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
564
|
+
candidates: [
|
|
565
|
+
{
|
|
566
|
+
content: {
|
|
567
|
+
role: 'model',
|
|
568
|
+
parts: [{ text: summaryText }],
|
|
569
|
+
},
|
|
570
|
+
},
|
|
571
|
+
],
|
|
470
572
|
});
|
|
471
573
|
const initialChat = client.getChat();
|
|
472
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
574
|
+
const result = await client.tryCompressChat('prompt-id-3', false);
|
|
473
575
|
const newChat = client.getChat();
|
|
474
576
|
expect(tokenLimit).toHaveBeenCalled();
|
|
475
|
-
expect(
|
|
577
|
+
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
476
578
|
// Assert that summarization happened and returned the correct stats
|
|
477
579
|
expect(result).toEqual({
|
|
580
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
478
581
|
originalTokenCount,
|
|
479
582
|
newTokenCount,
|
|
480
583
|
});
|
|
@@ -484,7 +587,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
484
587
|
it('should not compress across a function call response', async () => {
|
|
485
588
|
const MOCKED_TOKEN_LIMIT = 1000;
|
|
486
589
|
vi.mocked(tokenLimit).mockReturnValue(MOCKED_TOKEN_LIMIT);
|
|
487
|
-
|
|
590
|
+
const history = [
|
|
488
591
|
{ role: 'user', parts: [{ text: '...history 1...' }] },
|
|
489
592
|
{ role: 'model', parts: [{ text: '...history 2...' }] },
|
|
490
593
|
{ role: 'user', parts: [{ text: '...history 3...' }] },
|
|
@@ -501,55 +604,111 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
501
604
|
{ role: 'model', parts: [{ text: '...history 10...' }] },
|
|
502
605
|
// Instead we will break here.
|
|
503
606
|
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
504
|
-
]
|
|
607
|
+
];
|
|
608
|
+
mockGetHistory.mockReturnValue(history);
|
|
505
609
|
const originalTokenCount = 1000 * 0.7;
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
610
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
611
|
+
// Mock summary and new chat
|
|
612
|
+
const summaryText = 'This is a summary.';
|
|
613
|
+
const splitPoint = findCompressSplitPoint(history, 0.7); // This should be 10
|
|
614
|
+
expect(splitPoint).toBe(10); // Verify split point logic
|
|
615
|
+
const historyToKeep = history.slice(splitPoint); // Should keep last user message
|
|
616
|
+
expect(historyToKeep).toEqual([
|
|
617
|
+
{ role: 'user', parts: [{ text: '...history 10...' }] },
|
|
618
|
+
]);
|
|
619
|
+
const newCompressedHistory = [
|
|
620
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
621
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
622
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
623
|
+
{
|
|
624
|
+
role: 'model',
|
|
625
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
626
|
+
},
|
|
627
|
+
...historyToKeep,
|
|
628
|
+
];
|
|
629
|
+
const mockNewChat = {
|
|
630
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
631
|
+
};
|
|
632
|
+
client['startChat'] = vi
|
|
633
|
+
.fn()
|
|
634
|
+
.mockResolvedValue(mockNewChat);
|
|
635
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
636
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
510
637
|
// Mock the summary response from the chat
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
638
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
639
|
+
candidates: [
|
|
640
|
+
{
|
|
641
|
+
content: {
|
|
642
|
+
role: 'model',
|
|
643
|
+
parts: [{ text: summaryText }],
|
|
644
|
+
},
|
|
645
|
+
},
|
|
646
|
+
],
|
|
514
647
|
});
|
|
515
648
|
const initialChat = client.getChat();
|
|
516
|
-
const result = await client.tryCompressChat('prompt-id-3');
|
|
649
|
+
const result = await client.tryCompressChat('prompt-id-3', false);
|
|
517
650
|
const newChat = client.getChat();
|
|
518
651
|
expect(tokenLimit).toHaveBeenCalled();
|
|
519
|
-
expect(
|
|
652
|
+
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
520
653
|
// Assert that summarization happened and returned the correct stats
|
|
521
654
|
expect(result).toEqual({
|
|
655
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
522
656
|
originalTokenCount,
|
|
523
657
|
newTokenCount,
|
|
524
658
|
});
|
|
525
659
|
// Assert that the chat was reset
|
|
526
660
|
expect(newChat).not.toBe(initialChat);
|
|
527
|
-
// 1. standard start context message
|
|
528
|
-
// 2. standard canned
|
|
529
|
-
// 3. compressed summary message
|
|
530
|
-
// 4. standard canned
|
|
531
|
-
// 5. The last user message (
|
|
661
|
+
// 1. standard start context message (env)
|
|
662
|
+
// 2. standard canned model response
|
|
663
|
+
// 3. compressed summary message (user)
|
|
664
|
+
// 4. standard canned model response
|
|
665
|
+
// 5. The last user message (historyToKeep)
|
|
532
666
|
expect(newChat.getHistory().length).toEqual(5);
|
|
533
667
|
});
|
|
534
668
|
it('should always trigger summarization when force is true, regardless of token count', async () => {
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
669
|
+
const history = [{ role: 'user', parts: [{ text: '...history...' }] }];
|
|
670
|
+
mockGetHistory.mockReturnValue(history);
|
|
671
|
+
const originalTokenCount = 100; // Well below threshold, but > estimated new count
|
|
672
|
+
vi.mocked(uiTelemetryService.getLastPromptTokenCount).mockReturnValue(originalTokenCount);
|
|
673
|
+
// Mock summary and new chat
|
|
674
|
+
const summaryText = 'This is a summary.';
|
|
675
|
+
const splitPoint = findCompressSplitPoint(history, 0.7);
|
|
676
|
+
const historyToKeep = history.slice(splitPoint);
|
|
677
|
+
const newCompressedHistory = [
|
|
678
|
+
{ role: 'user', parts: [{ text: 'Mocked env context' }] },
|
|
679
|
+
{ role: 'model', parts: [{ text: 'Got it. Thanks for the context!' }] },
|
|
680
|
+
{ role: 'user', parts: [{ text: summaryText }] },
|
|
681
|
+
{
|
|
682
|
+
role: 'model',
|
|
683
|
+
parts: [{ text: 'Got it. Thanks for the additional context!' }],
|
|
684
|
+
},
|
|
685
|
+
...historyToKeep,
|
|
686
|
+
];
|
|
687
|
+
const mockNewChat = {
|
|
688
|
+
getHistory: vi.fn().mockReturnValue(newCompressedHistory),
|
|
689
|
+
};
|
|
690
|
+
client['startChat'] = vi
|
|
691
|
+
.fn()
|
|
692
|
+
.mockResolvedValue(mockNewChat);
|
|
693
|
+
const totalChars = newCompressedHistory.reduce((total, content) => total + JSON.stringify(content).length, 0);
|
|
694
|
+
const newTokenCount = Math.floor(totalChars / 4);
|
|
543
695
|
// Mock the summary response from the chat
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
696
|
+
mockGenerateContentFn.mockResolvedValue({
|
|
697
|
+
candidates: [
|
|
698
|
+
{
|
|
699
|
+
content: {
|
|
700
|
+
role: 'model',
|
|
701
|
+
parts: [{ text: summaryText }],
|
|
702
|
+
},
|
|
703
|
+
},
|
|
704
|
+
],
|
|
547
705
|
});
|
|
548
706
|
const initialChat = client.getChat();
|
|
549
707
|
const result = await client.tryCompressChat('prompt-id-1', true); // force = true
|
|
550
708
|
const newChat = client.getChat();
|
|
551
|
-
expect(
|
|
709
|
+
expect(mockGenerateContentFn).toHaveBeenCalled();
|
|
552
710
|
expect(result).toEqual({
|
|
711
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
553
712
|
originalTokenCount,
|
|
554
713
|
newTokenCount,
|
|
555
714
|
});
|
|
@@ -558,9 +717,55 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
558
717
|
});
|
|
559
718
|
});
|
|
560
719
|
describe('sendMessageStream', () => {
|
|
720
|
+
it('emits a compression event when the context was automatically compressed', async () => {
|
|
721
|
+
// Arrange
|
|
722
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
723
|
+
yield { type: 'content', value: 'Hello' };
|
|
724
|
+
})());
|
|
725
|
+
const compressionInfo = {
|
|
726
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
727
|
+
originalTokenCount: 1000,
|
|
728
|
+
newTokenCount: 500,
|
|
729
|
+
};
|
|
730
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValueOnce(compressionInfo);
|
|
731
|
+
// Act
|
|
732
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
|
|
733
|
+
const events = await fromAsync(stream);
|
|
734
|
+
// Assert
|
|
735
|
+
expect(events).toContainEqual({
|
|
736
|
+
type: GeminiEventType.ChatCompressed,
|
|
737
|
+
value: compressionInfo,
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
it.each([
|
|
741
|
+
{
|
|
742
|
+
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
743
|
+
},
|
|
744
|
+
{ compressionStatus: CompressionStatus.NOOP },
|
|
745
|
+
])('does not emit a compression event when the status is $compressionStatus', async ({ compressionStatus }) => {
|
|
746
|
+
// Arrange
|
|
747
|
+
const mockStream = (async function* () {
|
|
748
|
+
yield { type: 'content', value: 'Hello' };
|
|
749
|
+
})();
|
|
750
|
+
mockTurnRunFn.mockReturnValue(mockStream);
|
|
751
|
+
const compressionInfo = {
|
|
752
|
+
compressionStatus,
|
|
753
|
+
originalTokenCount: 1000,
|
|
754
|
+
newTokenCount: 500,
|
|
755
|
+
};
|
|
756
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValueOnce(compressionInfo);
|
|
757
|
+
// Act
|
|
758
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
|
|
759
|
+
const events = await fromAsync(stream);
|
|
760
|
+
// Assert
|
|
761
|
+
expect(events).not.toContainEqual({
|
|
762
|
+
type: GeminiEventType.ChatCompressed,
|
|
763
|
+
value: expect.anything(),
|
|
764
|
+
});
|
|
765
|
+
});
|
|
561
766
|
it('should include editor context when ideMode is enabled', async () => {
|
|
562
767
|
// Arrange
|
|
563
|
-
vi.mocked(
|
|
768
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
564
769
|
workspaceState: {
|
|
565
770
|
openFiles: [
|
|
566
771
|
{
|
|
@@ -581,21 +786,20 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
581
786
|
],
|
|
582
787
|
},
|
|
583
788
|
});
|
|
584
|
-
vi.
|
|
585
|
-
|
|
789
|
+
vi.mocked(mockConfig.getIdeMode).mockReturnValue(true);
|
|
790
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
791
|
+
originalTokenCount: 0,
|
|
792
|
+
newTokenCount: 0,
|
|
793
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
794
|
+
});
|
|
795
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
586
796
|
yield { type: 'content', value: 'Hello' };
|
|
587
|
-
})();
|
|
588
|
-
mockTurnRunFn.mockReturnValue(mockStream);
|
|
797
|
+
})());
|
|
589
798
|
const mockChat = {
|
|
590
799
|
addHistory: vi.fn(),
|
|
591
800
|
getHistory: vi.fn().mockReturnValue([]),
|
|
592
801
|
};
|
|
593
802
|
client['chat'] = mockChat;
|
|
594
|
-
const mockGenerator = {
|
|
595
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
596
|
-
generateContent: mockGenerateContentFn,
|
|
597
|
-
};
|
|
598
|
-
client['contentGenerator'] = mockGenerator;
|
|
599
803
|
const initialRequest = [{ text: 'Hi' }];
|
|
600
804
|
// Act
|
|
601
805
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -603,7 +807,7 @@ describe('Gemini Client (client.ts)', () => {
|
|
|
603
807
|
// consume stream
|
|
604
808
|
}
|
|
605
809
|
// Assert
|
|
606
|
-
expect(
|
|
810
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
607
811
|
const expectedContext = `
|
|
608
812
|
Here is the user's editor context as a JSON object. This is for your information only.
|
|
609
813
|
\`\`\`json
|
|
@@ -628,7 +832,7 @@ ${JSON.stringify({
|
|
|
628
832
|
});
|
|
629
833
|
it('should not add context if ideMode is enabled but no open files', async () => {
|
|
630
834
|
// Arrange
|
|
631
|
-
vi.mocked(
|
|
835
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
632
836
|
workspaceState: {
|
|
633
837
|
openFiles: [],
|
|
634
838
|
},
|
|
@@ -643,11 +847,6 @@ ${JSON.stringify({
|
|
|
643
847
|
getHistory: vi.fn().mockReturnValue([]),
|
|
644
848
|
};
|
|
645
849
|
client['chat'] = mockChat;
|
|
646
|
-
const mockGenerator = {
|
|
647
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
648
|
-
generateContent: mockGenerateContentFn,
|
|
649
|
-
};
|
|
650
|
-
client['contentGenerator'] = mockGenerator;
|
|
651
850
|
const initialRequest = [{ text: 'Hi' }];
|
|
652
851
|
// Act
|
|
653
852
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -655,12 +854,16 @@ ${JSON.stringify({
|
|
|
655
854
|
// consume stream
|
|
656
855
|
}
|
|
657
856
|
// Assert
|
|
658
|
-
expect(
|
|
659
|
-
|
|
857
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
858
|
+
// The `turn.run` method is now called with the model name as the first
|
|
859
|
+
// argument. We use `expect.any(String)` because this test is
|
|
860
|
+
// concerned with the IDE context logic, not the model routing,
|
|
861
|
+
// which is tested in its own dedicated suite.
|
|
862
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith(expect.any(String), initialRequest, expect.any(Object));
|
|
660
863
|
});
|
|
661
864
|
it('should add context if ideMode is enabled and there is one active file', async () => {
|
|
662
865
|
// Arrange
|
|
663
|
-
vi.mocked(
|
|
866
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
664
867
|
workspaceState: {
|
|
665
868
|
openFiles: [
|
|
666
869
|
{
|
|
@@ -674,6 +877,11 @@ ${JSON.stringify({
|
|
|
674
877
|
},
|
|
675
878
|
});
|
|
676
879
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
880
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
881
|
+
originalTokenCount: 0,
|
|
882
|
+
newTokenCount: 0,
|
|
883
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
884
|
+
});
|
|
677
885
|
const mockStream = (async function* () {
|
|
678
886
|
yield { type: 'content', value: 'Hello' };
|
|
679
887
|
})();
|
|
@@ -683,11 +891,6 @@ ${JSON.stringify({
|
|
|
683
891
|
getHistory: vi.fn().mockReturnValue([]),
|
|
684
892
|
};
|
|
685
893
|
client['chat'] = mockChat;
|
|
686
|
-
const mockGenerator = {
|
|
687
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
688
|
-
generateContent: mockGenerateContentFn,
|
|
689
|
-
};
|
|
690
|
-
client['contentGenerator'] = mockGenerator;
|
|
691
894
|
const initialRequest = [{ text: 'Hi' }];
|
|
692
895
|
// Act
|
|
693
896
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -695,7 +898,7 @@ ${JSON.stringify({
|
|
|
695
898
|
// consume stream
|
|
696
899
|
}
|
|
697
900
|
// Assert
|
|
698
|
-
expect(
|
|
901
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
699
902
|
const expectedContext = `
|
|
700
903
|
Here is the user's editor context as a JSON object. This is for your information only.
|
|
701
904
|
\`\`\`json
|
|
@@ -719,7 +922,7 @@ ${JSON.stringify({
|
|
|
719
922
|
});
|
|
720
923
|
it('should add context if ideMode is enabled and there are open files but no active file', async () => {
|
|
721
924
|
// Arrange
|
|
722
|
-
vi.mocked(
|
|
925
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
723
926
|
workspaceState: {
|
|
724
927
|
openFiles: [
|
|
725
928
|
{
|
|
@@ -734,6 +937,11 @@ ${JSON.stringify({
|
|
|
734
937
|
},
|
|
735
938
|
});
|
|
736
939
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
940
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
941
|
+
originalTokenCount: 0,
|
|
942
|
+
newTokenCount: 0,
|
|
943
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
944
|
+
});
|
|
737
945
|
const mockStream = (async function* () {
|
|
738
946
|
yield { type: 'content', value: 'Hello' };
|
|
739
947
|
})();
|
|
@@ -743,11 +951,6 @@ ${JSON.stringify({
|
|
|
743
951
|
getHistory: vi.fn().mockReturnValue([]),
|
|
744
952
|
};
|
|
745
953
|
client['chat'] = mockChat;
|
|
746
|
-
const mockGenerator = {
|
|
747
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
748
|
-
generateContent: mockGenerateContentFn,
|
|
749
|
-
};
|
|
750
|
-
client['contentGenerator'] = mockGenerator;
|
|
751
954
|
const initialRequest = [{ text: 'Hi' }];
|
|
752
955
|
// Act
|
|
753
956
|
const stream = client.sendMessageStream(initialRequest, new AbortController().signal, 'prompt-id-ide');
|
|
@@ -755,7 +958,7 @@ ${JSON.stringify({
|
|
|
755
958
|
// consume stream
|
|
756
959
|
}
|
|
757
960
|
// Assert
|
|
758
|
-
expect(
|
|
961
|
+
expect(ideContextStore.get).toHaveBeenCalled();
|
|
759
962
|
const expectedContext = `
|
|
760
963
|
Here is the user's editor context as a JSON object. This is for your information only.
|
|
761
964
|
\`\`\`json
|
|
@@ -781,11 +984,6 @@ ${JSON.stringify({
|
|
|
781
984
|
getHistory: vi.fn().mockReturnValue([]),
|
|
782
985
|
};
|
|
783
986
|
client['chat'] = mockChat;
|
|
784
|
-
const mockGenerator = {
|
|
785
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
786
|
-
generateContent: mockGenerateContentFn,
|
|
787
|
-
};
|
|
788
|
-
client['contentGenerator'] = mockGenerator;
|
|
789
987
|
// Act
|
|
790
988
|
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-1');
|
|
791
989
|
// Consume the stream manually to get the final return value.
|
|
@@ -818,11 +1016,6 @@ ${JSON.stringify({
|
|
|
818
1016
|
getHistory: vi.fn().mockReturnValue([]),
|
|
819
1017
|
};
|
|
820
1018
|
client['chat'] = mockChat;
|
|
821
|
-
const mockGenerator = {
|
|
822
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
823
|
-
generateContent: mockGenerateContentFn,
|
|
824
|
-
};
|
|
825
|
-
client['contentGenerator'] = mockGenerator;
|
|
826
1019
|
// Use a signal that never gets aborted
|
|
827
1020
|
const abortController = new AbortController();
|
|
828
1021
|
const signal = abortController.signal;
|
|
@@ -885,11 +1078,6 @@ ${JSON.stringify({
|
|
|
885
1078
|
getHistory: vi.fn().mockReturnValue([]),
|
|
886
1079
|
};
|
|
887
1080
|
client['chat'] = mockChat;
|
|
888
|
-
const mockGenerator = {
|
|
889
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
890
|
-
generateContent: mockGenerateContentFn,
|
|
891
|
-
};
|
|
892
|
-
client['contentGenerator'] = mockGenerator;
|
|
893
1081
|
// Act & Assert
|
|
894
1082
|
// Run up to the limit
|
|
895
1083
|
for (let i = 0; i < MAX_SESSION_TURNS; i++) {
|
|
@@ -928,11 +1116,6 @@ ${JSON.stringify({
|
|
|
928
1116
|
getHistory: vi.fn().mockReturnValue([]),
|
|
929
1117
|
};
|
|
930
1118
|
client['chat'] = mockChat;
|
|
931
|
-
const mockGenerator = {
|
|
932
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
933
|
-
generateContent: mockGenerateContentFn,
|
|
934
|
-
};
|
|
935
|
-
client['contentGenerator'] = mockGenerator;
|
|
936
1119
|
// Use a signal that never gets aborted
|
|
937
1120
|
const abortController = new AbortController();
|
|
938
1121
|
const signal = abortController.signal;
|
|
@@ -971,19 +1154,107 @@ ${JSON.stringify({
|
|
|
971
1154
|
console.log(`Infinite loop protection working: checkNextSpeaker called ${callCount} times, ` +
|
|
972
1155
|
`${eventCount} events generated (properly bounded by MAX_TURNS)`);
|
|
973
1156
|
});
|
|
1157
|
+
describe('Model Routing', () => {
|
|
1158
|
+
let mockRouterService;
|
|
1159
|
+
beforeEach(() => {
|
|
1160
|
+
mockRouterService = {
|
|
1161
|
+
route: vi
|
|
1162
|
+
.fn()
|
|
1163
|
+
.mockResolvedValue({ model: 'routed-model', reason: 'test' }),
|
|
1164
|
+
};
|
|
1165
|
+
vi.mocked(mockConfig.getModelRouterService).mockReturnValue(mockRouterService);
|
|
1166
|
+
mockTurnRunFn.mockReturnValue((async function* () {
|
|
1167
|
+
yield { type: 'content', value: 'Hello' };
|
|
1168
|
+
})());
|
|
1169
|
+
});
|
|
1170
|
+
it('should use the model router service to select a model on the first turn', async () => {
|
|
1171
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1172
|
+
await fromAsync(stream); // consume stream
|
|
1173
|
+
expect(mockConfig.getModelRouterService).toHaveBeenCalled();
|
|
1174
|
+
expect(mockRouterService.route).toHaveBeenCalled();
|
|
1175
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', // The model from the router
|
|
1176
|
+
[{ text: 'Hi' }], expect.any(Object));
|
|
1177
|
+
});
|
|
1178
|
+
it('should use the same model for subsequent turns in the same prompt (stickiness)', async () => {
|
|
1179
|
+
// First turn
|
|
1180
|
+
let stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1181
|
+
await fromAsync(stream);
|
|
1182
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
|
1183
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', [{ text: 'Hi' }], expect.any(Object));
|
|
1184
|
+
// Second turn
|
|
1185
|
+
stream = client.sendMessageStream([{ text: 'Continue' }], new AbortController().signal, 'prompt-1');
|
|
1186
|
+
await fromAsync(stream);
|
|
1187
|
+
// Router should not be called again
|
|
1188
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
|
1189
|
+
// Should stick to the first model
|
|
1190
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', [{ text: 'Continue' }], expect.any(Object));
|
|
1191
|
+
});
|
|
1192
|
+
it('should reset the sticky model and re-route when the prompt_id changes', async () => {
|
|
1193
|
+
// First prompt
|
|
1194
|
+
let stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1195
|
+
await fromAsync(stream);
|
|
1196
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(1);
|
|
1197
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('routed-model', [{ text: 'Hi' }], expect.any(Object));
|
|
1198
|
+
// New prompt
|
|
1199
|
+
mockRouterService.route.mockResolvedValue({
|
|
1200
|
+
model: 'new-routed-model',
|
|
1201
|
+
reason: 'test',
|
|
1202
|
+
});
|
|
1203
|
+
stream = client.sendMessageStream([{ text: 'A new topic' }], new AbortController().signal, 'prompt-2');
|
|
1204
|
+
await fromAsync(stream);
|
|
1205
|
+
// Router should be called again for the new prompt
|
|
1206
|
+
expect(mockRouterService.route).toHaveBeenCalledTimes(2);
|
|
1207
|
+
// Should use the newly routed model
|
|
1208
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith('new-routed-model', [{ text: 'A new topic' }], expect.any(Object));
|
|
1209
|
+
});
|
|
1210
|
+
it('should use the fallback model and bypass routing when in fallback mode', async () => {
|
|
1211
|
+
vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(true);
|
|
1212
|
+
mockRouterService.route.mockResolvedValue({
|
|
1213
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1214
|
+
reason: 'fallback',
|
|
1215
|
+
});
|
|
1216
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-1');
|
|
1217
|
+
await fromAsync(stream);
|
|
1218
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith(DEFAULT_GEMINI_FLASH_MODEL, [{ text: 'Hi' }], expect.any(Object));
|
|
1219
|
+
});
|
|
1220
|
+
it('should stick to the fallback model for the entire sequence even if fallback mode ends', async () => {
|
|
1221
|
+
// Start the sequence in fallback mode
|
|
1222
|
+
vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(true);
|
|
1223
|
+
mockRouterService.route.mockResolvedValue({
|
|
1224
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1225
|
+
reason: 'fallback',
|
|
1226
|
+
});
|
|
1227
|
+
let stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-fallback-stickiness');
|
|
1228
|
+
await fromAsync(stream);
|
|
1229
|
+
// First call should use fallback model
|
|
1230
|
+
expect(mockTurnRunFn).toHaveBeenCalledWith(DEFAULT_GEMINI_FLASH_MODEL, [{ text: 'Hi' }], expect.any(Object));
|
|
1231
|
+
// End fallback mode
|
|
1232
|
+
vi.mocked(mockConfig.isInFallbackMode).mockReturnValue(false);
|
|
1233
|
+
// Second call in the same sequence
|
|
1234
|
+
stream = client.sendMessageStream([{ text: 'Continue' }], new AbortController().signal, 'prompt-fallback-stickiness');
|
|
1235
|
+
await fromAsync(stream);
|
|
1236
|
+
// Router should still not be called, and it should stick to the fallback model
|
|
1237
|
+
expect(mockTurnRunFn).toHaveBeenCalledTimes(2); // Ensure it was called again
|
|
1238
|
+
expect(mockTurnRunFn).toHaveBeenLastCalledWith(DEFAULT_GEMINI_FLASH_MODEL, // Still the fallback model
|
|
1239
|
+
[{ text: 'Continue' }], expect.any(Object));
|
|
1240
|
+
});
|
|
1241
|
+
});
|
|
974
1242
|
describe('Editor context delta', () => {
|
|
975
1243
|
const mockStream = (async function* () {
|
|
976
1244
|
yield { type: 'content', value: 'Hello' };
|
|
977
1245
|
})();
|
|
978
1246
|
beforeEach(() => {
|
|
979
1247
|
client['forceFullIdeContext'] = false; // Reset before each delta test
|
|
980
|
-
vi.spyOn(client, 'tryCompressChat').mockResolvedValue(
|
|
1248
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
1249
|
+
originalTokenCount: 0,
|
|
1250
|
+
newTokenCount: 0,
|
|
1251
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
1252
|
+
});
|
|
981
1253
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
982
1254
|
mockTurnRunFn.mockReturnValue(mockStream);
|
|
983
1255
|
const mockChat = {
|
|
984
1256
|
addHistory: vi.fn(),
|
|
985
1257
|
setHistory: vi.fn(),
|
|
986
|
-
sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
|
|
987
1258
|
// Assume history is not empty for delta checks
|
|
988
1259
|
getHistory: vi
|
|
989
1260
|
.fn()
|
|
@@ -992,11 +1263,6 @@ ${JSON.stringify({
|
|
|
992
1263
|
]),
|
|
993
1264
|
};
|
|
994
1265
|
client['chat'] = mockChat;
|
|
995
|
-
const mockGenerator = {
|
|
996
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
997
|
-
generateContent: mockGenerateContentFn,
|
|
998
|
-
};
|
|
999
|
-
client['contentGenerator'] = mockGenerator;
|
|
1000
1266
|
});
|
|
1001
1267
|
const testCases = [
|
|
1002
1268
|
{
|
|
@@ -1112,7 +1378,7 @@ ${JSON.stringify({
|
|
|
1112
1378
|
},
|
|
1113
1379
|
};
|
|
1114
1380
|
// Setup current context
|
|
1115
|
-
vi.mocked(
|
|
1381
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
1116
1382
|
workspaceState: {
|
|
1117
1383
|
openFiles: [
|
|
1118
1384
|
{ ...currentActiveFile, isActive: true, timestamp: Date.now() },
|
|
@@ -1158,7 +1424,7 @@ ${JSON.stringify({
|
|
|
1158
1424
|
},
|
|
1159
1425
|
};
|
|
1160
1426
|
// Setup current context (same as previous)
|
|
1161
|
-
vi.mocked(
|
|
1427
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
1162
1428
|
workspaceState: {
|
|
1163
1429
|
openFiles: [
|
|
1164
1430
|
{ ...activeFile, isActive: true, timestamp: Date.now() },
|
|
@@ -1190,7 +1456,11 @@ ${JSON.stringify({
|
|
|
1190
1456
|
describe('IDE context with pending tool calls', () => {
|
|
1191
1457
|
let mockChat;
|
|
1192
1458
|
beforeEach(() => {
|
|
1193
|
-
vi.spyOn(client, 'tryCompressChat').mockResolvedValue(
|
|
1459
|
+
vi.spyOn(client, 'tryCompressChat').mockResolvedValue({
|
|
1460
|
+
originalTokenCount: 0,
|
|
1461
|
+
newTokenCount: 0,
|
|
1462
|
+
compressionStatus: CompressionStatus.COMPRESSED,
|
|
1463
|
+
});
|
|
1194
1464
|
const mockStream = (async function* () {
|
|
1195
1465
|
yield { type: 'content', value: 'response' };
|
|
1196
1466
|
})();
|
|
@@ -1199,15 +1469,10 @@ ${JSON.stringify({
|
|
|
1199
1469
|
addHistory: vi.fn(),
|
|
1200
1470
|
getHistory: vi.fn().mockReturnValue([]), // Default empty history
|
|
1201
1471
|
setHistory: vi.fn(),
|
|
1202
|
-
sendMessage: vi.fn().mockResolvedValue({ text: 'summary' }),
|
|
1203
1472
|
};
|
|
1204
1473
|
client['chat'] = mockChat;
|
|
1205
|
-
const mockGenerator = {
|
|
1206
|
-
countTokens: vi.fn().mockResolvedValue({ totalTokens: 0 }),
|
|
1207
|
-
};
|
|
1208
|
-
client['contentGenerator'] = mockGenerator;
|
|
1209
1474
|
vi.spyOn(client['config'], 'getIdeMode').mockReturnValue(true);
|
|
1210
|
-
vi.mocked(
|
|
1475
|
+
vi.mocked(ideContextStore.get).mockReturnValue({
|
|
1211
1476
|
workspaceState: {
|
|
1212
1477
|
openFiles: [{ path: '/path/to/file.ts', timestamp: Date.now() }],
|
|
1213
1478
|
},
|
|
@@ -1283,7 +1548,7 @@ ${JSON.stringify({
|
|
|
1283
1548
|
openFiles: [{ path: '/path/to/fileA.ts', timestamp: Date.now() }],
|
|
1284
1549
|
},
|
|
1285
1550
|
};
|
|
1286
|
-
vi.mocked(
|
|
1551
|
+
vi.mocked(ideContextStore.get).mockReturnValue(initialIdeContext);
|
|
1287
1552
|
// Act: Send the tool response
|
|
1288
1553
|
let stream = client.sendMessageStream([
|
|
1289
1554
|
{
|
|
@@ -1329,7 +1594,7 @@ ${JSON.stringify({
|
|
|
1329
1594
|
openFiles: [{ path: '/path/to/fileB.ts', timestamp: Date.now() }],
|
|
1330
1595
|
},
|
|
1331
1596
|
};
|
|
1332
|
-
vi.mocked(
|
|
1597
|
+
vi.mocked(ideContextStore.get).mockReturnValue(newIdeContext);
|
|
1333
1598
|
// Act: Send a new, regular user message
|
|
1334
1599
|
stream = client.sendMessageStream([{ text: 'Thanks!' }], new AbortController().signal, 'prompt-id-final');
|
|
1335
1600
|
for await (const _ of stream) {
|
|
@@ -1359,7 +1624,7 @@ ${JSON.stringify({
|
|
|
1359
1624
|
],
|
|
1360
1625
|
},
|
|
1361
1626
|
};
|
|
1362
|
-
vi.mocked(
|
|
1627
|
+
vi.mocked(ideContextStore.get).mockReturnValue(contextA);
|
|
1363
1628
|
// Act: Send a regular message to establish the initial context
|
|
1364
1629
|
let stream = client.sendMessageStream([{ text: 'Initial message' }], new AbortController().signal, 'prompt-id-initial');
|
|
1365
1630
|
for await (const _ of stream) {
|
|
@@ -1392,7 +1657,7 @@ ${JSON.stringify({
|
|
|
1392
1657
|
],
|
|
1393
1658
|
},
|
|
1394
1659
|
};
|
|
1395
|
-
vi.mocked(
|
|
1660
|
+
vi.mocked(ideContextStore.get).mockReturnValue(contextB);
|
|
1396
1661
|
// Act: Send the tool response
|
|
1397
1662
|
stream = client.sendMessageStream([
|
|
1398
1663
|
{
|
|
@@ -1437,7 +1702,7 @@ ${JSON.stringify({
|
|
|
1437
1702
|
],
|
|
1438
1703
|
},
|
|
1439
1704
|
};
|
|
1440
|
-
vi.mocked(
|
|
1705
|
+
vi.mocked(ideContextStore.get).mockReturnValue(contextC);
|
|
1441
1706
|
// Act: Send a new, regular user message
|
|
1442
1707
|
stream = client.sendMessageStream([{ text: 'Thanks!' }], new AbortController().signal, 'prompt-id-final');
|
|
1443
1708
|
for await (const _ of stream) {
|
|
@@ -1453,150 +1718,130 @@ ${JSON.stringify({
|
|
|
1453
1718
|
expect(JSON.stringify(finalCall)).toContain('fileC.ts');
|
|
1454
1719
|
});
|
|
1455
1720
|
});
|
|
1721
|
+
it('should not call checkNextSpeaker when turn.run() yields an error', async () => {
|
|
1722
|
+
// Arrange
|
|
1723
|
+
const { checkNextSpeaker } = await import('../utils/nextSpeakerChecker.js');
|
|
1724
|
+
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
|
|
1725
|
+
const mockStream = (async function* () {
|
|
1726
|
+
yield {
|
|
1727
|
+
type: GeminiEventType.Error,
|
|
1728
|
+
value: { error: { message: 'test error' } },
|
|
1729
|
+
};
|
|
1730
|
+
})();
|
|
1731
|
+
mockTurnRunFn.mockReturnValue(mockStream);
|
|
1732
|
+
const mockChat = {
|
|
1733
|
+
addHistory: vi.fn(),
|
|
1734
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1735
|
+
};
|
|
1736
|
+
client['chat'] = mockChat;
|
|
1737
|
+
// Act
|
|
1738
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
|
|
1739
|
+
for await (const _ of stream) {
|
|
1740
|
+
// consume stream
|
|
1741
|
+
}
|
|
1742
|
+
// Assert
|
|
1743
|
+
expect(mockCheckNextSpeaker).not.toHaveBeenCalled();
|
|
1744
|
+
});
|
|
1745
|
+
it('should not call checkNextSpeaker when turn.run() yields a value then an error', async () => {
|
|
1746
|
+
// Arrange
|
|
1747
|
+
const { checkNextSpeaker } = await import('../utils/nextSpeakerChecker.js');
|
|
1748
|
+
const mockCheckNextSpeaker = vi.mocked(checkNextSpeaker);
|
|
1749
|
+
const mockStream = (async function* () {
|
|
1750
|
+
yield { type: GeminiEventType.Content, value: 'some content' };
|
|
1751
|
+
yield {
|
|
1752
|
+
type: GeminiEventType.Error,
|
|
1753
|
+
value: { error: { message: 'test error' } },
|
|
1754
|
+
};
|
|
1755
|
+
})();
|
|
1756
|
+
mockTurnRunFn.mockReturnValue(mockStream);
|
|
1757
|
+
const mockChat = {
|
|
1758
|
+
addHistory: vi.fn(),
|
|
1759
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1760
|
+
};
|
|
1761
|
+
client['chat'] = mockChat;
|
|
1762
|
+
// Act
|
|
1763
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-error');
|
|
1764
|
+
for await (const _ of stream) {
|
|
1765
|
+
// consume stream
|
|
1766
|
+
}
|
|
1767
|
+
// Assert
|
|
1768
|
+
expect(mockCheckNextSpeaker).not.toHaveBeenCalled();
|
|
1769
|
+
});
|
|
1770
|
+
it('should abort linked signal when loop is detected', async () => {
|
|
1771
|
+
// Arrange
|
|
1772
|
+
vi.spyOn(client['loopDetector'], 'turnStarted').mockResolvedValue(false);
|
|
1773
|
+
vi.spyOn(client['loopDetector'], 'addAndCheck')
|
|
1774
|
+
.mockReturnValueOnce(false)
|
|
1775
|
+
.mockReturnValueOnce(true);
|
|
1776
|
+
let capturedSignal;
|
|
1777
|
+
mockTurnRunFn.mockImplementation((model, request, signal) => {
|
|
1778
|
+
capturedSignal = signal;
|
|
1779
|
+
return (async function* () {
|
|
1780
|
+
yield { type: 'content', value: 'First event' };
|
|
1781
|
+
yield { type: 'content', value: 'Second event' };
|
|
1782
|
+
})();
|
|
1783
|
+
});
|
|
1784
|
+
const mockChat = {
|
|
1785
|
+
addHistory: vi.fn(),
|
|
1786
|
+
getHistory: vi.fn().mockReturnValue([]),
|
|
1787
|
+
};
|
|
1788
|
+
client['chat'] = mockChat;
|
|
1789
|
+
// Act
|
|
1790
|
+
const stream = client.sendMessageStream([{ text: 'Hi' }], new AbortController().signal, 'prompt-id-loop');
|
|
1791
|
+
const events = [];
|
|
1792
|
+
for await (const event of stream) {
|
|
1793
|
+
events.push(event);
|
|
1794
|
+
}
|
|
1795
|
+
// Assert
|
|
1796
|
+
expect(events).toContainEqual({ type: GeminiEventType.LoopDetected });
|
|
1797
|
+
expect(capturedSignal.aborted).toBe(true);
|
|
1798
|
+
});
|
|
1456
1799
|
});
|
|
1457
1800
|
describe('generateContent', () => {
|
|
1801
|
+
it('should call generateContent with the correct parameters', async () => {
|
|
1802
|
+
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
1803
|
+
const generationConfig = { temperature: 0.5 };
|
|
1804
|
+
const abortSignal = new AbortController().signal;
|
|
1805
|
+
await client.generateContent(contents, generationConfig, abortSignal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
1806
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
1807
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1808
|
+
config: {
|
|
1809
|
+
abortSignal,
|
|
1810
|
+
systemInstruction: getCoreSystemPrompt(''),
|
|
1811
|
+
temperature: 0.5,
|
|
1812
|
+
topP: 1,
|
|
1813
|
+
},
|
|
1814
|
+
contents,
|
|
1815
|
+
}, 'test-session-id');
|
|
1816
|
+
});
|
|
1458
1817
|
it('should use current model from config for content generation', async () => {
|
|
1459
1818
|
const initialModel = client['config'].getModel();
|
|
1460
1819
|
const contents = [{ role: 'user', parts: [{ text: 'test' }] }];
|
|
1461
1820
|
const currentModel = initialModel + '-changed';
|
|
1462
1821
|
vi.spyOn(client['config'], 'getModel').mockReturnValueOnce(currentModel);
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
generateContent: mockGenerateContentFn,
|
|
1466
|
-
};
|
|
1467
|
-
client['contentGenerator'] = mockGenerator;
|
|
1468
|
-
await client.generateContent(contents, {}, new AbortController().signal);
|
|
1469
|
-
expect(mockGenerateContentFn).not.toHaveBeenCalledWith({
|
|
1822
|
+
await client.generateContent(contents, {}, new AbortController().signal, DEFAULT_GEMINI_FLASH_MODEL);
|
|
1823
|
+
expect(mockContentGenerator.generateContent).not.toHaveBeenCalledWith({
|
|
1470
1824
|
model: initialModel,
|
|
1471
1825
|
config: expect.any(Object),
|
|
1472
1826
|
contents,
|
|
1473
1827
|
});
|
|
1474
|
-
expect(
|
|
1475
|
-
model:
|
|
1828
|
+
expect(mockContentGenerator.generateContent).toHaveBeenCalledWith({
|
|
1829
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1476
1830
|
config: expect.any(Object),
|
|
1477
1831
|
contents,
|
|
1478
1832
|
}, 'test-session-id');
|
|
1479
1833
|
});
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
const
|
|
1484
|
-
const
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
{ role: 'model', parts: [{ text: 'Long response' }] },
|
|
1492
|
-
];
|
|
1493
|
-
const mockChat = {
|
|
1494
|
-
getHistory: vi.fn().mockReturnValue(mockChatHistory),
|
|
1495
|
-
setHistory: vi.fn(),
|
|
1496
|
-
sendMessage: mockSendMessage,
|
|
1497
|
-
};
|
|
1498
|
-
const mockGenerator = {
|
|
1499
|
-
countTokens: mockCountTokens,
|
|
1500
|
-
};
|
|
1501
|
-
// mock the model has been changed between calls of `countTokens`
|
|
1502
|
-
const firstCurrentModel = initialModel + '-changed-1';
|
|
1503
|
-
const secondCurrentModel = initialModel + '-changed-2';
|
|
1504
|
-
vi.spyOn(client['config'], 'getModel')
|
|
1505
|
-
.mockReturnValueOnce(firstCurrentModel)
|
|
1506
|
-
.mockReturnValueOnce(secondCurrentModel);
|
|
1507
|
-
client['chat'] = mockChat;
|
|
1508
|
-
client['contentGenerator'] = mockGenerator;
|
|
1509
|
-
client['startChat'] = vi.fn().mockResolvedValue(mockChat);
|
|
1510
|
-
const result = await client.tryCompressChat('prompt-id-4', true);
|
|
1511
|
-
expect(mockCountTokens).toHaveBeenCalledTimes(2);
|
|
1512
|
-
expect(mockCountTokens).toHaveBeenNthCalledWith(1, {
|
|
1513
|
-
model: firstCurrentModel,
|
|
1514
|
-
contents: mockChatHistory,
|
|
1515
|
-
});
|
|
1516
|
-
expect(mockCountTokens).toHaveBeenNthCalledWith(2, {
|
|
1517
|
-
model: secondCurrentModel,
|
|
1518
|
-
contents: expect.any(Array),
|
|
1519
|
-
});
|
|
1520
|
-
expect(result).toEqual({
|
|
1521
|
-
originalTokenCount: 100000,
|
|
1522
|
-
newTokenCount: 5000,
|
|
1523
|
-
});
|
|
1524
|
-
});
|
|
1525
|
-
});
|
|
1526
|
-
describe('handleFlashFallback', () => {
|
|
1527
|
-
it('should use current model from config when checking for fallback', async () => {
|
|
1528
|
-
const initialModel = client['config'].getModel();
|
|
1529
|
-
const fallbackModel = DEFAULT_GEMINI_FLASH_MODEL;
|
|
1530
|
-
// mock config been changed
|
|
1531
|
-
const currentModel = initialModel + '-changed';
|
|
1532
|
-
const getModelSpy = vi.spyOn(client['config'], 'getModel');
|
|
1533
|
-
getModelSpy.mockReturnValue(currentModel);
|
|
1534
|
-
const mockFallbackHandler = vi.fn().mockResolvedValue(true);
|
|
1535
|
-
client['config'].flashFallbackHandler = mockFallbackHandler;
|
|
1536
|
-
client['config'].setModel = vi.fn();
|
|
1537
|
-
const result = await client['handleFlashFallback'](AuthType.LOGIN_WITH_GOOGLE);
|
|
1538
|
-
expect(result).toBe(fallbackModel);
|
|
1539
|
-
expect(mockFallbackHandler).toHaveBeenCalledWith(currentModel, fallbackModel, undefined);
|
|
1540
|
-
});
|
|
1541
|
-
});
|
|
1542
|
-
describe('setHistory', () => {
|
|
1543
|
-
it('should strip thought signatures when stripThoughts is true', () => {
|
|
1544
|
-
const mockChat = {
|
|
1545
|
-
setHistory: vi.fn(),
|
|
1546
|
-
};
|
|
1547
|
-
client['chat'] = mockChat;
|
|
1548
|
-
const historyWithThoughts = [
|
|
1549
|
-
{
|
|
1550
|
-
role: 'user',
|
|
1551
|
-
parts: [{ text: 'hello' }],
|
|
1552
|
-
},
|
|
1553
|
-
{
|
|
1554
|
-
role: 'model',
|
|
1555
|
-
parts: [
|
|
1556
|
-
{ text: 'thinking...', thoughtSignature: 'thought-123' },
|
|
1557
|
-
{
|
|
1558
|
-
functionCall: { name: 'test', args: {} },
|
|
1559
|
-
thoughtSignature: 'thought-456',
|
|
1560
|
-
},
|
|
1561
|
-
],
|
|
1562
|
-
},
|
|
1563
|
-
];
|
|
1564
|
-
client.setHistory(historyWithThoughts, { stripThoughts: true });
|
|
1565
|
-
const expectedHistory = [
|
|
1566
|
-
{
|
|
1567
|
-
role: 'user',
|
|
1568
|
-
parts: [{ text: 'hello' }],
|
|
1569
|
-
},
|
|
1570
|
-
{
|
|
1571
|
-
role: 'model',
|
|
1572
|
-
parts: [
|
|
1573
|
-
{ text: 'thinking...' },
|
|
1574
|
-
{ functionCall: { name: 'test', args: {} } },
|
|
1575
|
-
],
|
|
1576
|
-
},
|
|
1577
|
-
];
|
|
1578
|
-
expect(mockChat.setHistory).toHaveBeenCalledWith(expectedHistory);
|
|
1579
|
-
});
|
|
1580
|
-
it('should not strip thought signatures when stripThoughts is false', () => {
|
|
1581
|
-
const mockChat = {
|
|
1582
|
-
setHistory: vi.fn(),
|
|
1583
|
-
};
|
|
1584
|
-
client['chat'] = mockChat;
|
|
1585
|
-
const historyWithThoughts = [
|
|
1586
|
-
{
|
|
1587
|
-
role: 'user',
|
|
1588
|
-
parts: [{ text: 'hello' }],
|
|
1589
|
-
},
|
|
1590
|
-
{
|
|
1591
|
-
role: 'model',
|
|
1592
|
-
parts: [
|
|
1593
|
-
{ text: 'thinking...', thoughtSignature: 'thought-123' },
|
|
1594
|
-
{ text: 'ok', thoughtSignature: 'thought-456' },
|
|
1595
|
-
],
|
|
1596
|
-
},
|
|
1597
|
-
];
|
|
1598
|
-
client.setHistory(historyWithThoughts, { stripThoughts: false });
|
|
1599
|
-
expect(mockChat.setHistory).toHaveBeenCalledWith(historyWithThoughts);
|
|
1834
|
+
it('should use the Flash model when fallback mode is active', async () => {
|
|
1835
|
+
const contents = [{ role: 'user', parts: [{ text: 'hello' }] }];
|
|
1836
|
+
const generationConfig = { temperature: 0.5 };
|
|
1837
|
+
const abortSignal = new AbortController().signal;
|
|
1838
|
+
const requestedModel = 'gemini-2.5-pro'; // A non-flash model
|
|
1839
|
+
// Mock config to be in fallback mode
|
|
1840
|
+
vi.spyOn(client['config'], 'isInFallbackMode').mockReturnValue(true);
|
|
1841
|
+
await client.generateContent(contents, generationConfig, abortSignal, requestedModel);
|
|
1842
|
+
expect(mockGenerateContentFn).toHaveBeenCalledWith(expect.objectContaining({
|
|
1843
|
+
model: DEFAULT_GEMINI_FLASH_MODEL,
|
|
1844
|
+
}), 'test-session-id');
|
|
1600
1845
|
});
|
|
1601
1846
|
});
|
|
1602
1847
|
});
|