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