@google/gemini-cli-core 0.0.8999999 → 0.0.77777773
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +105 -62
- package/dist/index.d.ts +5 -2
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/src/agents/codebase-investigator.d.ts +46 -0
- package/dist/src/agents/codebase-investigator.js +136 -0
- package/dist/src/agents/codebase-investigator.js.map +1 -0
- package/dist/src/agents/executor.d.ts +92 -0
- package/dist/src/agents/executor.js +579 -0
- package/dist/src/agents/executor.js.map +1 -0
- package/dist/src/agents/executor.test.d.ts +6 -0
- package/dist/src/agents/executor.test.js +595 -0
- package/dist/src/agents/executor.test.js.map +1 -0
- package/dist/src/agents/invocation.d.ts +46 -0
- package/dist/src/agents/invocation.js +102 -0
- package/dist/src/agents/invocation.js.map +1 -0
- package/dist/src/agents/invocation.test.d.ts +6 -0
- package/dist/src/agents/invocation.test.js +215 -0
- package/dist/src/agents/invocation.test.js.map +1 -0
- package/dist/src/agents/registry.d.ts +36 -0
- package/dist/src/agents/registry.js +60 -0
- package/dist/src/agents/registry.js.map +1 -0
- package/dist/src/agents/registry.test.d.ts +6 -0
- package/dist/src/agents/registry.test.js +146 -0
- package/dist/src/agents/registry.test.js.map +1 -0
- package/dist/src/agents/schema-utils.d.ts +39 -0
- package/dist/src/agents/schema-utils.js +57 -0
- package/dist/src/agents/schema-utils.js.map +1 -0
- package/dist/src/agents/schema-utils.test.d.ts +6 -0
- package/dist/src/agents/schema-utils.test.js +144 -0
- package/dist/src/agents/schema-utils.test.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.d.ts +38 -0
- package/dist/src/agents/subagent-tool-wrapper.js +48 -0
- package/dist/src/agents/subagent-tool-wrapper.js.map +1 -0
- package/dist/src/agents/subagent-tool-wrapper.test.d.ts +6 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js +112 -0
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -0
- package/dist/src/agents/types.d.ts +145 -0
- package/dist/src/agents/types.js +18 -0
- package/dist/src/agents/types.js.map +1 -0
- package/dist/src/agents/utils.d.ts +15 -0
- package/dist/src/agents/utils.js +29 -0
- package/dist/src/agents/utils.js.map +1 -0
- package/dist/src/agents/utils.test.d.ts +6 -0
- package/dist/src/agents/utils.test.js +87 -0
- package/dist/src/agents/utils.test.js.map +1 -0
- package/dist/src/code_assist/oauth-credential-storage.js +1 -1
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +14 -13
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/setup.js +4 -2
- package/dist/src/code_assist/setup.js.map +1 -1
- package/dist/src/config/config.d.ts +46 -15
- package/dist/src/config/config.js +106 -27
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +88 -3
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/constants.d.ts +11 -0
- package/dist/src/config/constants.js +16 -0
- package/dist/src/config/constants.js.map +1 -0
- package/dist/src/config/storage.d.ts +0 -1
- package/dist/src/config/storage.js +2 -2
- package/dist/src/config/storage.js.map +1 -1
- package/dist/src/config/storage.test.js +7 -6
- package/dist/src/config/storage.test.js.map +1 -1
- package/dist/src/core/baseLlmClient.d.ts +4 -0
- package/dist/src/core/baseLlmClient.js +24 -23
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +76 -13
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.d.ts +3 -1
- package/dist/src/core/client.js +68 -47
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +385 -134
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.js +3 -1
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/coreToolScheduler.js +24 -15
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +359 -23
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +11 -14
- package/dist/src/core/geminiChat.js +75 -124
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +260 -239
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/logger.test.js +2 -2
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +11 -11
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/prompts.d.ts +2 -1
- package/dist/src/core/prompts.js +93 -18
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +78 -29
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/turn.d.ts +15 -6
- package/dist/src/core/turn.js +14 -13
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +14 -2
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/ide/detect-ide.d.ts +5 -2
- package/dist/src/ide/detect-ide.js +11 -2
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +34 -0
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +2 -1
- package/dist/src/ide/ide-client.js +25 -20
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +6 -6
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.js +1 -1
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +13 -1
- package/dist/src/ide/ide-installer.test.js.map +1 -1
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.js +3 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +1 -0
- package/dist/src/mcp/oauth-provider.js +19 -14
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +137 -1
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-utils.js +1 -0
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/sa-impersonation-provider.d.ts +33 -0
- package/dist/src/mcp/sa-impersonation-provider.js +130 -0
- package/dist/src/mcp/sa-impersonation-provider.js.map +1 -0
- package/dist/src/mcp/sa-impersonation-provider.test.d.ts +6 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js +117 -0
- package/dist/src/mcp/sa-impersonation-provider.test.js.map +1 -0
- package/dist/src/mcp/token-storage/file-token-storage.js +2 -1
- package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -1
- package/dist/src/mcp/token-storage/file-token-storage.test.js +4 -3
- package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -1
- package/dist/src/routing/strategies/compositeStrategy.js +4 -3
- package/dist/src/routing/strategies/compositeStrategy.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +3 -2
- package/dist/src/services/chatRecordingService.js +3 -2
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/fileSystemService.d.ts +9 -0
- package/dist/src/services/fileSystemService.js +11 -0
- package/dist/src/services/fileSystemService.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +3 -0
- package/dist/src/services/shellExecutionService.js +165 -49
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +74 -5
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +18 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +156 -11
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.d.ts +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +226 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +19 -3
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +44 -5
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/config.d.ts +31 -0
- package/dist/src/telemetry/config.js +76 -0
- package/dist/src/telemetry/config.js.map +1 -0
- package/dist/src/telemetry/config.test.d.ts +6 -0
- package/dist/src/telemetry/config.test.js +124 -0
- package/dist/src/telemetry/config.test.js.map +1 -0
- package/dist/src/telemetry/constants.d.ts +0 -34
- package/dist/src/telemetry/constants.js +0 -34
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +6 -2
- package/dist/src/telemetry/index.js +17 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +10 -2
- package/dist/src/telemetry/loggers.js +206 -273
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +3 -3
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +260 -13
- package/dist/src/telemetry/loggers.test.js.map +1 -1
- package/dist/src/telemetry/memory-monitor.d.ts +149 -0
- package/dist/src/telemetry/memory-monitor.js +335 -0
- package/dist/src/telemetry/memory-monitor.js.map +1 -0
- package/dist/src/telemetry/memory-monitor.test.d.ts +6 -0
- package/dist/src/telemetry/memory-monitor.test.js +472 -0
- package/dist/src/telemetry/memory-monitor.test.js.map +1 -0
- package/dist/src/telemetry/metrics.d.ts +436 -11
- package/dist/src/telemetry/metrics.js +600 -110
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +898 -16
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sdk.js +4 -1
- package/dist/src/telemetry/sdk.js.map +1 -1
- package/dist/src/telemetry/sdk.test.js +13 -0
- package/dist/src/telemetry/sdk.test.js.map +1 -1
- package/dist/src/telemetry/telemetryAttributes.d.ts +8 -0
- package/dist/src/telemetry/telemetryAttributes.js +18 -0
- package/dist/src/telemetry/telemetryAttributes.js.map +1 -0
- package/dist/src/telemetry/types.d.ts +167 -5
- package/dist/src/telemetry/types.js +692 -35
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
- package/dist/src/telemetry/uiTelemetry.js +1 -1
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +3 -3
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/mock-tool.d.ts +28 -3
- package/dist/src/test-utils/mock-tool.js +71 -1
- package/dist/src/test-utils/mock-tool.js.map +1 -1
- package/dist/src/tools/edit.js +6 -0
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +41 -0
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.js +4 -2
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/ls.js +1 -1
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +5 -14
- package/dist/src/tools/mcp-client.js +50 -97
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +175 -157
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.js +30 -2
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +117 -0
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +1 -1
- package/dist/src/tools/memoryTool.js +1 -2
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/memoryTool.test.js +9 -8
- package/dist/src/tools/memoryTool.test.js.map +1 -1
- package/dist/src/tools/message-bus-integration.test.d.ts +6 -0
- package/dist/src/tools/message-bus-integration.test.js +183 -0
- package/dist/src/tools/message-bus-integration.test.js.map +1 -0
- package/dist/src/tools/shell.js +60 -4
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +2 -1
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +1 -1
- package/dist/src/tools/smart-edit.js +122 -12
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +136 -29
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-error.d.ts +22 -0
- package/dist/src/tools/tool-error.js +28 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +9 -0
- package/dist/src/tools/tool-names.js +18 -0
- package/dist/src/tools/tool-names.js.map +1 -0
- package/dist/src/tools/tool-registry.test.js +10 -10
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +11 -3
- package/dist/src/tools/tools.js +94 -3
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.js +3 -0
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +44 -0
- package/dist/src/tools/web-fetch.test.js.map +1 -1
- package/dist/src/tools/web-search.js +2 -1
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/write-file.js +2 -1
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +25 -0
- package/dist/src/tools/write-todos.js +151 -0
- package/dist/src/tools/write-todos.js.map +1 -0
- package/dist/src/tools/write-todos.test.d.ts +6 -0
- package/dist/src/tools/write-todos.test.js +89 -0
- package/dist/src/tools/write-todos.test.js.map +1 -0
- package/dist/src/utils/bfsFileSearch.d.ts +1 -1
- package/dist/src/utils/editCorrector.js +2 -2
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editor.js +1 -0
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +1 -0
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/errorParsing.d.ts +1 -1
- package/dist/src/utils/errorParsing.js +5 -33
- package/dist/src/utils/errorParsing.js.map +1 -1
- package/dist/src/utils/errorParsing.test.js +0 -88
- package/dist/src/utils/errorParsing.test.js.map +1 -1
- package/dist/src/utils/flashFallback.test.js +26 -45
- package/dist/src/utils/flashFallback.test.js.map +1 -1
- package/dist/src/utils/formatters.d.ts +1 -0
- package/dist/src/utils/formatters.js +2 -1
- package/dist/src/utils/formatters.js.map +1 -1
- package/dist/src/utils/formatters.test.d.ts +6 -0
- package/dist/src/utils/formatters.test.js +26 -0
- package/dist/src/utils/formatters.test.js.map +1 -0
- package/dist/src/utils/getFolderStructure.d.ts +1 -1
- package/dist/src/utils/getFolderStructure.js +1 -1
- package/dist/src/utils/getFolderStructure.js.map +1 -1
- package/dist/src/utils/getFolderStructure.test.js +7 -6
- package/dist/src/utils/getFolderStructure.test.js.map +1 -1
- package/dist/src/utils/googleErrors.d.ts +104 -0
- package/dist/src/utils/googleErrors.js +108 -0
- package/dist/src/utils/googleErrors.js.map +1 -0
- package/dist/src/utils/googleErrors.test.d.ts +6 -0
- package/dist/src/utils/googleErrors.test.js +212 -0
- package/dist/src/utils/googleErrors.test.js.map +1 -0
- package/dist/src/utils/googleQuotaErrors.d.ts +35 -0
- package/dist/src/utils/googleQuotaErrors.js +108 -0
- package/dist/src/utils/googleQuotaErrors.js.map +1 -0
- package/dist/src/utils/googleQuotaErrors.test.d.ts +6 -0
- package/dist/src/utils/googleQuotaErrors.test.js +189 -0
- package/dist/src/utils/googleQuotaErrors.test.js.map +1 -0
- package/dist/src/utils/installationManager.test.js +2 -1
- package/dist/src/utils/installationManager.test.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.js +14 -4
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.js +81 -0
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.d.ts +2 -1
- package/dist/src/utils/memoryDiscovery.js +3 -2
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +99 -21
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +13 -20
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.test.js +14 -0
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
- package/dist/src/utils/pathCorrector.d.ts +25 -0
- package/dist/src/utils/pathCorrector.js +33 -0
- package/dist/src/utils/pathCorrector.js.map +1 -0
- package/dist/src/utils/pathCorrector.test.d.ts +6 -0
- package/dist/src/utils/pathCorrector.test.js +83 -0
- package/dist/src/utils/pathCorrector.test.js.map +1 -0
- package/dist/src/utils/quotaErrorDetection.d.ts +0 -2
- package/dist/src/utils/quotaErrorDetection.js +0 -46
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/retry.d.ts +3 -1
- package/dist/src/utils/retry.js +60 -162
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +105 -135
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/schemaValidator.js +11 -1
- package/dist/src/utils/schemaValidator.js.map +1 -1
- package/dist/src/utils/schemaValidator.test.d.ts +6 -0
- package/dist/src/utils/schemaValidator.test.js +113 -0
- package/dist/src/utils/schemaValidator.test.js.map +1 -0
- package/dist/src/utils/shell-utils.d.ts +1 -0
- package/dist/src/utils/shell-utils.js +6 -2
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +5 -0
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/terminalSerializer.d.ts +1 -4
- package/dist/src/utils/terminalSerializer.js +3 -3
- package/dist/src/utils/terminalSerializer.js.map +1 -1
- package/dist/src/utils/thoughtUtils.d.ts +21 -0
- package/dist/src/utils/thoughtUtils.js +39 -0
- package/dist/src/utils/thoughtUtils.js.map +1 -0
- package/dist/src/utils/thoughtUtils.test.d.ts +6 -0
- package/dist/src/utils/thoughtUtils.test.js +78 -0
- package/dist/src/utils/thoughtUtils.test.js.map +1 -0
- package/dist/src/utils/tool-utils.js +2 -2
- package/dist/src/utils/tool-utils.js.map +1 -1
- package/dist/src/utils/tool-utils.test.js +8 -0
- package/dist/src/utils/tool-utils.test.js.map +1 -1
- package/dist/src/utils/userAccountManager.test.js +2 -1
- package/dist/src/utils/userAccountManager.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/dist/src/test-utils/tools.d.ts +0 -45
- package/dist/src/test-utils/tools.js +0 -105
- package/dist/src/test-utils/tools.js.map +0 -1
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
|
-
import {
|
|
7
|
+
import { ApiError } from '@google/genai';
|
|
8
|
+
import { GeminiChat, InvalidStreamError, StreamEventType, } from './geminiChat.js';
|
|
8
9
|
import { setSimulate429 } from '../utils/testUtils.js';
|
|
9
10
|
import { DEFAULT_GEMINI_FLASH_MODEL } from '../config/models.js';
|
|
10
11
|
import { AuthType } from './contentGenerator.js';
|
|
11
12
|
import {} from '../utils/retry.js';
|
|
12
|
-
import { Kind } from '../tools/tools.js';
|
|
13
13
|
import { uiTelemetryService } from '../telemetry/uiTelemetry.js';
|
|
14
14
|
// Mock fs module to prevent actual file system operations during tests
|
|
15
15
|
const mockFileSystem = new Map();
|
|
@@ -47,13 +47,11 @@ vi.mock('../utils/retry.js', () => ({
|
|
|
47
47
|
vi.mock('../fallback/handler.js', () => ({
|
|
48
48
|
handleFallback: mockHandleFallback,
|
|
49
49
|
}));
|
|
50
|
-
const {
|
|
51
|
-
mockLogInvalidChunk: vi.fn(),
|
|
50
|
+
const { mockLogContentRetry, mockLogContentRetryFailure } = vi.hoisted(() => ({
|
|
52
51
|
mockLogContentRetry: vi.fn(),
|
|
53
52
|
mockLogContentRetryFailure: vi.fn(),
|
|
54
53
|
}));
|
|
55
54
|
vi.mock('../telemetry/loggers.js', () => ({
|
|
56
|
-
logInvalidChunk: mockLogInvalidChunk,
|
|
57
55
|
logContentRetry: mockLogContentRetry,
|
|
58
56
|
logContentRetryFailure: mockLogContentRetryFailure,
|
|
59
57
|
}));
|
|
@@ -187,7 +185,7 @@ describe('GeminiChat', () => {
|
|
|
187
185
|
for await (const _ of stream) {
|
|
188
186
|
/* consume stream */
|
|
189
187
|
}
|
|
190
|
-
})()).rejects.toThrow(
|
|
188
|
+
})()).rejects.toThrow(InvalidStreamError);
|
|
191
189
|
});
|
|
192
190
|
it('should succeed if the stream ends with an invalid part but has a finishReason and contained a valid part', async () => {
|
|
193
191
|
// 1. Mock a stream that sends a valid chunk, then an invalid one, but has a finish reason.
|
|
@@ -352,7 +350,7 @@ describe('GeminiChat', () => {
|
|
|
352
350
|
expect(modelTurn?.parts?.length).toBe(1);
|
|
353
351
|
expect(modelTurn?.parts[0].text).toBe('This is the visible text that should not be lost.');
|
|
354
352
|
});
|
|
355
|
-
it('should
|
|
353
|
+
it('should throw an error when a tool call is followed by an empty stream response', async () => {
|
|
356
354
|
// 1. Setup: A history where the model has just made a function call.
|
|
357
355
|
const initialHistory = [
|
|
358
356
|
{
|
|
@@ -393,20 +391,113 @@ describe('GeminiChat', () => {
|
|
|
393
391
|
},
|
|
394
392
|
},
|
|
395
393
|
}, 'prompt-id-stream-1');
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
394
|
+
// 4. Assert: The stream processing should throw an InvalidStreamError.
|
|
395
|
+
await expect((async () => {
|
|
396
|
+
for await (const _ of stream) {
|
|
397
|
+
// This loop consumes the stream to trigger the internal logic.
|
|
398
|
+
}
|
|
399
|
+
})()).rejects.toThrow(InvalidStreamError);
|
|
400
|
+
});
|
|
401
|
+
it('should succeed when there is a tool call without finish reason', async () => {
|
|
402
|
+
// Setup: Stream with tool call but no finish reason
|
|
403
|
+
const streamWithToolCall = (async function* () {
|
|
404
|
+
yield {
|
|
405
|
+
candidates: [
|
|
406
|
+
{
|
|
407
|
+
content: {
|
|
408
|
+
role: 'model',
|
|
409
|
+
parts: [
|
|
410
|
+
{
|
|
411
|
+
functionCall: {
|
|
412
|
+
name: 'test_function',
|
|
413
|
+
args: { param: 'value' },
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
// No finishReason
|
|
419
|
+
},
|
|
420
|
+
],
|
|
421
|
+
};
|
|
422
|
+
})();
|
|
423
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithToolCall);
|
|
424
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-1');
|
|
425
|
+
// Should not throw an error
|
|
426
|
+
await expect((async () => {
|
|
427
|
+
for await (const _ of stream) {
|
|
428
|
+
// consume stream
|
|
429
|
+
}
|
|
430
|
+
})()).resolves.not.toThrow();
|
|
431
|
+
});
|
|
432
|
+
it('should throw InvalidStreamError when no tool call and no finish reason', async () => {
|
|
433
|
+
// Setup: Stream with text but no finish reason and no tool call
|
|
434
|
+
const streamWithoutFinishReason = (async function* () {
|
|
435
|
+
yield {
|
|
436
|
+
candidates: [
|
|
437
|
+
{
|
|
438
|
+
content: {
|
|
439
|
+
role: 'model',
|
|
440
|
+
parts: [{ text: 'some response' }],
|
|
441
|
+
},
|
|
442
|
+
// No finishReason
|
|
443
|
+
},
|
|
444
|
+
],
|
|
445
|
+
};
|
|
446
|
+
})();
|
|
447
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithoutFinishReason);
|
|
448
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-1');
|
|
449
|
+
await expect((async () => {
|
|
450
|
+
for await (const _ of stream) {
|
|
451
|
+
// consume stream
|
|
452
|
+
}
|
|
453
|
+
})()).rejects.toThrow(InvalidStreamError);
|
|
454
|
+
});
|
|
455
|
+
it('should throw InvalidStreamError when no tool call and empty response text', async () => {
|
|
456
|
+
// Setup: Stream with finish reason but empty response (only thoughts)
|
|
457
|
+
const streamWithEmptyResponse = (async function* () {
|
|
458
|
+
yield {
|
|
459
|
+
candidates: [
|
|
460
|
+
{
|
|
461
|
+
content: {
|
|
462
|
+
role: 'model',
|
|
463
|
+
parts: [{ thought: 'thinking...' }],
|
|
464
|
+
},
|
|
465
|
+
finishReason: 'STOP',
|
|
466
|
+
},
|
|
467
|
+
],
|
|
468
|
+
};
|
|
469
|
+
})();
|
|
470
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(streamWithEmptyResponse);
|
|
471
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-1');
|
|
472
|
+
await expect((async () => {
|
|
473
|
+
for await (const _ of stream) {
|
|
474
|
+
// consume stream
|
|
475
|
+
}
|
|
476
|
+
})()).rejects.toThrow(InvalidStreamError);
|
|
477
|
+
});
|
|
478
|
+
it('should succeed when there is finish reason and response text', async () => {
|
|
479
|
+
// Setup: Stream with both finish reason and text content
|
|
480
|
+
const validStream = (async function* () {
|
|
481
|
+
yield {
|
|
482
|
+
candidates: [
|
|
483
|
+
{
|
|
484
|
+
content: {
|
|
485
|
+
role: 'model',
|
|
486
|
+
parts: [{ text: 'valid response' }],
|
|
487
|
+
},
|
|
488
|
+
finishReason: 'STOP',
|
|
489
|
+
},
|
|
490
|
+
],
|
|
491
|
+
};
|
|
492
|
+
})();
|
|
493
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(validStream);
|
|
494
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-1');
|
|
495
|
+
// Should not throw an error
|
|
496
|
+
await expect((async () => {
|
|
497
|
+
for await (const _ of stream) {
|
|
498
|
+
// consume stream
|
|
499
|
+
}
|
|
500
|
+
})()).resolves.not.toThrow();
|
|
410
501
|
});
|
|
411
502
|
it('should call generateContentStream with the correct parameters', async () => {
|
|
412
503
|
const response = (async function* () {
|
|
@@ -540,7 +631,6 @@ describe('GeminiChat', () => {
|
|
|
540
631
|
chunks.push(chunk);
|
|
541
632
|
}
|
|
542
633
|
// Assertions
|
|
543
|
-
expect(mockLogInvalidChunk).toHaveBeenCalledTimes(1);
|
|
544
634
|
expect(mockLogContentRetry).toHaveBeenCalledTimes(1);
|
|
545
635
|
expect(mockLogContentRetryFailure).not.toHaveBeenCalled();
|
|
546
636
|
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
|
|
@@ -564,6 +654,46 @@ describe('GeminiChat', () => {
|
|
|
564
654
|
// Verify that token counting is not called when usageMetadata is missing
|
|
565
655
|
expect(uiTelemetryService.setLastPromptTokenCount).not.toHaveBeenCalled();
|
|
566
656
|
});
|
|
657
|
+
it('should set temperature to 1 on retry', async () => {
|
|
658
|
+
// Use mockImplementationOnce to provide a fresh, promise-wrapped generator for each attempt.
|
|
659
|
+
vi.mocked(mockContentGenerator.generateContentStream)
|
|
660
|
+
.mockImplementationOnce(async () =>
|
|
661
|
+
// First call returns an invalid stream
|
|
662
|
+
(async function* () {
|
|
663
|
+
yield {
|
|
664
|
+
candidates: [{ content: { parts: [{ text: '' }] } }], // Invalid empty text part
|
|
665
|
+
};
|
|
666
|
+
})())
|
|
667
|
+
.mockImplementationOnce(async () =>
|
|
668
|
+
// Second call returns a valid stream
|
|
669
|
+
(async function* () {
|
|
670
|
+
yield {
|
|
671
|
+
candidates: [
|
|
672
|
+
{
|
|
673
|
+
content: { parts: [{ text: 'Successful response' }] },
|
|
674
|
+
finishReason: 'STOP',
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
};
|
|
678
|
+
})());
|
|
679
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test', config: { temperature: 0.5 } }, 'prompt-id-retry-temperature');
|
|
680
|
+
for await (const _ of stream) {
|
|
681
|
+
// consume stream
|
|
682
|
+
}
|
|
683
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
|
|
684
|
+
// First call should have original temperature
|
|
685
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(1, expect.objectContaining({
|
|
686
|
+
config: expect.objectContaining({
|
|
687
|
+
temperature: 0.5,
|
|
688
|
+
}),
|
|
689
|
+
}), 'prompt-id-retry-temperature');
|
|
690
|
+
// Second call (retry) should have temperature 1
|
|
691
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenNthCalledWith(2, expect.objectContaining({
|
|
692
|
+
config: expect.objectContaining({
|
|
693
|
+
temperature: 1,
|
|
694
|
+
}),
|
|
695
|
+
}), 'prompt-id-retry-temperature');
|
|
696
|
+
});
|
|
567
697
|
it('should fail after all retries on persistent invalid content and report metrics', async () => {
|
|
568
698
|
vi.mocked(mockContentGenerator.generateContentStream).mockImplementation(async () => (async function* () {
|
|
569
699
|
yield {
|
|
@@ -582,15 +712,114 @@ describe('GeminiChat', () => {
|
|
|
582
712
|
for await (const _ of stream) {
|
|
583
713
|
// Must loop to trigger the internal logic that throws.
|
|
584
714
|
}
|
|
585
|
-
}).rejects.toThrow(
|
|
586
|
-
// Should be called
|
|
587
|
-
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(
|
|
588
|
-
expect(
|
|
589
|
-
expect(mockLogContentRetry).toHaveBeenCalledTimes(2);
|
|
715
|
+
}).rejects.toThrow(InvalidStreamError);
|
|
716
|
+
// Should be called 2 times (initial + 1 retry)
|
|
717
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
|
|
718
|
+
expect(mockLogContentRetry).toHaveBeenCalledTimes(1);
|
|
590
719
|
expect(mockLogContentRetryFailure).toHaveBeenCalledTimes(1);
|
|
591
|
-
// History should
|
|
720
|
+
// History should still contain the user message.
|
|
592
721
|
const history = chat.getHistory();
|
|
593
|
-
expect(history.length).toBe(
|
|
722
|
+
expect(history.length).toBe(1);
|
|
723
|
+
expect(history[0]).toEqual({
|
|
724
|
+
role: 'user',
|
|
725
|
+
parts: [{ text: 'test' }],
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
describe('API error retry behavior', () => {
|
|
729
|
+
beforeEach(() => {
|
|
730
|
+
// Use a more direct mock for retry testing
|
|
731
|
+
mockRetryWithBackoff.mockImplementation(async (apiCall) => {
|
|
732
|
+
try {
|
|
733
|
+
return await apiCall();
|
|
734
|
+
}
|
|
735
|
+
catch (error) {
|
|
736
|
+
// Simulate the logic of defaultShouldRetry for ApiError
|
|
737
|
+
let shouldRetry = false;
|
|
738
|
+
if (error instanceof ApiError && error.message) {
|
|
739
|
+
if (error.status === 429 ||
|
|
740
|
+
(error.status >= 500 && error.status < 600)) {
|
|
741
|
+
shouldRetry = true;
|
|
742
|
+
}
|
|
743
|
+
// Explicitly don't retry on these
|
|
744
|
+
if (error.status === 400) {
|
|
745
|
+
shouldRetry = false;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (shouldRetry) {
|
|
749
|
+
// Try again
|
|
750
|
+
return await apiCall();
|
|
751
|
+
}
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
it('should not retry on 400 Bad Request errors', async () => {
|
|
757
|
+
const error400 = new ApiError({ message: 'Bad Request', status: 400 });
|
|
758
|
+
vi.mocked(mockContentGenerator.generateContentStream).mockRejectedValue(error400);
|
|
759
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-400');
|
|
760
|
+
await expect((async () => {
|
|
761
|
+
for await (const _ of stream) {
|
|
762
|
+
/* consume stream */
|
|
763
|
+
}
|
|
764
|
+
})()).rejects.toThrow(error400);
|
|
765
|
+
// Should only be called once (no retry)
|
|
766
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(1);
|
|
767
|
+
});
|
|
768
|
+
it('should retry on 429 Rate Limit errors', async () => {
|
|
769
|
+
const error429 = new ApiError({ message: 'Rate Limited', status: 429 });
|
|
770
|
+
vi.mocked(mockContentGenerator.generateContentStream)
|
|
771
|
+
.mockRejectedValueOnce(error429)
|
|
772
|
+
.mockResolvedValueOnce((async function* () {
|
|
773
|
+
yield {
|
|
774
|
+
candidates: [
|
|
775
|
+
{
|
|
776
|
+
content: { parts: [{ text: 'Success after retry' }] },
|
|
777
|
+
finishReason: 'STOP',
|
|
778
|
+
},
|
|
779
|
+
],
|
|
780
|
+
};
|
|
781
|
+
})());
|
|
782
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-429-retry');
|
|
783
|
+
const events = [];
|
|
784
|
+
for await (const event of stream) {
|
|
785
|
+
events.push(event);
|
|
786
|
+
}
|
|
787
|
+
// Should be called twice (initial + retry)
|
|
788
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
|
|
789
|
+
// Should have successful content
|
|
790
|
+
expect(events.some((e) => e.type === StreamEventType.CHUNK &&
|
|
791
|
+
e.value.candidates?.[0]?.content?.parts?.[0]?.text ===
|
|
792
|
+
'Success after retry')).toBe(true);
|
|
793
|
+
});
|
|
794
|
+
it('should retry on 5xx server errors', async () => {
|
|
795
|
+
const error500 = new ApiError({
|
|
796
|
+
message: 'Internal Server Error 500',
|
|
797
|
+
status: 500,
|
|
798
|
+
});
|
|
799
|
+
vi.mocked(mockContentGenerator.generateContentStream)
|
|
800
|
+
.mockRejectedValueOnce(error500)
|
|
801
|
+
.mockResolvedValueOnce((async function* () {
|
|
802
|
+
yield {
|
|
803
|
+
candidates: [
|
|
804
|
+
{
|
|
805
|
+
content: { parts: [{ text: 'Recovered from 500' }] },
|
|
806
|
+
finishReason: 'STOP',
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
};
|
|
810
|
+
})());
|
|
811
|
+
const stream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-500-retry');
|
|
812
|
+
const events = [];
|
|
813
|
+
for await (const event of stream) {
|
|
814
|
+
events.push(event);
|
|
815
|
+
}
|
|
816
|
+
// Should be called twice (initial + retry)
|
|
817
|
+
expect(mockContentGenerator.generateContentStream).toHaveBeenCalledTimes(2);
|
|
818
|
+
});
|
|
819
|
+
afterEach(() => {
|
|
820
|
+
// Reset to default behavior
|
|
821
|
+
mockRetryWithBackoff.mockImplementation(async (apiCall) => apiCall());
|
|
822
|
+
});
|
|
594
823
|
});
|
|
595
824
|
});
|
|
596
825
|
it('should correctly retry and append to an existing history mid-conversation', async () => {
|
|
@@ -762,215 +991,6 @@ describe('GeminiChat', () => {
|
|
|
762
991
|
}
|
|
763
992
|
expect(turn4.parts[0].text).toBe('second response');
|
|
764
993
|
});
|
|
765
|
-
describe('stopBeforeSecondMutator', () => {
|
|
766
|
-
beforeEach(() => {
|
|
767
|
-
// Common setup for these tests: mock the tool registry.
|
|
768
|
-
const mockToolRegistry = {
|
|
769
|
-
getTool: vi.fn((toolName) => {
|
|
770
|
-
if (toolName === 'edit') {
|
|
771
|
-
return { kind: Kind.Edit };
|
|
772
|
-
}
|
|
773
|
-
return { kind: Kind.Other };
|
|
774
|
-
}),
|
|
775
|
-
};
|
|
776
|
-
vi.mocked(mockConfig.getToolRegistry).mockReturnValue(mockToolRegistry);
|
|
777
|
-
});
|
|
778
|
-
it('should stop streaming before a second mutator tool call', async () => {
|
|
779
|
-
const responses = [
|
|
780
|
-
{
|
|
781
|
-
candidates: [
|
|
782
|
-
{ content: { role: 'model', parts: [{ text: 'First part. ' }] } },
|
|
783
|
-
],
|
|
784
|
-
},
|
|
785
|
-
{
|
|
786
|
-
candidates: [
|
|
787
|
-
{
|
|
788
|
-
content: {
|
|
789
|
-
role: 'model',
|
|
790
|
-
parts: [{ functionCall: { name: 'edit', args: {} } }],
|
|
791
|
-
},
|
|
792
|
-
},
|
|
793
|
-
],
|
|
794
|
-
},
|
|
795
|
-
{
|
|
796
|
-
candidates: [
|
|
797
|
-
{
|
|
798
|
-
content: {
|
|
799
|
-
role: 'model',
|
|
800
|
-
parts: [{ functionCall: { name: 'fetch', args: {} } }],
|
|
801
|
-
},
|
|
802
|
-
},
|
|
803
|
-
],
|
|
804
|
-
},
|
|
805
|
-
// This chunk contains the second mutator and should be clipped.
|
|
806
|
-
{
|
|
807
|
-
candidates: [
|
|
808
|
-
{
|
|
809
|
-
content: {
|
|
810
|
-
role: 'model',
|
|
811
|
-
parts: [
|
|
812
|
-
{ functionCall: { name: 'edit', args: {} } },
|
|
813
|
-
{ text: 'some trailing text' },
|
|
814
|
-
],
|
|
815
|
-
},
|
|
816
|
-
},
|
|
817
|
-
],
|
|
818
|
-
},
|
|
819
|
-
// This chunk should never be reached.
|
|
820
|
-
{
|
|
821
|
-
candidates: [
|
|
822
|
-
{
|
|
823
|
-
content: {
|
|
824
|
-
role: 'model',
|
|
825
|
-
parts: [{ text: 'This should not appear.' }],
|
|
826
|
-
},
|
|
827
|
-
},
|
|
828
|
-
],
|
|
829
|
-
},
|
|
830
|
-
];
|
|
831
|
-
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue((async function* () {
|
|
832
|
-
for (const response of responses) {
|
|
833
|
-
yield response;
|
|
834
|
-
}
|
|
835
|
-
})());
|
|
836
|
-
const stream = await chat.sendMessageStream('test-model', { message: 'test message' }, 'prompt-id-mutator-test');
|
|
837
|
-
for await (const _ of stream) {
|
|
838
|
-
// Consume the stream to trigger history recording.
|
|
839
|
-
}
|
|
840
|
-
const history = chat.getHistory();
|
|
841
|
-
expect(history.length).toBe(2);
|
|
842
|
-
const modelTurn = history[1];
|
|
843
|
-
expect(modelTurn.role).toBe('model');
|
|
844
|
-
expect(modelTurn?.parts?.length).toBe(3);
|
|
845
|
-
expect(modelTurn?.parts[0].text).toBe('First part. ');
|
|
846
|
-
expect(modelTurn.parts[1].functionCall?.name).toBe('edit');
|
|
847
|
-
expect(modelTurn.parts[2].functionCall?.name).toBe('fetch');
|
|
848
|
-
});
|
|
849
|
-
it('should not stop streaming if only one mutator is present', async () => {
|
|
850
|
-
const responses = [
|
|
851
|
-
{
|
|
852
|
-
candidates: [
|
|
853
|
-
{ content: { role: 'model', parts: [{ text: 'Part 1. ' }] } },
|
|
854
|
-
],
|
|
855
|
-
},
|
|
856
|
-
{
|
|
857
|
-
candidates: [
|
|
858
|
-
{
|
|
859
|
-
content: {
|
|
860
|
-
role: 'model',
|
|
861
|
-
parts: [{ functionCall: { name: 'edit', args: {} } }],
|
|
862
|
-
},
|
|
863
|
-
},
|
|
864
|
-
],
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
candidates: [
|
|
868
|
-
{
|
|
869
|
-
content: {
|
|
870
|
-
role: 'model',
|
|
871
|
-
parts: [{ text: 'Part 2.' }],
|
|
872
|
-
},
|
|
873
|
-
finishReason: 'STOP',
|
|
874
|
-
},
|
|
875
|
-
],
|
|
876
|
-
},
|
|
877
|
-
];
|
|
878
|
-
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue((async function* () {
|
|
879
|
-
for (const response of responses) {
|
|
880
|
-
yield response;
|
|
881
|
-
}
|
|
882
|
-
})());
|
|
883
|
-
const stream = await chat.sendMessageStream('test-model', { message: 'test message' }, 'prompt-id-one-mutator');
|
|
884
|
-
for await (const _ of stream) {
|
|
885
|
-
/* consume */
|
|
886
|
-
}
|
|
887
|
-
const history = chat.getHistory();
|
|
888
|
-
const modelTurn = history[1];
|
|
889
|
-
expect(modelTurn?.parts?.length).toBe(3);
|
|
890
|
-
expect(modelTurn.parts[1].functionCall?.name).toBe('edit');
|
|
891
|
-
expect(modelTurn.parts[2].text).toBe('Part 2.');
|
|
892
|
-
});
|
|
893
|
-
it('should clip the chunk containing the second mutator, preserving prior parts', async () => {
|
|
894
|
-
const responses = [
|
|
895
|
-
{
|
|
896
|
-
candidates: [
|
|
897
|
-
{
|
|
898
|
-
content: {
|
|
899
|
-
role: 'model',
|
|
900
|
-
parts: [{ functionCall: { name: 'edit', args: {} } }],
|
|
901
|
-
},
|
|
902
|
-
},
|
|
903
|
-
],
|
|
904
|
-
},
|
|
905
|
-
// This chunk has a valid part before the second mutator.
|
|
906
|
-
// The valid part should be kept, the rest of the chunk discarded.
|
|
907
|
-
{
|
|
908
|
-
candidates: [
|
|
909
|
-
{
|
|
910
|
-
content: {
|
|
911
|
-
role: 'model',
|
|
912
|
-
parts: [
|
|
913
|
-
{ text: 'Keep this text. ' },
|
|
914
|
-
{ functionCall: { name: 'edit', args: {} } },
|
|
915
|
-
{ text: 'Discard this text.' },
|
|
916
|
-
],
|
|
917
|
-
},
|
|
918
|
-
finishReason: 'STOP',
|
|
919
|
-
},
|
|
920
|
-
],
|
|
921
|
-
},
|
|
922
|
-
];
|
|
923
|
-
const stream = (async function* () {
|
|
924
|
-
for (const response of responses) {
|
|
925
|
-
yield response;
|
|
926
|
-
}
|
|
927
|
-
})();
|
|
928
|
-
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
|
|
929
|
-
const resultStream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-clip-chunk');
|
|
930
|
-
for await (const _ of resultStream) {
|
|
931
|
-
/* consume */
|
|
932
|
-
}
|
|
933
|
-
const history = chat.getHistory();
|
|
934
|
-
const modelTurn = history[1];
|
|
935
|
-
expect(modelTurn?.parts?.length).toBe(2);
|
|
936
|
-
expect(modelTurn.parts[0].functionCall?.name).toBe('edit');
|
|
937
|
-
expect(modelTurn.parts[1].text).toBe('Keep this text. ');
|
|
938
|
-
});
|
|
939
|
-
it('should handle two mutators in the same chunk (parallel call scenario)', async () => {
|
|
940
|
-
const responses = [
|
|
941
|
-
{
|
|
942
|
-
candidates: [
|
|
943
|
-
{
|
|
944
|
-
content: {
|
|
945
|
-
role: 'model',
|
|
946
|
-
parts: [
|
|
947
|
-
{ text: 'Some text. ' },
|
|
948
|
-
{ functionCall: { name: 'edit', args: {} } },
|
|
949
|
-
{ functionCall: { name: 'edit', args: {} } },
|
|
950
|
-
],
|
|
951
|
-
},
|
|
952
|
-
finishReason: 'STOP',
|
|
953
|
-
},
|
|
954
|
-
],
|
|
955
|
-
},
|
|
956
|
-
];
|
|
957
|
-
const stream = (async function* () {
|
|
958
|
-
for (const response of responses) {
|
|
959
|
-
yield response;
|
|
960
|
-
}
|
|
961
|
-
})();
|
|
962
|
-
vi.mocked(mockContentGenerator.generateContentStream).mockResolvedValue(stream);
|
|
963
|
-
const resultStream = await chat.sendMessageStream('test-model', { message: 'test' }, 'prompt-id-parallel-mutators');
|
|
964
|
-
for await (const _ of resultStream) {
|
|
965
|
-
/* consume */
|
|
966
|
-
}
|
|
967
|
-
const history = chat.getHistory();
|
|
968
|
-
const modelTurn = history[1];
|
|
969
|
-
expect(modelTurn?.parts?.length).toBe(2);
|
|
970
|
-
expect(modelTurn.parts[0].text).toBe('Some text. ');
|
|
971
|
-
expect(modelTurn.parts[1].functionCall?.name).toBe('edit');
|
|
972
|
-
});
|
|
973
|
-
});
|
|
974
994
|
describe('Model Resolution', () => {
|
|
975
995
|
const mockResponse = {
|
|
976
996
|
candidates: [
|
|
@@ -996,7 +1016,8 @@ describe('GeminiChat', () => {
|
|
|
996
1016
|
});
|
|
997
1017
|
});
|
|
998
1018
|
describe('Fallback Integration (Retries)', () => {
|
|
999
|
-
const error429 =
|
|
1019
|
+
const error429 = new ApiError({
|
|
1020
|
+
message: 'API Error 429: Quota exceeded',
|
|
1000
1021
|
status: 429,
|
|
1001
1022
|
});
|
|
1002
1023
|
// Define the simulated behavior for retryWithBackoff for these tests.
|