@machina.ai/cell-cli-core 1.8.2-rc1 → 1.11.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 +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/package.json +5 -3
- package/dist/src/agents/codebase-investigator.d.ts +36 -1
- package/dist/src/agents/codebase-investigator.js +93 -34
- package/dist/src/agents/codebase-investigator.js.map +1 -1
- package/dist/src/agents/executor.d.ts +15 -11
- package/dist/src/agents/executor.js +271 -115
- package/dist/src/agents/executor.js.map +1 -1
- package/dist/src/agents/executor.test.js +508 -242
- package/dist/src/agents/executor.test.js.map +1 -1
- package/dist/src/agents/invocation.d.ts +5 -2
- package/dist/src/agents/invocation.js +4 -2
- package/dist/src/agents/invocation.js.map +1 -1
- package/dist/src/agents/invocation.test.js +9 -0
- package/dist/src/agents/invocation.test.js.map +1 -1
- package/dist/src/agents/registry.d.ts +2 -1
- package/dist/src/agents/registry.js +28 -4
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/subagent-tool-wrapper.d.ts +3 -1
- package/dist/src/agents/subagent-tool-wrapper.js +4 -3
- package/dist/src/agents/subagent-tool-wrapper.js.map +1 -1
- package/dist/src/agents/subagent-tool-wrapper.test.js +8 -1
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
- package/dist/src/agents/types.d.ts +35 -6
- package/dist/src/agents/types.js +1 -0
- package/dist/src/agents/types.js.map +1 -1
- package/dist/src/code_assist/converter.d.ts +1 -0
- package/dist/src/code_assist/converter.js +1 -0
- package/dist/src/code_assist/converter.js.map +1 -1
- package/dist/src/code_assist/converter.test.js +19 -0
- package/dist/src/code_assist/converter.test.js.map +1 -1
- 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.js +13 -12
- package/dist/src/code_assist/oauth2.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 +58 -18
- package/dist/src/config/config.js +125 -36
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +125 -18
- package/dist/src/config/config.test.js.map +1 -1
- 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/confirmation-bus/message-bus.d.ts +2 -1
- package/dist/src/confirmation-bus/message-bus.js +7 -1
- package/dist/src/confirmation-bus/message-bus.js.map +1 -1
- package/dist/src/confirmation-bus/types.d.ts +12 -2
- package/dist/src/confirmation-bus/types.js +1 -0
- package/dist/src/confirmation-bus/types.js.map +1 -1
- package/dist/src/core/client.d.ts +3 -1
- package/dist/src/core/client.js +70 -19
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +199 -25
- 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.d.ts +7 -0
- package/dist/src/core/coreToolScheduler.js +58 -22
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +351 -3
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +7 -11
- package/dist/src/core/geminiChat.js +33 -70
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +93 -228
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/logger.js +11 -10
- package/dist/src/core/logger.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.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 +11 -8
- 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 +58 -123
- 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/turn.d.ts +16 -2
- package/dist/src/core/turn.js +15 -2
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +62 -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.test.js +11 -0
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.js +5 -4
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +4 -4
- 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/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 +6 -0
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/oauth-provider.js +21 -20
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-utils.js +9 -8
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/oauth-utils.test.js +13 -2
- package/dist/src/mcp/oauth-utils.test.js.map +1 -1
- 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/output/stream-json-formatter.d.ts +32 -0
- package/dist/src/output/stream-json-formatter.js +52 -0
- package/dist/src/output/stream-json-formatter.js.map +1 -0
- package/dist/src/output/stream-json-formatter.test.js +479 -0
- package/dist/src/output/stream-json-formatter.test.js.map +1 -0
- package/dist/src/output/types.d.ts +63 -1
- package/dist/src/output/types.js +11 -0
- package/dist/src/output/types.js.map +1 -1
- package/dist/src/prompts/prompt-registry.js +2 -1
- package/dist/src/prompts/prompt-registry.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.js +3 -2
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
- package/dist/src/services/chatRecordingService.d.ts +2 -1
- package/dist/src/services/chatRecordingService.js +2 -1
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/loopDetectionService.js +3 -2
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.d.ts +1 -0
- package/dist/src/services/shellExecutionService.js +177 -91
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +113 -12
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/telemetry/activity-monitor.d.ts +116 -0
- package/dist/src/telemetry/activity-monitor.js +209 -0
- package/dist/src/telemetry/activity-monitor.js.map +1 -0
- package/dist/src/telemetry/activity-monitor.test.d.ts +6 -0
- package/dist/src/telemetry/activity-monitor.test.js +248 -0
- package/dist/src/telemetry/activity-monitor.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +15 -2
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +154 -31
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js +84 -15
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +16 -3
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +38 -5
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js.map +1 -1
- package/dist/src/telemetry/constants.d.ts +0 -28
- package/dist/src/telemetry/constants.js +0 -29
- package/dist/src/telemetry/constants.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +6 -3
- package/dist/src/telemetry/index.js +12 -4
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +8 -2
- package/dist/src/telemetry/loggers.js +165 -299
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +213 -21
- 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 +131 -4
- package/dist/src/telemetry/metrics.js +182 -6
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +360 -1
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/telemetry/sdk.js +3 -2
- package/dist/src/telemetry/sdk.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 +163 -7
- package/dist/src/telemetry/types.js +691 -38
- 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 +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/tools/edit.js +8 -7
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +0 -1
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +3 -2
- package/dist/src/tools/glob.js +8 -7
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/grep.d.ts +3 -2
- package/dist/src/tools/grep.js +20 -14
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/ls.d.ts +3 -2
- package/dist/src/tools/ls.js +8 -7
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.d.ts +2 -9
- package/dist/src/tools/mcp-client-manager.js +6 -14
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.test.js +16 -6
- package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +3 -2
- package/dist/src/tools/mcp-client.js +49 -52
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +168 -5
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +2 -1
- package/dist/src/tools/mcp-tool.js +7 -3
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +2 -2
- package/dist/src/tools/memoryTool.js +4 -4
- 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.js +14 -1
- package/dist/src/tools/message-bus-integration.test.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +4 -3
- package/dist/src/tools/read-file.js +9 -8
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +4 -3
- package/dist/src/tools/read-many-files.js +11 -8
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +0 -1
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +3 -2
- package/dist/src/tools/ripGrep.js +47 -17
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +106 -60
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +1 -1
- package/dist/src/tools/shell.js +31 -14
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +63 -9
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +1 -20
- package/dist/src/tools/smart-edit.js +64 -61
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +70 -87
- package/dist/src/tools/smart-edit.test.js.map +1 -1
- package/dist/src/tools/tool-error.d.ts +21 -0
- package/dist/src/tools/tool-error.js +27 -0
- package/dist/src/tools/tool-error.js.map +1 -1
- package/dist/src/tools/tool-names.d.ts +17 -0
- package/dist/src/tools/tool-names.js +21 -0
- package/dist/src/tools/tool-names.js.map +1 -0
- package/dist/src/tools/tool-registry.js +4 -3
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tools.d.ts +17 -10
- package/dist/src/tools/tools.js +54 -39
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/web-fetch.d.ts +11 -3
- package/dist/src/tools/web-fetch.js +90 -32
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +388 -8
- 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 +10 -7
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/write-file.d.ts +1 -1
- package/dist/src/tools/write-file.js +3 -2
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +0 -1
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +3 -8
- package/dist/src/tools/write-todos.js +14 -7
- package/dist/src/tools/write-todos.js.map +1 -1
- package/dist/src/tools/write-todos.test.js +2 -2
- package/dist/src/tools/write-todos.test.js.map +1 -1
- package/dist/src/utils/bfsFileSearch.js +3 -2
- package/dist/src/utils/bfsFileSearch.js.map +1 -1
- package/dist/src/utils/debugLogger.d.ts +25 -0
- package/dist/src/utils/debugLogger.js +33 -0
- package/dist/src/utils/debugLogger.js.map +1 -0
- package/dist/src/utils/debugLogger.test.d.ts +6 -0
- package/dist/src/utils/debugLogger.test.js +67 -0
- package/dist/src/utils/debugLogger.test.js.map +1 -0
- package/dist/src/utils/delay.d.ts +16 -0
- package/dist/src/utils/delay.js +43 -0
- package/dist/src/utils/delay.js.map +1 -0
- package/dist/src/utils/delay.test.d.ts +6 -0
- package/dist/src/utils/delay.test.js +88 -0
- package/dist/src/utils/delay.test.js.map +1 -0
- package/dist/src/utils/editCorrector.js +6 -10
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +3 -5
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/editor.js +33 -37
- 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/environmentContext.js +0 -33
- package/dist/src/utils/environmentContext.js.map +1 -1
- package/dist/src/utils/environmentContext.test.js +0 -34
- package/dist/src/utils/environmentContext.test.js.map +1 -1
- package/dist/src/utils/fetch.d.ts +1 -0
- package/dist/src/utils/fetch.js +9 -0
- package/dist/src/utils/fetch.js.map +1 -1
- package/dist/src/utils/fileUtils.d.ts +4 -0
- package/dist/src/utils/fileUtils.js +34 -2
- package/dist/src/utils/fileUtils.js.map +1 -1
- package/dist/src/utils/fileUtils.test.js +12 -1
- package/dist/src/utils/fileUtils.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.js +2 -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/gitIgnoreParser.js +7 -6
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +30 -0
- package/dist/src/utils/gitIgnoreParser.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 +5 -4
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.d.ts +3 -1
- package/dist/src/utils/memoryDiscovery.js +14 -12
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +136 -36
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +3 -2
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.js +2 -1
- package/dist/src/utils/nextSpeakerChecker.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 +3 -1
- package/dist/src/utils/retry.js +44 -33
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +102 -40
- package/dist/src/utils/retry.test.js.map +1 -1
- package/dist/src/utils/safeJsonStringify.d.ts +4 -4
- package/dist/src/utils/safeJsonStringify.js +31 -7
- package/dist/src/utils/safeJsonStringify.js.map +1 -1
- package/dist/src/utils/shell-utils.d.ts +15 -2
- package/dist/src/utils/shell-utils.js +354 -137
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +154 -60
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/systemEncoding.js +5 -4
- package/dist/src/utils/systemEncoding.js.map +1 -1
- package/dist/src/utils/tool-utils.d.ts +2 -2
- package/dist/src/utils/tool-utils.js +15 -6
- 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.js +5 -4
- package/dist/src/utils/userAccountManager.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/src/utils/workspaceContext.js +2 -1
- package/dist/src/utils/workspaceContext.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +5 -3
- package/dist/src/core/subagent.d.ts +0 -236
- package/dist/src/core/subagent.js +0 -482
- package/dist/src/core/subagent.js.map +0 -1
- package/dist/src/core/subagent.test.js +0 -530
- package/dist/src/core/subagent.test.js.map +0 -1
- /package/dist/src/{core/subagent.test.d.ts → output/stream-json-formatter.test.d.ts} +0 -0
|
@@ -3,16 +3,21 @@
|
|
|
3
3
|
* Copyright 2025 Google LLC
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
|
-
import { describe, it, expect, vi, beforeEach, afterEach
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
7
7
|
import { AgentExecutor } from './executor.js';
|
|
8
|
-
import { AgentTerminateMode } from './types.js';
|
|
9
8
|
import { makeFakeConfig } from '../test-utils/config.js';
|
|
10
9
|
import { ToolRegistry } from '../tools/tool-registry.js';
|
|
11
10
|
import { LSTool } from '../tools/ls.js';
|
|
12
|
-
import {
|
|
11
|
+
import { LS_TOOL_NAME, READ_FILE_TOOL_NAME } from '../tools/tool-names.js';
|
|
13
12
|
import { GeminiChat, StreamEventType, } from '../core/geminiChat.js';
|
|
13
|
+
import {} from '@google/genai';
|
|
14
14
|
import { MockTool } from '../test-utils/mock-tool.js';
|
|
15
15
|
import { getDirectoryContextString } from '../utils/environmentContext.js';
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { promptIdContext } from '../utils/promptIdContext.js';
|
|
18
|
+
import { logAgentStart, logAgentFinish } from '../telemetry/loggers.js';
|
|
19
|
+
import { AgentStartEvent, AgentFinishEvent } from '../telemetry/types.js';
|
|
20
|
+
import { AgentTerminateMode } from './types.js';
|
|
16
21
|
const { mockSendMessageStream, mockExecuteToolCall } = vi.hoisted(() => ({
|
|
17
22
|
mockSendMessageStream: vi.fn(),
|
|
18
23
|
mockExecuteToolCall: vi.fn(),
|
|
@@ -30,13 +35,40 @@ vi.mock('../core/nonInteractiveToolExecutor.js', () => ({
|
|
|
30
35
|
executeToolCall: mockExecuteToolCall,
|
|
31
36
|
}));
|
|
32
37
|
vi.mock('../utils/environmentContext.js');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
38
|
+
vi.mock('../telemetry/loggers.js', () => ({
|
|
39
|
+
logAgentStart: vi.fn(),
|
|
40
|
+
logAgentFinish: vi.fn(),
|
|
41
|
+
}));
|
|
42
|
+
vi.mock('../utils/promptIdContext.js', async (importOriginal) => {
|
|
43
|
+
const actual = await importOriginal();
|
|
44
|
+
return {
|
|
45
|
+
...actual,
|
|
46
|
+
promptIdContext: {
|
|
47
|
+
...actual.promptIdContext,
|
|
48
|
+
getStore: vi.fn(),
|
|
49
|
+
run: vi.fn((_id, fn) => fn()),
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
const MockedGeminiChat = vi.mocked(GeminiChat);
|
|
54
|
+
const mockedGetDirectoryContextString = vi.mocked(getDirectoryContextString);
|
|
55
|
+
const mockedPromptIdContext = vi.mocked(promptIdContext);
|
|
56
|
+
const mockedLogAgentStart = vi.mocked(logAgentStart);
|
|
57
|
+
const mockedLogAgentFinish = vi.mocked(logAgentFinish);
|
|
58
|
+
// Constants for testing
|
|
59
|
+
const TASK_COMPLETE_TOOL_NAME = 'complete_task';
|
|
60
|
+
const MOCK_TOOL_NOT_ALLOWED = new MockTool({ name: 'write_file_interactive' });
|
|
61
|
+
/**
|
|
62
|
+
* Helper to create a mock API response chunk.
|
|
63
|
+
* Uses conditional spread to handle readonly functionCalls property safely.
|
|
64
|
+
*/
|
|
36
65
|
const createMockResponseChunk = (parts, functionCalls) => ({
|
|
37
66
|
candidates: [{ index: 0, content: { role: 'model', parts } }],
|
|
38
|
-
functionCalls,
|
|
67
|
+
...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}),
|
|
39
68
|
});
|
|
69
|
+
/**
|
|
70
|
+
* Helper to mock a single turn of model response in the stream.
|
|
71
|
+
*/
|
|
40
72
|
const mockModelResponse = (functionCalls, thought, text) => {
|
|
41
73
|
const parts = [];
|
|
42
74
|
if (thought) {
|
|
@@ -47,9 +79,7 @@ const mockModelResponse = (functionCalls, thought, text) => {
|
|
|
47
79
|
}
|
|
48
80
|
if (text)
|
|
49
81
|
parts.push({ text });
|
|
50
|
-
const responseChunk = createMockResponseChunk(parts,
|
|
51
|
-
// Ensure functionCalls is undefined if the array is empty, matching API behavior
|
|
52
|
-
functionCalls.length > 0 ? functionCalls : undefined);
|
|
82
|
+
const responseChunk = createMockResponseChunk(parts, functionCalls);
|
|
53
83
|
mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
|
|
54
84
|
yield {
|
|
55
85
|
type: StreamEventType.CHUNK,
|
|
@@ -57,38 +87,67 @@ const mockModelResponse = (functionCalls, thought, text) => {
|
|
|
57
87
|
};
|
|
58
88
|
})());
|
|
59
89
|
};
|
|
90
|
+
/**
|
|
91
|
+
* Helper to extract the message parameters sent to sendMessageStream.
|
|
92
|
+
* Provides type safety for inspecting mock calls.
|
|
93
|
+
*/
|
|
94
|
+
const getMockMessageParams = (callIndex) => {
|
|
95
|
+
const call = mockSendMessageStream.mock.calls[callIndex];
|
|
96
|
+
expect(call).toBeDefined();
|
|
97
|
+
// Arg 1 of sendMessageStream is the message parameters
|
|
98
|
+
return call[1];
|
|
99
|
+
};
|
|
60
100
|
let mockConfig;
|
|
61
101
|
let parentToolRegistry;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
102
|
+
/**
|
|
103
|
+
* Type-safe helper to create agent definitions for tests.
|
|
104
|
+
*/
|
|
105
|
+
const createTestDefinition = (tools = [LS_TOOL_NAME], runConfigOverrides = {}, outputConfigMode = 'default', schema = z.string()) => {
|
|
106
|
+
let outputConfig;
|
|
107
|
+
if (outputConfigMode === 'default') {
|
|
108
|
+
outputConfig = {
|
|
109
|
+
outputName: 'finalResult',
|
|
110
|
+
description: 'The final result.',
|
|
111
|
+
schema,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
name: 'TestAgent',
|
|
116
|
+
description: 'An agent for testing.',
|
|
117
|
+
inputConfig: {
|
|
118
|
+
inputs: { goal: { type: 'string', required: true, description: 'goal' } },
|
|
119
|
+
},
|
|
120
|
+
modelConfig: { model: 'gemini-test-model', temp: 0, top_p: 1 },
|
|
121
|
+
runConfig: { max_time_minutes: 5, max_turns: 5, ...runConfigOverrides },
|
|
122
|
+
promptConfig: { systemPrompt: 'Achieve the goal: ${goal}.' },
|
|
123
|
+
toolConfig: { tools },
|
|
124
|
+
outputConfig,
|
|
125
|
+
};
|
|
126
|
+
};
|
|
74
127
|
describe('AgentExecutor', () => {
|
|
75
128
|
let activities;
|
|
76
129
|
let onActivity;
|
|
77
130
|
let abortController;
|
|
78
131
|
let signal;
|
|
79
132
|
beforeEach(async () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
133
|
+
vi.resetAllMocks();
|
|
134
|
+
mockSendMessageStream.mockReset();
|
|
135
|
+
mockExecuteToolCall.mockReset();
|
|
136
|
+
mockedLogAgentStart.mockReset();
|
|
137
|
+
mockedLogAgentFinish.mockReset();
|
|
138
|
+
mockedPromptIdContext.getStore.mockReset();
|
|
139
|
+
mockedPromptIdContext.run.mockImplementation((_id, fn) => fn());
|
|
140
|
+
MockedGeminiChat.mockImplementation(() => ({
|
|
141
|
+
sendMessageStream: mockSendMessageStream,
|
|
142
|
+
}));
|
|
84
143
|
vi.useFakeTimers();
|
|
85
144
|
mockConfig = makeFakeConfig();
|
|
86
145
|
parentToolRegistry = new ToolRegistry(mockConfig);
|
|
87
146
|
parentToolRegistry.registerTool(new LSTool(mockConfig));
|
|
88
|
-
parentToolRegistry.registerTool(new
|
|
147
|
+
parentToolRegistry.registerTool(new MockTool({ name: READ_FILE_TOOL_NAME }));
|
|
89
148
|
parentToolRegistry.registerTool(MOCK_TOOL_NOT_ALLOWED);
|
|
90
149
|
vi.spyOn(mockConfig, 'getToolRegistry').mockResolvedValue(parentToolRegistry);
|
|
91
|
-
|
|
150
|
+
mockedGetDirectoryContextString.mockResolvedValue('Mocked Environment Context');
|
|
92
151
|
activities = [];
|
|
93
152
|
onActivity = (activity) => activities.push(activity);
|
|
94
153
|
abortController = new AbortController();
|
|
@@ -99,319 +158,526 @@ describe('AgentExecutor', () => {
|
|
|
99
158
|
});
|
|
100
159
|
describe('create (Initialization and Validation)', () => {
|
|
101
160
|
it('should create successfully with allowed tools', async () => {
|
|
102
|
-
const definition = createTestDefinition([
|
|
161
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
103
162
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
104
163
|
expect(executor).toBeInstanceOf(AgentExecutor);
|
|
105
164
|
});
|
|
106
165
|
it('SECURITY: should throw if a tool is not on the non-interactive allowlist', async () => {
|
|
107
166
|
const definition = createTestDefinition([MOCK_TOOL_NOT_ALLOWED.name]);
|
|
108
|
-
await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(
|
|
167
|
+
await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(/not on the allow-list for non-interactive execution/);
|
|
109
168
|
});
|
|
110
169
|
it('should create an isolated ToolRegistry for the agent', async () => {
|
|
111
|
-
const definition = createTestDefinition([
|
|
170
|
+
const definition = createTestDefinition([
|
|
171
|
+
LS_TOOL_NAME,
|
|
172
|
+
READ_FILE_TOOL_NAME,
|
|
173
|
+
]);
|
|
112
174
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
113
|
-
|
|
114
|
-
const agentRegistry = executor.toolRegistry;
|
|
175
|
+
const agentRegistry = executor['toolRegistry'];
|
|
115
176
|
expect(agentRegistry).not.toBe(parentToolRegistry);
|
|
116
|
-
expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([
|
|
177
|
+
expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([LS_TOOL_NAME, READ_FILE_TOOL_NAME]));
|
|
117
178
|
expect(agentRegistry.getAllToolNames()).toHaveLength(2);
|
|
118
179
|
expect(agentRegistry.getTool(MOCK_TOOL_NOT_ALLOWED.name)).toBeUndefined();
|
|
119
180
|
});
|
|
181
|
+
it('should use parentPromptId from context to create agentId', async () => {
|
|
182
|
+
const parentId = 'parent-id';
|
|
183
|
+
mockedPromptIdContext.getStore.mockReturnValue(parentId);
|
|
184
|
+
const definition = createTestDefinition();
|
|
185
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
186
|
+
expect(executor['agentId']).toMatch(new RegExp(`^${parentId}-${definition.name}-`));
|
|
187
|
+
});
|
|
120
188
|
});
|
|
121
189
|
describe('run (Execution Loop and Logic)', () => {
|
|
122
|
-
it('should
|
|
190
|
+
it('should log AgentFinish with error if run throws', async () => {
|
|
191
|
+
const definition = createTestDefinition();
|
|
192
|
+
// Make the definition invalid to cause an error during run
|
|
193
|
+
definition.inputConfig.inputs = {
|
|
194
|
+
goal: { type: 'string', required: true, description: 'goal' },
|
|
195
|
+
};
|
|
196
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
197
|
+
// Run without inputs to trigger validation error
|
|
198
|
+
await expect(executor.run({}, signal)).rejects.toThrow(/Missing required input parameters/);
|
|
199
|
+
expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
|
|
200
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
|
|
201
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
|
|
202
|
+
terminate_reason: AgentTerminateMode.ERROR,
|
|
203
|
+
}));
|
|
204
|
+
});
|
|
205
|
+
it('should execute successfully when model calls complete_task with output (Happy Path with Output)', async () => {
|
|
123
206
|
const definition = createTestDefinition();
|
|
124
207
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
125
208
|
const inputs = { goal: 'Find files' };
|
|
126
209
|
// Turn 1: Model calls ls
|
|
127
|
-
mockModelResponse([{ name:
|
|
210
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' }], 'T1: Listing');
|
|
128
211
|
mockExecuteToolCall.mockResolvedValueOnce({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
212
|
+
status: 'success',
|
|
213
|
+
request: {
|
|
214
|
+
callId: 'call1',
|
|
215
|
+
name: LS_TOOL_NAME,
|
|
216
|
+
args: { path: '.' },
|
|
217
|
+
isClientInitiated: false,
|
|
218
|
+
prompt_id: 'test-prompt',
|
|
219
|
+
},
|
|
220
|
+
tool: {},
|
|
221
|
+
invocation: {},
|
|
222
|
+
response: {
|
|
223
|
+
callId: 'call1',
|
|
224
|
+
resultDisplay: 'file1.txt',
|
|
225
|
+
responseParts: [
|
|
226
|
+
{
|
|
227
|
+
functionResponse: {
|
|
228
|
+
name: LS_TOOL_NAME,
|
|
229
|
+
response: { result: 'file1.txt' },
|
|
230
|
+
id: 'call1',
|
|
231
|
+
},
|
|
137
232
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
233
|
+
],
|
|
234
|
+
error: undefined,
|
|
235
|
+
errorType: undefined,
|
|
236
|
+
contentLength: undefined,
|
|
237
|
+
},
|
|
141
238
|
});
|
|
142
|
-
// Turn 2: Model
|
|
143
|
-
mockModelResponse([
|
|
144
|
-
|
|
145
|
-
|
|
239
|
+
// Turn 2: Model calls complete_task with required output
|
|
240
|
+
mockModelResponse([
|
|
241
|
+
{
|
|
242
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
243
|
+
args: { finalResult: 'Found file1.txt' },
|
|
244
|
+
id: 'call2',
|
|
245
|
+
},
|
|
246
|
+
], 'T2: Done');
|
|
146
247
|
const output = await executor.run(inputs, signal);
|
|
147
|
-
expect(mockSendMessageStream).toHaveBeenCalledTimes(
|
|
148
|
-
expect(mockExecuteToolCall).toHaveBeenCalledTimes(1);
|
|
149
|
-
// Verify System Prompt Templating
|
|
248
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
150
249
|
const chatConstructorArgs = MockedGeminiChat.mock.calls[0];
|
|
151
250
|
const chatConfig = chatConstructorArgs[1];
|
|
152
|
-
expect(chatConfig?.systemInstruction).toContain(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
expect(
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}), expect.stringContaining('#extraction'));
|
|
169
|
-
expect(output.result).toBe('Result: file1.txt.');
|
|
251
|
+
expect(chatConfig?.systemInstruction).toContain(`MUST call the \`${TASK_COMPLETE_TOOL_NAME}\` tool`);
|
|
252
|
+
const turn1Params = getMockMessageParams(0);
|
|
253
|
+
const firstToolGroup = turn1Params.config?.tools?.[0];
|
|
254
|
+
expect(firstToolGroup).toBeDefined();
|
|
255
|
+
if (!firstToolGroup || !('functionDeclarations' in firstToolGroup)) {
|
|
256
|
+
throw new Error('Test expectation failed: Config does not contain functionDeclarations.');
|
|
257
|
+
}
|
|
258
|
+
const sentTools = firstToolGroup.functionDeclarations;
|
|
259
|
+
expect(sentTools).toBeDefined();
|
|
260
|
+
expect(sentTools).toEqual(expect.arrayContaining([
|
|
261
|
+
expect.objectContaining({ name: LS_TOOL_NAME }),
|
|
262
|
+
expect.objectContaining({ name: TASK_COMPLETE_TOOL_NAME }),
|
|
263
|
+
]));
|
|
264
|
+
const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
|
|
265
|
+
expect(completeToolDef?.parameters?.required).toContain('finalResult');
|
|
266
|
+
expect(output.result).toBe('Found file1.txt');
|
|
170
267
|
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
171
|
-
//
|
|
268
|
+
// Telemetry checks
|
|
269
|
+
expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
|
|
270
|
+
expect(mockedLogAgentStart).toHaveBeenCalledWith(mockConfig, expect.any(AgentStartEvent));
|
|
271
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
|
|
272
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.any(AgentFinishEvent));
|
|
273
|
+
const finishEvent = mockedLogAgentFinish.mock.calls[0][1];
|
|
274
|
+
expect(finishEvent.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
275
|
+
// Context checks
|
|
276
|
+
expect(mockedPromptIdContext.run).toHaveBeenCalledTimes(2); // Two turns
|
|
277
|
+
const agentId = executor['agentId'];
|
|
278
|
+
expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(1, `${agentId}#0`, expect.any(Function));
|
|
279
|
+
expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(2, `${agentId}#1`, expect.any(Function));
|
|
172
280
|
expect(activities).toEqual(expect.arrayContaining([
|
|
173
|
-
// Thought subjects are extracted by the executor (parseThought)
|
|
174
281
|
expect.objectContaining({
|
|
175
282
|
type: 'THOUGHT_CHUNK',
|
|
176
283
|
data: { text: 'T1: Listing' },
|
|
177
284
|
}),
|
|
178
285
|
expect.objectContaining({
|
|
179
|
-
type: '
|
|
180
|
-
data: { name:
|
|
286
|
+
type: 'TOOL_CALL_END',
|
|
287
|
+
data: { name: LS_TOOL_NAME, output: 'file1.txt' },
|
|
181
288
|
}),
|
|
182
289
|
expect.objectContaining({
|
|
183
|
-
type: '
|
|
184
|
-
data: {
|
|
290
|
+
type: 'TOOL_CALL_START',
|
|
291
|
+
data: {
|
|
292
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
293
|
+
args: { finalResult: 'Found file1.txt' },
|
|
294
|
+
},
|
|
185
295
|
}),
|
|
186
296
|
expect.objectContaining({
|
|
187
|
-
type: '
|
|
188
|
-
data: {
|
|
297
|
+
type: 'TOOL_CALL_END',
|
|
298
|
+
data: {
|
|
299
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
300
|
+
output: expect.stringContaining('Output submitted'),
|
|
301
|
+
},
|
|
189
302
|
}),
|
|
190
303
|
]));
|
|
191
304
|
});
|
|
192
|
-
it('should execute
|
|
193
|
-
const definition = createTestDefinition([
|
|
305
|
+
it('should execute successfully when model calls complete_task without output (Happy Path No Output)', async () => {
|
|
306
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {}, 'none');
|
|
194
307
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
195
|
-
|
|
196
|
-
name:
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
activeCalls++;
|
|
213
|
-
maxActiveCalls = Math.max(maxActiveCalls, activeCalls);
|
|
214
|
-
// Simulate latency. We must advance the fake timers for this to resolve.
|
|
215
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
216
|
-
activeCalls--;
|
|
217
|
-
return {
|
|
218
|
-
callId: reqInfo.callId,
|
|
219
|
-
resultDisplay: `Result for ${reqInfo.name}`,
|
|
308
|
+
mockModelResponse([
|
|
309
|
+
{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
|
|
310
|
+
]);
|
|
311
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
312
|
+
status: 'success',
|
|
313
|
+
request: {
|
|
314
|
+
callId: 'call1',
|
|
315
|
+
name: LS_TOOL_NAME,
|
|
316
|
+
args: { path: '.' },
|
|
317
|
+
isClientInitiated: false,
|
|
318
|
+
prompt_id: 'test-prompt',
|
|
319
|
+
},
|
|
320
|
+
tool: {},
|
|
321
|
+
invocation: {},
|
|
322
|
+
response: {
|
|
323
|
+
callId: 'call1',
|
|
324
|
+
resultDisplay: 'ok',
|
|
220
325
|
responseParts: [
|
|
221
326
|
{
|
|
222
327
|
functionResponse: {
|
|
223
|
-
name:
|
|
328
|
+
name: LS_TOOL_NAME,
|
|
224
329
|
response: {},
|
|
225
|
-
id:
|
|
330
|
+
id: 'call1',
|
|
226
331
|
},
|
|
227
332
|
},
|
|
228
333
|
],
|
|
229
334
|
error: undefined,
|
|
230
|
-
|
|
335
|
+
errorType: undefined,
|
|
336
|
+
contentLength: undefined,
|
|
337
|
+
},
|
|
231
338
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
expect(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
expect(turn2Parts.length).toBe(2);
|
|
248
|
-
expect(turn2Parts[0]).toEqual(expect.objectContaining({
|
|
249
|
-
functionResponse: expect.objectContaining({ id: 'call1' }),
|
|
250
|
-
}));
|
|
251
|
-
expect(turn2Parts[1]).toEqual(expect.objectContaining({
|
|
252
|
-
functionResponse: expect.objectContaining({ id: 'call2' }),
|
|
253
|
-
}));
|
|
339
|
+
mockModelResponse([{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' }], 'Task finished.');
|
|
340
|
+
const output = await executor.run({ goal: 'Do work' }, signal);
|
|
341
|
+
const turn1Params = getMockMessageParams(0);
|
|
342
|
+
const firstToolGroup = turn1Params.config?.tools?.[0];
|
|
343
|
+
expect(firstToolGroup).toBeDefined();
|
|
344
|
+
if (!firstToolGroup || !('functionDeclarations' in firstToolGroup)) {
|
|
345
|
+
throw new Error('Test expectation failed: Config does not contain functionDeclarations.');
|
|
346
|
+
}
|
|
347
|
+
const sentTools = firstToolGroup.functionDeclarations;
|
|
348
|
+
expect(sentTools).toBeDefined();
|
|
349
|
+
const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
|
|
350
|
+
expect(completeToolDef?.parameters?.required).toEqual([]);
|
|
351
|
+
expect(completeToolDef?.description).toContain('signal that you have completed');
|
|
352
|
+
expect(output.result).toBe('Task completed successfully.');
|
|
353
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
254
354
|
});
|
|
255
|
-
it('should
|
|
256
|
-
const definition = createTestDefinition(
|
|
355
|
+
it('should error immediately if the model stops tools without calling complete_task (Protocol Violation)', async () => {
|
|
356
|
+
const definition = createTestDefinition();
|
|
257
357
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
258
|
-
// Turn 1: Model calls ls, but it fails
|
|
259
358
|
mockModelResponse([
|
|
260
|
-
{ name:
|
|
359
|
+
{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
|
|
261
360
|
]);
|
|
262
|
-
const errorMessage = 'Internal failure.';
|
|
263
361
|
mockExecuteToolCall.mockResolvedValueOnce({
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
362
|
+
status: 'success',
|
|
363
|
+
request: {
|
|
364
|
+
callId: 'call1',
|
|
365
|
+
name: LS_TOOL_NAME,
|
|
366
|
+
args: { path: '.' },
|
|
367
|
+
isClientInitiated: false,
|
|
368
|
+
prompt_id: 'test-prompt',
|
|
369
|
+
},
|
|
370
|
+
tool: {},
|
|
371
|
+
invocation: {},
|
|
372
|
+
response: {
|
|
373
|
+
callId: 'call1',
|
|
374
|
+
resultDisplay: 'ok',
|
|
375
|
+
responseParts: [
|
|
376
|
+
{
|
|
377
|
+
functionResponse: {
|
|
378
|
+
name: LS_TOOL_NAME,
|
|
379
|
+
response: {},
|
|
380
|
+
id: 'call1',
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
error: undefined,
|
|
385
|
+
errorType: undefined,
|
|
386
|
+
contentLength: undefined,
|
|
387
|
+
},
|
|
268
388
|
});
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
389
|
+
mockModelResponse([], 'I think I am done.');
|
|
390
|
+
const output = await executor.run({ goal: 'Strict test' }, signal);
|
|
391
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
392
|
+
const expectedError = `Agent stopped calling tools but did not call '${TASK_COMPLETE_TOOL_NAME}' to finalize the session.`;
|
|
393
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.ERROR);
|
|
394
|
+
expect(output.result).toBe(expectedError);
|
|
395
|
+
// Telemetry check for error
|
|
396
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
|
|
397
|
+
terminate_reason: AgentTerminateMode.ERROR,
|
|
398
|
+
}));
|
|
399
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
400
|
+
type: 'ERROR',
|
|
401
|
+
data: expect.objectContaining({
|
|
402
|
+
context: 'protocol_violation',
|
|
403
|
+
error: expectedError,
|
|
404
|
+
}),
|
|
405
|
+
}));
|
|
406
|
+
});
|
|
407
|
+
it('should report an error if complete_task is called with missing required arguments', async () => {
|
|
408
|
+
const definition = createTestDefinition();
|
|
409
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
410
|
+
// Turn 1: Missing arg
|
|
411
|
+
mockModelResponse([
|
|
412
|
+
{
|
|
413
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
414
|
+
args: { wrongArg: 'oops' },
|
|
415
|
+
id: 'call1',
|
|
416
|
+
},
|
|
417
|
+
]);
|
|
418
|
+
// Turn 2: Corrected
|
|
419
|
+
mockModelResponse([
|
|
420
|
+
{
|
|
421
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
422
|
+
args: { finalResult: 'Corrected result' },
|
|
423
|
+
id: 'call2',
|
|
424
|
+
},
|
|
425
|
+
]);
|
|
426
|
+
const output = await executor.run({ goal: 'Error test' }, signal);
|
|
427
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
428
|
+
const expectedError = "Missing required argument 'finalResult' for completion.";
|
|
274
429
|
expect(activities).toContainEqual(expect.objectContaining({
|
|
275
430
|
type: 'ERROR',
|
|
276
431
|
data: {
|
|
277
|
-
error: errorMessage,
|
|
278
432
|
context: 'tool_call',
|
|
279
|
-
name:
|
|
433
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
434
|
+
error: expectedError,
|
|
280
435
|
},
|
|
281
436
|
}));
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
expect(turn2Parts).
|
|
437
|
+
const turn2Params = getMockMessageParams(1);
|
|
438
|
+
const turn2Parts = turn2Params.message;
|
|
439
|
+
expect(turn2Parts).toBeDefined();
|
|
440
|
+
expect(turn2Parts).toHaveLength(1);
|
|
441
|
+
expect(turn2Parts[0]).toEqual(expect.objectContaining({
|
|
442
|
+
functionResponse: expect.objectContaining({
|
|
443
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
444
|
+
response: { error: expectedError },
|
|
445
|
+
id: 'call1',
|
|
446
|
+
}),
|
|
447
|
+
}));
|
|
448
|
+
expect(output.result).toBe('Corrected result');
|
|
449
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
450
|
+
});
|
|
451
|
+
it('should handle multiple calls to complete_task in the same turn (accept first, block rest)', async () => {
|
|
452
|
+
const definition = createTestDefinition([], {}, 'none');
|
|
453
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
454
|
+
// Turn 1: Duplicate calls
|
|
455
|
+
mockModelResponse([
|
|
456
|
+
{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call1' },
|
|
457
|
+
{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' },
|
|
458
|
+
]);
|
|
459
|
+
const output = await executor.run({ goal: 'Dup test' }, signal);
|
|
460
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
|
|
461
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
462
|
+
const completions = activities.filter((a) => a.type === 'TOOL_CALL_END' &&
|
|
463
|
+
a.data['name'] === TASK_COMPLETE_TOOL_NAME);
|
|
464
|
+
const errors = activities.filter((a) => a.type === 'ERROR' && a.data['name'] === TASK_COMPLETE_TOOL_NAME);
|
|
465
|
+
expect(completions).toHaveLength(1);
|
|
466
|
+
expect(errors).toHaveLength(1);
|
|
467
|
+
expect(errors[0].data['error']).toContain('Task already marked complete in this turn');
|
|
468
|
+
});
|
|
469
|
+
it('should execute parallel tool calls and then complete', async () => {
|
|
470
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
471
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
472
|
+
const call1 = {
|
|
473
|
+
name: LS_TOOL_NAME,
|
|
474
|
+
args: { path: '/a' },
|
|
475
|
+
id: 'c1',
|
|
476
|
+
};
|
|
477
|
+
const call2 = {
|
|
478
|
+
name: LS_TOOL_NAME,
|
|
479
|
+
args: { path: '/b' },
|
|
480
|
+
id: 'c2',
|
|
481
|
+
};
|
|
482
|
+
// Turn 1: Parallel calls
|
|
483
|
+
mockModelResponse([call1, call2]);
|
|
484
|
+
// Concurrency mock
|
|
485
|
+
let callsStarted = 0;
|
|
486
|
+
let resolveCalls;
|
|
487
|
+
const bothStarted = new Promise((r) => {
|
|
488
|
+
resolveCalls = r;
|
|
489
|
+
});
|
|
490
|
+
mockExecuteToolCall.mockImplementation(async (_ctx, reqInfo) => {
|
|
491
|
+
callsStarted++;
|
|
492
|
+
if (callsStarted === 2)
|
|
493
|
+
resolveCalls();
|
|
494
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
495
|
+
return {
|
|
496
|
+
status: 'success',
|
|
497
|
+
request: reqInfo,
|
|
498
|
+
tool: {},
|
|
499
|
+
invocation: {},
|
|
500
|
+
response: {
|
|
501
|
+
callId: reqInfo.callId,
|
|
502
|
+
resultDisplay: 'ok',
|
|
503
|
+
responseParts: [
|
|
504
|
+
{
|
|
505
|
+
functionResponse: {
|
|
506
|
+
name: reqInfo.name,
|
|
507
|
+
response: {},
|
|
508
|
+
id: reqInfo.callId,
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
error: undefined,
|
|
513
|
+
errorType: undefined,
|
|
514
|
+
contentLength: undefined,
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
});
|
|
518
|
+
// Turn 2: Completion
|
|
519
|
+
mockModelResponse([
|
|
286
520
|
{
|
|
287
|
-
|
|
521
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
522
|
+
args: { finalResult: 'done' },
|
|
523
|
+
id: 'c3',
|
|
288
524
|
},
|
|
289
525
|
]);
|
|
526
|
+
const runPromise = executor.run({ goal: 'Parallel' }, signal);
|
|
527
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
528
|
+
await bothStarted;
|
|
529
|
+
await vi.advanceTimersByTimeAsync(150);
|
|
530
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
531
|
+
const output = await runPromise;
|
|
532
|
+
expect(mockExecuteToolCall).toHaveBeenCalledTimes(2);
|
|
533
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
534
|
+
// Safe access to message parts
|
|
535
|
+
const turn2Params = getMockMessageParams(1);
|
|
536
|
+
const parts = turn2Params.message;
|
|
537
|
+
expect(parts).toBeDefined();
|
|
538
|
+
expect(parts).toHaveLength(2);
|
|
539
|
+
expect(parts).toEqual(expect.arrayContaining([
|
|
540
|
+
expect.objectContaining({
|
|
541
|
+
functionResponse: expect.objectContaining({ id: 'c1' }),
|
|
542
|
+
}),
|
|
543
|
+
expect.objectContaining({
|
|
544
|
+
functionResponse: expect.objectContaining({ id: 'c2' }),
|
|
545
|
+
}),
|
|
546
|
+
]));
|
|
290
547
|
});
|
|
291
|
-
it('SECURITY: should block
|
|
292
|
-
|
|
293
|
-
const definition = createTestDefinition([LSTool.Name]);
|
|
548
|
+
it('SECURITY: should block unauthorized tools and provide explicit failure to model', async () => {
|
|
549
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
294
550
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
295
|
-
// Turn 1: Model
|
|
296
|
-
|
|
551
|
+
// Turn 1: Model tries to use a tool not in its config
|
|
552
|
+
const badCallId = 'bad_call_1';
|
|
297
553
|
mockModelResponse([
|
|
298
554
|
{
|
|
299
|
-
name:
|
|
300
|
-
args: { path: '
|
|
301
|
-
id:
|
|
555
|
+
name: READ_FILE_TOOL_NAME,
|
|
556
|
+
args: { path: 'secret.txt' },
|
|
557
|
+
id: badCallId,
|
|
558
|
+
},
|
|
559
|
+
]);
|
|
560
|
+
// Turn 2: Model gives up and completes
|
|
561
|
+
mockModelResponse([
|
|
562
|
+
{
|
|
563
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
564
|
+
args: { finalResult: 'Could not read file.' },
|
|
565
|
+
id: 'c2',
|
|
302
566
|
},
|
|
303
567
|
]);
|
|
304
|
-
// Turn 2: Model stops
|
|
305
|
-
mockModelResponse([]);
|
|
306
|
-
// Extraction
|
|
307
|
-
mockModelResponse([], undefined, 'Done.');
|
|
308
568
|
const consoleWarnSpy = vi
|
|
309
569
|
.spyOn(console, 'warn')
|
|
310
570
|
.mockImplementation(() => { });
|
|
311
|
-
await executor.run({ goal: '
|
|
312
|
-
// Verify
|
|
571
|
+
await executor.run({ goal: 'Sec test' }, signal);
|
|
572
|
+
// Verify external executor was not called (Security held)
|
|
313
573
|
expect(mockExecuteToolCall).not.toHaveBeenCalled();
|
|
314
|
-
|
|
574
|
+
// 2. Verify console warning
|
|
575
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining(`[AgentExecutor] Blocked call:`));
|
|
315
576
|
consoleWarnSpy.mockRestore();
|
|
316
|
-
// Verify
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
expect(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
//
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
expect(extractionText).toContain('Be sure you have addressed:');
|
|
338
|
-
expect(extractionText).toContain('- Must include file names');
|
|
339
|
-
expect(extractionText).toContain('- Must be concise');
|
|
577
|
+
// Verify specific error was sent back to model
|
|
578
|
+
const turn2Params = getMockMessageParams(1);
|
|
579
|
+
const parts = turn2Params.message;
|
|
580
|
+
expect(parts).toBeDefined();
|
|
581
|
+
expect(parts[0]).toEqual(expect.objectContaining({
|
|
582
|
+
functionResponse: expect.objectContaining({
|
|
583
|
+
id: badCallId,
|
|
584
|
+
name: READ_FILE_TOOL_NAME,
|
|
585
|
+
response: {
|
|
586
|
+
error: expect.stringContaining('Unauthorized tool call'),
|
|
587
|
+
},
|
|
588
|
+
}),
|
|
589
|
+
}));
|
|
590
|
+
// Verify Activity Stream reported the error
|
|
591
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
592
|
+
type: 'ERROR',
|
|
593
|
+
data: expect.objectContaining({
|
|
594
|
+
context: 'tool_call_unauthorized',
|
|
595
|
+
name: READ_FILE_TOOL_NAME,
|
|
596
|
+
}),
|
|
597
|
+
}));
|
|
340
598
|
});
|
|
341
599
|
});
|
|
342
600
|
describe('run (Termination Conditions)', () => {
|
|
343
|
-
const
|
|
344
|
-
mockModelResponse([{ name:
|
|
345
|
-
mockExecuteToolCall.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
601
|
+
const mockWorkResponse = (id) => {
|
|
602
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
|
603
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
604
|
+
status: 'success',
|
|
605
|
+
request: {
|
|
606
|
+
callId: id,
|
|
607
|
+
name: LS_TOOL_NAME,
|
|
608
|
+
args: { path: '.' },
|
|
609
|
+
isClientInitiated: false,
|
|
610
|
+
prompt_id: 'test-prompt',
|
|
611
|
+
},
|
|
612
|
+
tool: {},
|
|
613
|
+
invocation: {},
|
|
614
|
+
response: {
|
|
615
|
+
callId: id,
|
|
616
|
+
resultDisplay: 'ok',
|
|
617
|
+
responseParts: [
|
|
618
|
+
{ functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
|
|
619
|
+
],
|
|
620
|
+
error: undefined,
|
|
621
|
+
errorType: undefined,
|
|
622
|
+
contentLength: undefined,
|
|
623
|
+
},
|
|
352
624
|
});
|
|
353
625
|
};
|
|
354
626
|
it('should terminate when max_turns is reached', async () => {
|
|
355
|
-
const
|
|
356
|
-
const definition = createTestDefinition([
|
|
357
|
-
max_turns:
|
|
627
|
+
const MAX = 2;
|
|
628
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
629
|
+
max_turns: MAX,
|
|
358
630
|
});
|
|
359
|
-
const executor = await AgentExecutor.create(definition, mockConfig
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
mockKeepAliveResponse();
|
|
364
|
-
const output = await executor.run({ goal: 'Termination test' }, signal);
|
|
631
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
632
|
+
mockWorkResponse('t1');
|
|
633
|
+
mockWorkResponse('t2');
|
|
634
|
+
const output = await executor.run({ goal: 'Turns test' }, signal);
|
|
365
635
|
expect(output.terminate_reason).toBe(AgentTerminateMode.MAX_TURNS);
|
|
366
|
-
expect(mockSendMessageStream).toHaveBeenCalledTimes(
|
|
367
|
-
// Extraction phase should be skipped when termination is forced
|
|
368
|
-
expect(mockSendMessageStream).not.toHaveBeenCalledWith(expect.any(String), expect.any(Object), expect.stringContaining('#extraction'));
|
|
636
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX);
|
|
369
637
|
});
|
|
370
638
|
it('should terminate if timeout is reached', async () => {
|
|
371
|
-
const definition = createTestDefinition([
|
|
372
|
-
max_time_minutes:
|
|
373
|
-
max_turns: 100,
|
|
639
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
640
|
+
max_time_minutes: 1,
|
|
374
641
|
});
|
|
375
|
-
const executor = await AgentExecutor.create(definition, mockConfig
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
await vi.advanceTimersByTimeAsync(
|
|
642
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
643
|
+
mockModelResponse([
|
|
644
|
+
{ name: LS_TOOL_NAME, args: { path: '.' }, id: 't1' },
|
|
645
|
+
]);
|
|
646
|
+
// Long running tool
|
|
647
|
+
mockExecuteToolCall.mockImplementationOnce(async (_ctx, reqInfo) => {
|
|
648
|
+
await vi.advanceTimersByTimeAsync(61 * 1000);
|
|
382
649
|
return {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
650
|
+
status: 'success',
|
|
651
|
+
request: reqInfo,
|
|
652
|
+
tool: {},
|
|
653
|
+
invocation: {},
|
|
654
|
+
response: {
|
|
655
|
+
callId: 't1',
|
|
656
|
+
resultDisplay: 'ok',
|
|
657
|
+
responseParts: [],
|
|
658
|
+
error: undefined,
|
|
659
|
+
errorType: undefined,
|
|
660
|
+
contentLength: undefined,
|
|
661
|
+
},
|
|
391
662
|
};
|
|
392
663
|
});
|
|
393
|
-
const output = await executor.run({ goal: '
|
|
664
|
+
const output = await executor.run({ goal: 'Timeout test' }, signal);
|
|
394
665
|
expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
|
|
395
|
-
// Should only have called the model once before the timeout check stopped it
|
|
396
666
|
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
|
|
397
667
|
});
|
|
398
|
-
it('should terminate when AbortSignal is triggered
|
|
668
|
+
it('should terminate when AbortSignal is triggered', async () => {
|
|
399
669
|
const definition = createTestDefinition();
|
|
400
|
-
const executor = await AgentExecutor.create(definition, mockConfig
|
|
401
|
-
|
|
402
|
-
mockSendMessageStream.mockImplementation(async () => (async function* () {
|
|
403
|
-
// Yield the first chunk
|
|
670
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
671
|
+
mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
|
|
404
672
|
yield {
|
|
405
673
|
type: StreamEventType.CHUNK,
|
|
406
674
|
value: createMockResponseChunk([
|
|
407
|
-
{ text: '
|
|
675
|
+
{ text: 'Thinking...', thought: true },
|
|
408
676
|
]),
|
|
409
677
|
};
|
|
410
|
-
// Simulate abort happening mid-stream
|
|
411
678
|
abortController.abort();
|
|
412
|
-
// The loop in callModel should break immediately due to signal check.
|
|
413
679
|
})());
|
|
414
|
-
const output = await executor.run({ goal: '
|
|
680
|
+
const output = await executor.run({ goal: 'Abort test' }, signal);
|
|
415
681
|
expect(output.terminate_reason).toBe(AgentTerminateMode.ABORTED);
|
|
416
682
|
});
|
|
417
683
|
});
|