@office-ai/aioncli-core 0.8.1 → 0.18.4
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 +8 -2
- package/dist/index.js +7 -2
- package/dist/index.js.map +1 -1
- 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/codebase-investigator.test.d.ts +6 -0
- package/dist/src/agents/codebase-investigator.test.js +35 -0
- package/dist/src/agents/codebase-investigator.test.js.map +1 -0
- package/dist/src/agents/executor.d.ts +37 -11
- package/dist/src/agents/executor.js +512 -150
- package/dist/src/agents/executor.js.map +1 -1
- package/dist/src/agents/executor.test.js +1188 -245
- 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 +6 -1
- package/dist/src/agents/registry.js +51 -4
- package/dist/src/agents/registry.js.map +1 -1
- package/dist/src/agents/registry.test.js +30 -16
- package/dist/src/agents/registry.test.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 +9 -4
- package/dist/src/agents/subagent-tool-wrapper.test.js.map +1 -1
- package/dist/src/agents/types.d.ts +37 -7
- package/dist/src/agents/types.js +2 -0
- package/dist/src/agents/types.js.map +1 -1
- package/dist/src/code_assist/codeAssist.js +1 -1
- package/dist/src/code_assist/codeAssist.test.d.ts +6 -0
- package/dist/src/code_assist/codeAssist.test.js +99 -0
- package/dist/src/code_assist/codeAssist.test.js.map +1 -0
- 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/experiments/client_metadata.d.ts +12 -0
- package/dist/src/code_assist/experiments/client_metadata.js +50 -0
- package/dist/src/code_assist/experiments/client_metadata.js.map +1 -0
- package/dist/src/code_assist/experiments/client_metadata.test.d.ts +6 -0
- package/dist/src/code_assist/experiments/client_metadata.test.js +99 -0
- package/dist/src/code_assist/experiments/client_metadata.test.js.map +1 -0
- package/dist/src/code_assist/experiments/experiments.d.ts +17 -0
- package/dist/src/code_assist/experiments/experiments.js +36 -0
- package/dist/src/code_assist/experiments/experiments.js.map +1 -0
- package/dist/src/code_assist/experiments/experiments.test.d.ts +6 -0
- package/dist/src/code_assist/experiments/experiments.test.js +92 -0
- package/dist/src/code_assist/experiments/experiments.test.js.map +1 -0
- package/dist/src/code_assist/experiments/flagNames.d.ts +13 -0
- package/dist/src/code_assist/experiments/flagNames.js +13 -0
- package/dist/src/code_assist/experiments/flagNames.js.map +1 -0
- package/dist/src/code_assist/experiments/types.d.ts +35 -0
- package/dist/src/code_assist/experiments/types.js +7 -0
- package/dist/src/code_assist/experiments/types.js.map +1 -0
- package/dist/src/code_assist/oauth-credential-storage.js +6 -5
- package/dist/src/code_assist/oauth-credential-storage.js.map +1 -1
- package/dist/src/code_assist/oauth-credential-storage.test.js +65 -3
- package/dist/src/code_assist/oauth-credential-storage.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.d.ts +2 -2
- package/dist/src/code_assist/oauth2.js +161 -93
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/oauth2.test.js +103 -57
- package/dist/src/code_assist/oauth2.test.js.map +1 -1
- package/dist/src/code_assist/server.d.ts +6 -4
- package/dist/src/code_assist/server.js +16 -8
- package/dist/src/code_assist/server.js.map +1 -1
- package/dist/src/code_assist/server.test.js +126 -28
- package/dist/src/code_assist/server.test.js.map +1 -1
- package/dist/src/code_assist/setup.d.ts +2 -2
- package/dist/src/code_assist/setup.js +4 -2
- package/dist/src/code_assist/setup.js.map +1 -1
- package/dist/src/code_assist/types.d.ts +1 -1
- package/dist/src/code_assist/types.js.map +1 -1
- package/dist/src/commands/extensions.d.ts +7 -0
- package/dist/src/commands/extensions.js +9 -0
- package/dist/src/commands/extensions.js.map +1 -0
- package/dist/src/commands/extensions.test.d.ts +6 -0
- package/dist/src/commands/extensions.test.js +19 -0
- package/dist/src/commands/extensions.test.js.map +1 -0
- package/dist/src/config/config.d.ts +169 -43
- package/dist/src/config/config.js +418 -79
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +684 -49
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.d.ts +7 -0
- package/dist/src/config/defaultModelConfigs.js +185 -0
- package/dist/src/config/defaultModelConfigs.js.map +1 -0
- package/dist/src/config/models.d.ts +23 -2
- package/dist/src/config/models.js +50 -7
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/models.test.js +71 -10
- package/dist/src/config/models.test.js.map +1 -1
- package/dist/src/config/storage.d.ts +3 -1
- package/dist/src/config/storage.js +22 -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 +3 -2
- package/dist/src/confirmation-bus/message-bus.js +9 -3
- package/dist/src/confirmation-bus/message-bus.js.map +1 -1
- package/dist/src/confirmation-bus/message-bus.test.js +30 -24
- package/dist/src/confirmation-bus/message-bus.test.js.map +1 -1
- package/dist/src/confirmation-bus/types.d.ts +13 -2
- package/dist/src/confirmation-bus/types.js +1 -0
- package/dist/src/confirmation-bus/types.js.map +1 -1
- package/dist/src/core/apiKeyCredentialStorage.d.ts +17 -0
- package/dist/src/core/apiKeyCredentialStorage.js +64 -0
- package/dist/src/core/apiKeyCredentialStorage.js.map +1 -0
- package/dist/src/core/apiKeyCredentialStorage.test.d.ts +6 -0
- package/dist/src/core/apiKeyCredentialStorage.test.js +71 -0
- package/dist/src/core/apiKeyCredentialStorage.test.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +4 -8
- package/dist/src/core/baseLlmClient.js +6 -11
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/baseLlmClient.test.js +22 -27
- package/dist/src/core/baseLlmClient.test.js.map +1 -1
- package/dist/src/core/client.d.ts +12 -19
- package/dist/src/core/client.js +104 -206
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +329 -452
- package/dist/src/core/client.test.js.map +1 -1
- package/dist/src/core/contentGenerator.d.ts +3 -2
- package/dist/src/core/contentGenerator.js +56 -41
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/contentGenerator.test.js +49 -1
- package/dist/src/core/contentGenerator.test.js.map +1 -1
- package/dist/src/core/coreToolScheduler.d.ts +8 -4
- package/dist/src/core/coreToolScheduler.js +348 -179
- package/dist/src/core/coreToolScheduler.js.map +1 -1
- package/dist/src/core/coreToolScheduler.test.js +575 -219
- package/dist/src/core/coreToolScheduler.test.js.map +1 -1
- package/dist/src/core/fakeContentGenerator.d.ts +33 -0
- package/dist/src/core/fakeContentGenerator.js +58 -0
- package/dist/src/core/fakeContentGenerator.js.map +1 -0
- package/dist/src/core/fakeContentGenerator.test.d.ts +6 -0
- package/dist/src/core/fakeContentGenerator.test.js +127 -0
- package/dist/src/core/fakeContentGenerator.test.js.map +1 -0
- package/dist/src/core/geminiChat.d.ts +23 -18
- package/dist/src/core/geminiChat.js +186 -108
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +581 -270
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/logger.d.ts +7 -2
- package/dist/src/core/logger.js +35 -27
- package/dist/src/core/logger.js.map +1 -1
- package/dist/src/core/logger.test.js +45 -29
- package/dist/src/core/logger.test.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.d.ts +1 -0
- package/dist/src/core/loggingContentGenerator.js +113 -33
- package/dist/src/core/loggingContentGenerator.js.map +1 -1
- package/dist/src/core/loggingContentGenerator.test.d.ts +6 -0
- package/dist/src/core/loggingContentGenerator.test.js +180 -0
- package/dist/src/core/loggingContentGenerator.test.js.map +1 -0
- package/dist/src/core/nonInteractiveToolExecutor.d.ts +3 -2
- package/dist/src/core/nonInteractiveToolExecutor.js +12 -7
- package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -1
- package/dist/src/core/nonInteractiveToolExecutor.test.js +12 -8
- package/dist/src/core/nonInteractiveToolExecutor.test.js.map +1 -1
- package/dist/src/core/openaiContentGenerator.js +31 -8
- package/dist/src/core/openaiContentGenerator.js.map +1 -1
- package/dist/src/core/prompts.d.ts +2 -1
- package/dist/src/core/prompts.js +135 -154
- package/dist/src/core/prompts.js.map +1 -1
- package/dist/src/core/prompts.test.js +128 -189
- package/dist/src/core/prompts.test.js.map +1 -1
- package/dist/src/core/recordingContentGenerator.d.ts +18 -0
- package/dist/src/core/recordingContentGenerator.js +77 -0
- package/dist/src/core/recordingContentGenerator.js.map +1 -0
- package/dist/src/core/recordingContentGenerator.test.d.ts +6 -0
- package/dist/src/core/recordingContentGenerator.test.js +101 -0
- package/dist/src/core/recordingContentGenerator.test.js.map +1 -0
- package/dist/src/core/tokenLimits.test.d.ts +6 -0
- package/dist/src/core/tokenLimits.test.js +26 -0
- package/dist/src/core/tokenLimits.test.js.map +1 -0
- package/dist/src/core/turn.d.ts +23 -3
- package/dist/src/core/turn.js +18 -9
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +98 -104
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/fallback/handler.js +60 -8
- package/dist/src/fallback/handler.js.map +1 -1
- package/dist/src/fallback/handler.test.js +132 -17
- package/dist/src/fallback/handler.test.js.map +1 -1
- package/dist/src/fallback/types.d.ts +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/hooks/hookAggregator.d.ts +68 -0
- package/dist/src/hooks/hookAggregator.js +262 -0
- package/dist/src/hooks/hookAggregator.js.map +1 -0
- package/dist/src/hooks/hookAggregator.test.d.ts +6 -0
- package/dist/src/hooks/hookAggregator.test.js +387 -0
- package/dist/src/hooks/hookAggregator.test.js.map +1 -0
- package/dist/src/hooks/hookPlanner.d.ts +46 -0
- package/dist/src/hooks/hookPlanner.js +108 -0
- package/dist/src/hooks/hookPlanner.js.map +1 -0
- package/dist/src/hooks/hookPlanner.test.d.ts +6 -0
- package/dist/src/hooks/hookPlanner.test.js +255 -0
- package/dist/src/hooks/hookPlanner.test.js.map +1 -0
- package/dist/src/hooks/hookRegistry.d.ts +87 -0
- package/dist/src/hooks/hookRegistry.js +198 -0
- package/dist/src/hooks/hookRegistry.js.map +1 -0
- package/dist/src/hooks/hookRegistry.test.d.ts +6 -0
- package/dist/src/hooks/hookRegistry.test.js +341 -0
- package/dist/src/hooks/hookRegistry.test.js.map +1 -0
- package/dist/src/hooks/hookRunner.d.ts +42 -0
- package/dist/src/hooks/hookRunner.js +272 -0
- package/dist/src/hooks/hookRunner.js.map +1 -0
- package/dist/src/hooks/hookRunner.test.d.ts +6 -0
- package/dist/src/hooks/hookRunner.test.js +468 -0
- package/dist/src/hooks/hookRunner.test.js.map +1 -0
- package/dist/src/hooks/hookTranslator.d.ts +113 -0
- package/dist/src/hooks/hookTranslator.js +232 -0
- package/dist/src/hooks/hookTranslator.js.map +1 -0
- package/dist/src/hooks/hookTranslator.test.d.ts +6 -0
- package/dist/src/hooks/hookTranslator.test.js +192 -0
- package/dist/src/hooks/hookTranslator.test.js.map +1 -0
- package/dist/src/hooks/types.d.ts +384 -0
- package/dist/src/hooks/types.js +284 -0
- package/dist/src/hooks/types.js.map +1 -0
- package/dist/src/hooks/types.test.d.ts +6 -0
- package/dist/src/hooks/types.test.js +313 -0
- package/dist/src/hooks/types.test.js.map +1 -0
- package/dist/src/ide/detect-ide.d.ts +4 -0
- package/dist/src/ide/detect-ide.js +6 -1
- package/dist/src/ide/detect-ide.js.map +1 -1
- package/dist/src/ide/detect-ide.test.js +16 -0
- package/dist/src/ide/detect-ide.test.js.map +1 -1
- package/dist/src/ide/ide-client.d.ts +3 -1
- package/dist/src/ide/ide-client.js +12 -10
- package/dist/src/ide/ide-client.js.map +1 -1
- package/dist/src/ide/ide-client.test.js +163 -4
- package/dist/src/ide/ide-client.test.js.map +1 -1
- package/dist/src/ide/ide-installer.js +66 -21
- package/dist/src/ide/ide-installer.js.map +1 -1
- package/dist/src/ide/ide-installer.test.js +54 -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/ide/types.d.ts +1 -1
- package/dist/src/ide/types.js +1 -1
- package/dist/src/index.d.ts +21 -0
- package/dist/src/index.js +24 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcp/google-auth-provider.d.ts +2 -0
- package/dist/src/mcp/google-auth-provider.js +21 -3
- package/dist/src/mcp/google-auth-provider.js.map +1 -1
- package/dist/src/mcp/google-auth-provider.test.js +42 -9
- package/dist/src/mcp/google-auth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-provider.d.ts +8 -5
- package/dist/src/mcp/oauth-provider.js +140 -55
- package/dist/src/mcp/oauth-provider.js.map +1 -1
- package/dist/src/mcp/oauth-provider.test.js +369 -2
- package/dist/src/mcp/oauth-provider.test.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.js +5 -4
- package/dist/src/mcp/oauth-token-storage.js.map +1 -1
- package/dist/src/mcp/oauth-token-storage.test.js +17 -11
- package/dist/src/mcp/oauth-token-storage.test.js.map +1 -1
- package/dist/src/mcp/oauth-utils.d.ts +7 -0
- package/dist/src/mcp/oauth-utils.js +28 -8
- package/dist/src/mcp/oauth-utils.js.map +1 -1
- package/dist/src/mcp/oauth-utils.test.js +45 -2
- package/dist/src/mcp/oauth-utils.test.js.map +1 -1
- package/dist/src/mcp/sa-impersonation-provider.d.ts +0 -6
- package/dist/src/mcp/sa-impersonation-provider.js +6 -23
- package/dist/src/mcp/sa-impersonation-provider.js.map +1 -1
- package/dist/src/mcp/token-storage/base-token-storage.test.js +75 -84
- package/dist/src/mcp/token-storage/base-token-storage.test.js.map +1 -1
- package/dist/src/mcp/token-storage/file-token-storage.js +3 -2
- package/dist/src/mcp/token-storage/file-token-storage.js.map +1 -1
- package/dist/src/mcp/token-storage/file-token-storage.test.js +11 -8
- package/dist/src/mcp/token-storage/file-token-storage.test.js.map +1 -1
- package/dist/src/mcp/token-storage/keychain-token-storage.d.ts +6 -2
- package/dist/src/mcp/token-storage/keychain-token-storage.js +63 -7
- package/dist/src/mcp/token-storage/keychain-token-storage.js.map +1 -1
- package/dist/src/mcp/token-storage/keychain-token-storage.test.js +54 -3
- package/dist/src/mcp/token-storage/keychain-token-storage.test.js.map +1 -1
- package/dist/src/mcp/token-storage/types.d.ts +6 -0
- package/dist/src/mcp/token-storage/types.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.d.ts +6 -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/policy/config.d.ts +31 -0
- package/dist/src/policy/config.js +199 -0
- package/dist/src/policy/config.js.map +1 -0
- package/dist/src/policy/config.test.d.ts +6 -0
- package/dist/src/policy/config.test.js +538 -0
- package/dist/src/policy/config.test.js.map +1 -0
- package/dist/src/policy/index.d.ts +2 -0
- package/dist/src/policy/index.js +2 -0
- package/dist/src/policy/index.js.map +1 -1
- package/dist/src/policy/policies/discovered.toml +8 -0
- package/dist/src/policy/policies/read-only.toml +56 -0
- package/dist/src/policy/policies/write.toml +73 -0
- package/dist/src/policy/policies/yolo.toml +31 -0
- package/dist/src/policy/policy-engine.d.ts +12 -3
- package/dist/src/policy/policy-engine.js +74 -8
- package/dist/src/policy/policy-engine.js.map +1 -1
- package/dist/src/policy/policy-engine.test.js +460 -76
- package/dist/src/policy/policy-engine.test.js.map +1 -1
- package/dist/src/policy/toml-loader.d.ts +47 -0
- package/dist/src/policy/toml-loader.js +411 -0
- package/dist/src/policy/toml-loader.js.map +1 -0
- package/dist/src/policy/toml-loader.test.d.ts +6 -0
- package/dist/src/policy/toml-loader.test.js +376 -0
- package/dist/src/policy/toml-loader.test.js.map +1 -0
- package/dist/src/policy/types.d.ts +83 -0
- package/dist/src/policy/types.js +10 -0
- package/dist/src/policy/types.js.map +1 -1
- package/dist/src/prompts/mcp-prompts.test.d.ts +6 -0
- package/dist/src/prompts/mcp-prompts.test.js +39 -0
- package/dist/src/prompts/mcp-prompts.test.js.map +1 -0
- package/dist/src/prompts/prompt-registry.js +2 -1
- package/dist/src/prompts/prompt-registry.js.map +1 -1
- package/dist/src/prompts/prompt-registry.test.d.ts +6 -0
- package/dist/src/prompts/prompt-registry.test.js +96 -0
- package/dist/src/prompts/prompt-registry.test.js.map +1 -0
- package/dist/src/routing/modelRouterService.js +15 -0
- package/dist/src/routing/modelRouterService.js.map +1 -1
- package/dist/src/routing/modelRouterService.test.js +62 -0
- package/dist/src/routing/modelRouterService.test.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.d.ts +1 -1
- package/dist/src/routing/strategies/classifierStrategy.js +9 -16
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js +17 -13
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.js +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.js.map +1 -1
- package/dist/src/routing/strategies/fallbackStrategy.test.js +4 -0
- package/dist/src/routing/strategies/fallbackStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.js +2 -2
- package/dist/src/routing/strategies/overrideStrategy.js.map +1 -1
- package/dist/src/routing/strategies/overrideStrategy.test.js +3 -0
- package/dist/src/routing/strategies/overrideStrategy.test.js.map +1 -1
- package/dist/src/safety/built-in.d.ts +21 -0
- package/dist/src/safety/built-in.js +106 -0
- package/dist/src/safety/built-in.js.map +1 -0
- package/dist/src/safety/built-in.test.d.ts +6 -0
- package/dist/src/safety/built-in.test.js +199 -0
- package/dist/src/safety/built-in.test.js.map +1 -0
- package/dist/src/safety/checker-runner.d.ts +48 -0
- package/dist/src/safety/checker-runner.js +208 -0
- package/dist/src/safety/checker-runner.js.map +1 -0
- package/dist/src/safety/checker-runner.test.d.ts +6 -0
- package/dist/src/safety/checker-runner.test.js +238 -0
- package/dist/src/safety/checker-runner.test.js.map +1 -0
- package/dist/src/safety/context-builder.d.ts +23 -0
- package/dist/src/safety/context-builder.js +47 -0
- package/dist/src/safety/context-builder.js.map +1 -0
- package/dist/src/safety/context-builder.test.d.ts +6 -0
- package/dist/src/safety/context-builder.test.js +49 -0
- package/dist/src/safety/context-builder.test.js.map +1 -0
- package/dist/src/safety/protocol.d.ts +88 -0
- package/dist/src/safety/protocol.js +15 -0
- package/dist/src/safety/protocol.js.map +1 -0
- package/dist/src/safety/registry.d.ts +26 -0
- package/dist/src/safety/registry.js +65 -0
- package/dist/src/safety/registry.js.map +1 -0
- package/dist/src/safety/registry.test.d.ts +6 -0
- package/dist/src/safety/registry.test.js +31 -0
- package/dist/src/safety/registry.test.js.map +1 -0
- package/dist/src/services/chatCompressionService.d.ts +32 -0
- package/dist/src/services/chatCompressionService.js +162 -0
- package/dist/src/services/chatCompressionService.js.map +1 -0
- package/dist/src/services/chatCompressionService.test.d.ts +6 -0
- package/dist/src/services/chatCompressionService.test.js +210 -0
- package/dist/src/services/chatCompressionService.test.js.map +1 -0
- package/dist/src/services/chatRecordingService.d.ts +3 -2
- package/dist/src/services/chatRecordingService.js +11 -9
- package/dist/src/services/chatRecordingService.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.d.ts +2 -14
- package/dist/src/services/fileDiscoveryService.js +19 -55
- package/dist/src/services/fileDiscoveryService.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.test.js +91 -11
- package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
- package/dist/src/services/loopDetectionService.d.ts +4 -1
- package/dist/src/services/loopDetectionService.js +95 -42
- package/dist/src/services/loopDetectionService.js.map +1 -1
- package/dist/src/services/loopDetectionService.test.js +220 -12
- package/dist/src/services/loopDetectionService.test.js.map +1 -1
- package/dist/src/services/modelConfig.golden.test.d.ts +6 -0
- package/dist/src/services/modelConfig.golden.test.js +42 -0
- package/dist/src/services/modelConfig.golden.test.js.map +1 -0
- package/dist/src/services/modelConfig.integration.test.d.ts +6 -0
- package/dist/src/services/modelConfig.integration.test.js +247 -0
- package/dist/src/services/modelConfig.integration.test.js.map +1 -0
- package/dist/src/services/modelConfigService.d.ts +48 -0
- package/dist/src/services/modelConfigService.js +151 -0
- package/dist/src/services/modelConfigService.js.map +1 -0
- package/dist/src/services/modelConfigService.test.d.ts +6 -0
- package/dist/src/services/modelConfigService.test.js +531 -0
- package/dist/src/services/modelConfigService.test.js.map +1 -0
- package/dist/src/services/shellExecutionService.d.ts +1 -0
- package/dist/src/services/shellExecutionService.js +195 -92
- package/dist/src/services/shellExecutionService.js.map +1 -1
- package/dist/src/services/shellExecutionService.test.js +137 -14
- package/dist/src/services/shellExecutionService.test.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases.golden.json +202 -0
- 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 +251 -0
- package/dist/src/telemetry/activity-monitor.test.js.map +1 -0
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.d.ts +25 -7
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.js +294 -76
- 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 +192 -66
- package/dist/src/telemetry/clearcut-logger/clearcut-logger.test.js.map +1 -1
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.d.ts +25 -3
- package/dist/src/telemetry/clearcut-logger/event-metadata-key.js +59 -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/gcp-exporters.js +0 -1
- package/dist/src/telemetry/gcp-exporters.js.map +1 -1
- package/dist/src/telemetry/gcp-exporters.test.js +1 -1
- package/dist/src/telemetry/gcp-exporters.test.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +7 -3
- package/dist/src/telemetry/index.js +13 -4
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/loggers.d.ts +14 -7
- package/dist/src/telemetry/loggers.js +197 -320
- package/dist/src/telemetry/loggers.js.map +1 -1
- package/dist/src/telemetry/loggers.test.circular.js +0 -1
- package/dist/src/telemetry/loggers.test.circular.js.map +1 -1
- package/dist/src/telemetry/loggers.test.js +460 -59
- 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 +180 -4
- package/dist/src/telemetry/metrics.js +270 -6
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +502 -184
- 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/semantic.d.ts +82 -0
- package/dist/src/telemetry/semantic.js +269 -0
- package/dist/src/telemetry/semantic.js.map +1 -0
- package/dist/src/telemetry/semantic.test.d.ts +6 -0
- package/dist/src/telemetry/semantic.test.js +387 -0
- package/dist/src/telemetry/semantic.test.js.map +1 -0
- package/dist/src/telemetry/telemetry-utils.test.js +29 -28
- package/dist/src/telemetry/telemetry-utils.test.js.map +1 -1
- package/dist/src/telemetry/telemetryAttributes.d.ts +8 -0
- package/dist/src/telemetry/telemetryAttributes.js +19 -0
- package/dist/src/telemetry/telemetryAttributes.js.map +1 -0
- package/dist/src/telemetry/trace.d.ts +46 -0
- package/dist/src/telemetry/trace.js +121 -0
- package/dist/src/telemetry/trace.js.map +1 -0
- package/dist/src/telemetry/types.d.ts +227 -29
- package/dist/src/telemetry/types.js +858 -72
- package/dist/src/telemetry/types.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.d.ts +1 -1
- package/dist/src/telemetry/uiTelemetry.js +7 -7
- package/dist/src/telemetry/uiTelemetry.js.map +1 -1
- package/dist/src/telemetry/uiTelemetry.test.js +89 -67
- package/dist/src/telemetry/uiTelemetry.test.js.map +1 -1
- package/dist/src/test-utils/config.d.ts +1 -1
- package/dist/src/test-utils/config.js +1 -1
- package/dist/src/tools/base-tool-invocation.test.d.ts +6 -0
- package/dist/src/tools/base-tool-invocation.test.js +85 -0
- package/dist/src/tools/base-tool-invocation.test.js.map +1 -0
- package/dist/src/tools/edit.d.ts +4 -3
- package/dist/src/tools/edit.js +50 -47
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +306 -216
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/glob.d.ts +4 -3
- package/dist/src/tools/glob.js +24 -27
- package/dist/src/tools/glob.js.map +1 -1
- package/dist/src/tools/glob.test.js +212 -205
- package/dist/src/tools/glob.test.js.map +1 -1
- package/dist/src/tools/grep.d.ts +4 -3
- package/dist/src/tools/grep.js +31 -25
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/grep.test.js +15 -12
- package/dist/src/tools/grep.test.js.map +1 -1
- package/dist/src/tools/ls.d.ts +4 -3
- package/dist/src/tools/ls.js +29 -35
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/ls.test.js +34 -42
- package/dist/src/tools/ls.test.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.d.ts +49 -11
- package/dist/src/tools/mcp-client-manager.js +191 -31
- package/dist/src/tools/mcp-client-manager.js.map +1 -1
- package/dist/src/tools/mcp-client-manager.test.js +132 -25
- package/dist/src/tools/mcp-client-manager.test.js.map +1 -1
- package/dist/src/tools/mcp-client.d.ts +12 -5
- package/dist/src/tools/mcp-client.js +287 -267
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +352 -45
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/mcp-tool.d.ts +6 -2
- package/dist/src/tools/mcp-tool.js +19 -8
- package/dist/src/tools/mcp-tool.js.map +1 -1
- package/dist/src/tools/mcp-tool.test.js +186 -273
- package/dist/src/tools/mcp-tool.test.js.map +1 -1
- package/dist/src/tools/memoryTool.d.ts +7 -5
- package/dist/src/tools/memoryTool.js +14 -12
- package/dist/src/tools/memoryTool.js.map +1 -1
- package/dist/src/tools/memoryTool.test.js +10 -9
- 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/modifiable-tool.d.ts +5 -1
- package/dist/src/tools/modifiable-tool.js +38 -16
- package/dist/src/tools/modifiable-tool.js.map +1 -1
- package/dist/src/tools/modifiable-tool.test.js +66 -31
- package/dist/src/tools/modifiable-tool.test.js.map +1 -1
- package/dist/src/tools/read-file.d.ts +6 -5
- package/dist/src/tools/read-file.js +26 -32
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +68 -33
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/read-many-files.d.ts +6 -12
- package/dist/src/tools/read-many-files.js +28 -57
- package/dist/src/tools/read-many-files.js.map +1 -1
- package/dist/src/tools/read-many-files.test.js +37 -36
- package/dist/src/tools/read-many-files.test.js.map +1 -1
- package/dist/src/tools/ripGrep.d.ts +28 -10
- package/dist/src/tools/ripGrep.js +195 -193
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/ripGrep.test.js +533 -204
- package/dist/src/tools/ripGrep.test.js.map +1 -1
- package/dist/src/tools/shell.d.ts +8 -6
- package/dist/src/tools/shell.js +64 -38
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +134 -43
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/smart-edit.d.ts +10 -23
- package/dist/src/tools/smart-edit.js +100 -85
- package/dist/src/tools/smart-edit.js.map +1 -1
- package/dist/src/tools/smart-edit.test.js +229 -179
- 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.d.ts +32 -20
- package/dist/src/tools/tool-registry.js +122 -78
- package/dist/src/tools/tool-registry.js.map +1 -1
- package/dist/src/tools/tool-registry.test.js +167 -90
- package/dist/src/tools/tool-registry.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +23 -8
- package/dist/src/tools/tools.js +69 -37
- 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 +80 -38
- package/dist/src/tools/web-fetch.js.map +1 -1
- package/dist/src/tools/web-fetch.test.js +338 -9
- 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 +11 -9
- package/dist/src/tools/web-search.js.map +1 -1
- package/dist/src/tools/web-search.test.js +6 -0
- package/dist/src/tools/web-search.test.js.map +1 -1
- package/dist/src/tools/write-file.d.ts +3 -2
- package/dist/src/tools/write-file.js +41 -40
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +130 -123
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/tools/write-todos.d.ts +34 -9
- package/dist/src/tools/write-todos.js +54 -11
- 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/channel.d.ts +19 -0
- package/dist/src/utils/channel.js +49 -0
- package/dist/src/utils/channel.js.map +1 -0
- package/dist/src/utils/channel.test.d.ts +6 -0
- package/dist/src/utils/channel.test.js +170 -0
- package/dist/src/utils/channel.test.js.map +1 -0
- 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 +69 -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 +10 -25
- package/dist/src/utils/editCorrector.js.map +1 -1
- package/dist/src/utils/editCorrector.test.js +19 -5
- package/dist/src/utils/editCorrector.test.js.map +1 -1
- package/dist/src/utils/editor.d.ts +4 -2
- package/dist/src/utils/editor.js +53 -39
- package/dist/src/utils/editor.js.map +1 -1
- package/dist/src/utils/editor.test.js +18 -45
- package/dist/src/utils/editor.test.js.map +1 -1
- package/dist/src/utils/environmentContext.d.ts +2 -1
- package/dist/src/utils/environmentContext.js +20 -33
- package/dist/src/utils/environmentContext.js.map +1 -1
- package/dist/src/utils/environmentContext.test.js +6 -34
- package/dist/src/utils/environmentContext.test.js.map +1 -1
- package/dist/src/utils/errorParsing.d.ts +1 -1
- package/dist/src/utils/errorParsing.js +5 -33
- package/dist/src/utils/errorParsing.js.map +1 -1
- package/dist/src/utils/errorParsing.test.js +0 -88
- package/dist/src/utils/errorParsing.test.js.map +1 -1
- package/dist/src/utils/errors.d.ts +3 -0
- package/dist/src/utils/errors.js +6 -0
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/events.d.ts +121 -0
- package/dist/src/utils/events.js +84 -0
- package/dist/src/utils/events.js.map +1 -0
- package/dist/src/utils/events.test.d.ts +6 -0
- package/dist/src/utils/events.test.js +212 -0
- package/dist/src/utils/events.test.js.map +1 -0
- package/dist/src/utils/extensionLoader.d.ts +86 -0
- package/dist/src/utils/extensionLoader.js +208 -0
- package/dist/src/utils/extensionLoader.js.map +1 -0
- package/dist/src/utils/extensionLoader.test.d.ts +6 -0
- package/dist/src/utils/extensionLoader.test.js +154 -0
- package/dist/src/utils/extensionLoader.test.js.map +1 -0
- package/dist/src/utils/fetch.d.ts +1 -0
- package/dist/src/utils/fetch.js +4 -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 +87 -61
- package/dist/src/utils/fileUtils.test.js.map +1 -1
- package/dist/src/utils/filesearch/fileSearch.js +1 -1
- package/dist/src/utils/filesearch/fileSearch.js.map +1 -1
- package/dist/src/utils/flashFallback.test.js +28 -47
- 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.js +9 -17
- 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.d.ts +4 -1
- package/dist/src/utils/gitIgnoreParser.js +28 -10
- package/dist/src/utils/gitIgnoreParser.js.map +1 -1
- package/dist/src/utils/gitIgnoreParser.test.js +58 -0
- package/dist/src/utils/gitIgnoreParser.test.js.map +1 -1
- package/dist/src/utils/googleErrors.d.ts +104 -0
- package/dist/src/utils/googleErrors.js +152 -0
- package/dist/src/utils/googleErrors.js.map +1 -0
- package/dist/src/utils/googleErrors.test.d.ts +6 -0
- package/dist/src/utils/googleErrors.test.js +301 -0
- package/dist/src/utils/googleErrors.test.js.map +1 -0
- package/dist/src/utils/googleQuotaErrors.d.ts +37 -0
- package/dist/src/utils/googleQuotaErrors.js +157 -0
- package/dist/src/utils/googleQuotaErrors.js.map +1 -0
- package/dist/src/utils/googleQuotaErrors.test.d.ts +6 -0
- package/dist/src/utils/googleQuotaErrors.test.js +311 -0
- package/dist/src/utils/googleQuotaErrors.test.js.map +1 -0
- package/dist/src/utils/httpErrors.d.ts +18 -0
- package/dist/src/utils/httpErrors.js +36 -0
- package/dist/src/utils/httpErrors.js.map +1 -0
- package/dist/src/utils/ignorePatterns.test.js +26 -30
- package/dist/src/utils/ignorePatterns.test.js.map +1 -1
- package/dist/src/utils/installationManager.js +2 -1
- package/dist/src/utils/installationManager.js.map +1 -1
- package/dist/src/utils/installationManager.test.js +6 -4
- package/dist/src/utils/installationManager.test.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.d.ts +1 -1
- package/dist/src/utils/llm-edit-fixer.js +33 -9
- package/dist/src/utils/llm-edit-fixer.js.map +1 -1
- package/dist/src/utils/llm-edit-fixer.test.js +38 -1
- package/dist/src/utils/llm-edit-fixer.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.d.ts +20 -1
- package/dist/src/utils/memoryDiscovery.js +176 -12
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +299 -40
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.js +4 -3
- package/dist/src/utils/memoryImportProcessor.js.map +1 -1
- package/dist/src/utils/memoryImportProcessor.test.js +8 -14
- package/dist/src/utils/memoryImportProcessor.test.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.js +3 -3
- package/dist/src/utils/nextSpeakerChecker.js.map +1 -1
- package/dist/src/utils/nextSpeakerChecker.test.js +13 -5
- package/dist/src/utils/nextSpeakerChecker.test.js.map +1 -1
- package/dist/src/utils/package.d.ts +12 -0
- package/dist/src/utils/package.js +15 -0
- package/dist/src/utils/package.js.map +1 -0
- 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/pathReader.js +4 -4
- package/dist/src/utils/pathReader.js.map +1 -1
- package/dist/src/utils/pathReader.test.js +44 -1
- package/dist/src/utils/pathReader.test.js.map +1 -1
- package/dist/src/utils/paths.d.ts +1 -1
- package/dist/src/utils/paths.js +131 -29
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/paths.test.js +200 -68
- package/dist/src/utils/paths.test.js.map +1 -1
- package/dist/src/utils/quotaErrorDetection.d.ts +0 -2
- package/dist/src/utils/quotaErrorDetection.js +0 -46
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/retry.d.ts +3 -10
- package/dist/src/utils/retry.js +97 -195
- package/dist/src/utils/retry.js.map +1 -1
- package/dist/src/utils/retry.test.js +179 -145
- 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 +387 -140
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +244 -62
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/stdio.d.ts +32 -0
- package/dist/src/utils/stdio.js +85 -0
- package/dist/src/utils/stdio.js.map +1 -0
- package/dist/src/utils/stdio.test.d.ts +6 -0
- package/dist/src/utils/stdio.test.js +47 -0
- package/dist/src/utils/stdio.test.js.map +1 -0
- package/dist/src/utils/summarizer.d.ts +4 -2
- package/dist/src/utils/summarizer.js +8 -9
- package/dist/src/utils/summarizer.js.map +1 -1
- package/dist/src/utils/summarizer.test.js +32 -12
- package/dist/src/utils/summarizer.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/systemEncoding.test.js +2 -1
- package/dist/src/utils/systemEncoding.test.js.map +1 -1
- package/dist/src/utils/terminal.d.ts +14 -0
- package/dist/src/utils/terminal.js +38 -0
- package/dist/src/utils/terminal.js.map +1 -0
- 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 +9 -7
- package/dist/src/utils/userAccountManager.test.js.map +1 -1
- package/dist/src/utils/workspaceContext.d.ts +4 -3
- package/dist/src/utils/workspaceContext.js +13 -13
- package/dist/src/utils/workspaceContext.js.map +1 -1
- package/dist/src/utils/workspaceContext.test.js +8 -7
- package/dist/src/utils/workspaceContext.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +12 -7
|
@@ -4,18 +4,39 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
+
import { debugLogger } from '../utils/debugLogger.js';
|
|
7
8
|
import { AgentExecutor } from './executor.js';
|
|
8
|
-
import { AgentTerminateMode } from './types.js';
|
|
9
9
|
import { makeFakeConfig } from '../test-utils/config.js';
|
|
10
10
|
import { ToolRegistry } from '../tools/tool-registry.js';
|
|
11
11
|
import { LSTool } from '../tools/ls.js';
|
|
12
|
-
import {
|
|
12
|
+
import { LS_TOOL_NAME, READ_FILE_TOOL_NAME } from '../tools/tool-names.js';
|
|
13
13
|
import { GeminiChat, StreamEventType, } from '../core/geminiChat.js';
|
|
14
|
+
import {} from '@google/genai';
|
|
14
15
|
import { MockTool } from '../test-utils/mock-tool.js';
|
|
15
16
|
import { getDirectoryContextString } from '../utils/environmentContext.js';
|
|
16
|
-
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
import { promptIdContext } from '../utils/promptIdContext.js';
|
|
19
|
+
import { logAgentStart, logAgentFinish, logRecoveryAttempt, } from '../telemetry/loggers.js';
|
|
20
|
+
import { AgentStartEvent, AgentFinishEvent, RecoveryAttemptEvent, } from '../telemetry/types.js';
|
|
21
|
+
import { AgentTerminateMode } from './types.js';
|
|
22
|
+
import { CompressionStatus } from '../core/turn.js';
|
|
23
|
+
import { ChatCompressionService } from '../services/chatCompressionService.js';
|
|
24
|
+
import { getModelConfigAlias } from './registry.js';
|
|
25
|
+
const { mockSendMessageStream, mockExecuteToolCall, mockSetSystemInstruction, mockCompress, mockSetTools, } = vi.hoisted(() => ({
|
|
17
26
|
mockSendMessageStream: vi.fn(),
|
|
18
27
|
mockExecuteToolCall: vi.fn(),
|
|
28
|
+
mockSetSystemInstruction: vi.fn(),
|
|
29
|
+
mockCompress: vi.fn(),
|
|
30
|
+
mockSetTools: vi.fn(),
|
|
31
|
+
}));
|
|
32
|
+
let mockChatHistory = [];
|
|
33
|
+
const mockSetHistory = vi.fn((newHistory) => {
|
|
34
|
+
mockChatHistory = newHistory;
|
|
35
|
+
});
|
|
36
|
+
vi.mock('../services/chatCompressionService.js', () => ({
|
|
37
|
+
ChatCompressionService: vi.fn().mockImplementation(() => ({
|
|
38
|
+
compress: mockCompress,
|
|
39
|
+
})),
|
|
19
40
|
}));
|
|
20
41
|
vi.mock('../core/geminiChat.js', async (importOriginal) => {
|
|
21
42
|
const actual = await importOriginal();
|
|
@@ -23,6 +44,10 @@ vi.mock('../core/geminiChat.js', async (importOriginal) => {
|
|
|
23
44
|
...actual,
|
|
24
45
|
GeminiChat: vi.fn().mockImplementation(() => ({
|
|
25
46
|
sendMessageStream: mockSendMessageStream,
|
|
47
|
+
getHistory: vi.fn((_curated) => [...mockChatHistory]),
|
|
48
|
+
setHistory: mockSetHistory,
|
|
49
|
+
setSystemInstruction: mockSetSystemInstruction,
|
|
50
|
+
setTools: mockSetTools,
|
|
26
51
|
})),
|
|
27
52
|
};
|
|
28
53
|
});
|
|
@@ -30,13 +55,42 @@ vi.mock('../core/nonInteractiveToolExecutor.js', () => ({
|
|
|
30
55
|
executeToolCall: mockExecuteToolCall,
|
|
31
56
|
}));
|
|
32
57
|
vi.mock('../utils/environmentContext.js');
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
58
|
+
vi.mock('../telemetry/loggers.js', () => ({
|
|
59
|
+
logAgentStart: vi.fn(),
|
|
60
|
+
logAgentFinish: vi.fn(),
|
|
61
|
+
logRecoveryAttempt: vi.fn(),
|
|
62
|
+
}));
|
|
63
|
+
vi.mock('../utils/promptIdContext.js', async (importOriginal) => {
|
|
64
|
+
const actual = await importOriginal();
|
|
65
|
+
return {
|
|
66
|
+
...actual,
|
|
67
|
+
promptIdContext: {
|
|
68
|
+
...actual.promptIdContext,
|
|
69
|
+
getStore: vi.fn(),
|
|
70
|
+
run: vi.fn((_id, fn) => fn()),
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
const MockedGeminiChat = vi.mocked(GeminiChat);
|
|
75
|
+
const mockedGetDirectoryContextString = vi.mocked(getDirectoryContextString);
|
|
76
|
+
const mockedPromptIdContext = vi.mocked(promptIdContext);
|
|
77
|
+
const mockedLogAgentStart = vi.mocked(logAgentStart);
|
|
78
|
+
const mockedLogAgentFinish = vi.mocked(logAgentFinish);
|
|
79
|
+
const mockedLogRecoveryAttempt = vi.mocked(logRecoveryAttempt);
|
|
80
|
+
// Constants for testing
|
|
81
|
+
const TASK_COMPLETE_TOOL_NAME = 'complete_task';
|
|
82
|
+
const MOCK_TOOL_NOT_ALLOWED = new MockTool({ name: 'write_file_interactive' });
|
|
83
|
+
/**
|
|
84
|
+
* Helper to create a mock API response chunk.
|
|
85
|
+
* Uses conditional spread to handle readonly functionCalls property safely.
|
|
86
|
+
*/
|
|
36
87
|
const createMockResponseChunk = (parts, functionCalls) => ({
|
|
37
88
|
candidates: [{ index: 0, content: { role: 'model', parts } }],
|
|
38
|
-
functionCalls,
|
|
89
|
+
...(functionCalls && functionCalls.length > 0 ? { functionCalls } : {}),
|
|
39
90
|
});
|
|
91
|
+
/**
|
|
92
|
+
* Helper to mock a single turn of model response in the stream.
|
|
93
|
+
*/
|
|
40
94
|
const mockModelResponse = (functionCalls, thought, text) => {
|
|
41
95
|
const parts = [];
|
|
42
96
|
if (thought) {
|
|
@@ -47,9 +101,7 @@ const mockModelResponse = (functionCalls, thought, text) => {
|
|
|
47
101
|
}
|
|
48
102
|
if (text)
|
|
49
103
|
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);
|
|
104
|
+
const responseChunk = createMockResponseChunk(parts, functionCalls);
|
|
53
105
|
mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
|
|
54
106
|
yield {
|
|
55
107
|
type: StreamEventType.CHUNK,
|
|
@@ -57,38 +109,85 @@ const mockModelResponse = (functionCalls, thought, text) => {
|
|
|
57
109
|
};
|
|
58
110
|
})());
|
|
59
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* Helper to extract the message parameters sent to sendMessageStream.
|
|
114
|
+
* Provides type safety for inspecting mock calls.
|
|
115
|
+
*/
|
|
116
|
+
const getMockMessageParams = (callIndex) => {
|
|
117
|
+
const call = mockSendMessageStream.mock.calls[callIndex];
|
|
118
|
+
expect(call).toBeDefined();
|
|
119
|
+
return {
|
|
120
|
+
modelConfigKey: call[0],
|
|
121
|
+
message: call[1],
|
|
122
|
+
};
|
|
123
|
+
};
|
|
60
124
|
let mockConfig;
|
|
61
125
|
let parentToolRegistry;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
}
|
|
126
|
+
/**
|
|
127
|
+
* Type-safe helper to create agent definitions for tests.
|
|
128
|
+
*/
|
|
129
|
+
const createTestDefinition = (tools = [LS_TOOL_NAME], runConfigOverrides = {}, outputConfigMode = 'default', schema = z.string()) => {
|
|
130
|
+
let outputConfig;
|
|
131
|
+
if (outputConfigMode === 'default') {
|
|
132
|
+
outputConfig = {
|
|
133
|
+
outputName: 'finalResult',
|
|
134
|
+
description: 'The final result.',
|
|
135
|
+
schema,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
name: 'TestAgent',
|
|
140
|
+
description: 'An agent for testing.',
|
|
141
|
+
inputConfig: {
|
|
142
|
+
inputs: { goal: { type: 'string', required: true, description: 'goal' } },
|
|
143
|
+
},
|
|
144
|
+
modelConfig: { model: 'gemini-test-model', temp: 0, top_p: 1 },
|
|
145
|
+
runConfig: { max_time_minutes: 5, max_turns: 5, ...runConfigOverrides },
|
|
146
|
+
promptConfig: { systemPrompt: 'Achieve the goal: ${goal}.' },
|
|
147
|
+
toolConfig: { tools },
|
|
148
|
+
outputConfig,
|
|
149
|
+
};
|
|
150
|
+
};
|
|
74
151
|
describe('AgentExecutor', () => {
|
|
75
152
|
let activities;
|
|
76
153
|
let onActivity;
|
|
77
154
|
let abortController;
|
|
78
155
|
let signal;
|
|
79
156
|
beforeEach(async () => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
157
|
+
vi.resetAllMocks();
|
|
158
|
+
mockCompress.mockClear();
|
|
159
|
+
mockSetHistory.mockClear();
|
|
160
|
+
mockSendMessageStream.mockReset();
|
|
161
|
+
mockSetSystemInstruction.mockReset();
|
|
162
|
+
mockSetTools.mockReset();
|
|
163
|
+
mockExecuteToolCall.mockReset();
|
|
164
|
+
mockedLogAgentStart.mockReset();
|
|
165
|
+
mockedLogAgentFinish.mockReset();
|
|
166
|
+
mockedPromptIdContext.getStore.mockReset();
|
|
167
|
+
mockedPromptIdContext.run.mockImplementation((_id, fn) => fn());
|
|
168
|
+
ChatCompressionService.mockImplementation(() => ({
|
|
169
|
+
compress: mockCompress,
|
|
170
|
+
}));
|
|
171
|
+
mockCompress.mockResolvedValue({
|
|
172
|
+
newHistory: null,
|
|
173
|
+
info: { compressionStatus: CompressionStatus.NOOP },
|
|
174
|
+
});
|
|
175
|
+
MockedGeminiChat.mockImplementation(() => ({
|
|
176
|
+
sendMessageStream: mockSendMessageStream,
|
|
177
|
+
setSystemInstruction: mockSetSystemInstruction,
|
|
178
|
+
setTools: mockSetTools,
|
|
179
|
+
getHistory: vi.fn((_curated) => [...mockChatHistory]),
|
|
180
|
+
getLastPromptTokenCount: vi.fn(() => 100),
|
|
181
|
+
setHistory: mockSetHistory,
|
|
182
|
+
}));
|
|
84
183
|
vi.useFakeTimers();
|
|
85
184
|
mockConfig = makeFakeConfig();
|
|
86
185
|
parentToolRegistry = new ToolRegistry(mockConfig);
|
|
87
186
|
parentToolRegistry.registerTool(new LSTool(mockConfig));
|
|
88
|
-
parentToolRegistry.registerTool(new
|
|
187
|
+
parentToolRegistry.registerTool(new MockTool({ name: READ_FILE_TOOL_NAME }));
|
|
89
188
|
parentToolRegistry.registerTool(MOCK_TOOL_NOT_ALLOWED);
|
|
90
189
|
vi.spyOn(mockConfig, 'getToolRegistry').mockResolvedValue(parentToolRegistry);
|
|
91
|
-
|
|
190
|
+
mockedGetDirectoryContextString.mockResolvedValue('Mocked Environment Context');
|
|
92
191
|
activities = [];
|
|
93
192
|
onActivity = (activity) => activities.push(activity);
|
|
94
193
|
abortController = new AbortController();
|
|
@@ -99,321 +198,1165 @@ describe('AgentExecutor', () => {
|
|
|
99
198
|
});
|
|
100
199
|
describe('create (Initialization and Validation)', () => {
|
|
101
200
|
it('should create successfully with allowed tools', async () => {
|
|
102
|
-
const definition = createTestDefinition([
|
|
201
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
103
202
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
104
203
|
expect(executor).toBeInstanceOf(AgentExecutor);
|
|
105
204
|
});
|
|
106
205
|
it('SECURITY: should throw if a tool is not on the non-interactive allowlist', async () => {
|
|
107
206
|
const definition = createTestDefinition([MOCK_TOOL_NOT_ALLOWED.name]);
|
|
108
|
-
await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(
|
|
207
|
+
await expect(AgentExecutor.create(definition, mockConfig, onActivity)).rejects.toThrow(/not on the allow-list for non-interactive execution/);
|
|
109
208
|
});
|
|
110
209
|
it('should create an isolated ToolRegistry for the agent', async () => {
|
|
111
|
-
const definition = createTestDefinition([
|
|
210
|
+
const definition = createTestDefinition([
|
|
211
|
+
LS_TOOL_NAME,
|
|
212
|
+
READ_FILE_TOOL_NAME,
|
|
213
|
+
]);
|
|
112
214
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
113
|
-
|
|
114
|
-
const agentRegistry = executor.toolRegistry;
|
|
215
|
+
const agentRegistry = executor['toolRegistry'];
|
|
115
216
|
expect(agentRegistry).not.toBe(parentToolRegistry);
|
|
116
|
-
expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([
|
|
217
|
+
expect(agentRegistry.getAllToolNames()).toEqual(expect.arrayContaining([LS_TOOL_NAME, READ_FILE_TOOL_NAME]));
|
|
117
218
|
expect(agentRegistry.getAllToolNames()).toHaveLength(2);
|
|
118
219
|
expect(agentRegistry.getTool(MOCK_TOOL_NOT_ALLOWED.name)).toBeUndefined();
|
|
119
220
|
});
|
|
221
|
+
it('should use parentPromptId from context to create agentId', async () => {
|
|
222
|
+
const parentId = 'parent-id';
|
|
223
|
+
mockedPromptIdContext.getStore.mockReturnValue(parentId);
|
|
224
|
+
const definition = createTestDefinition();
|
|
225
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
226
|
+
expect(executor['agentId']).toMatch(new RegExp(`^${parentId}-${definition.name}-`));
|
|
227
|
+
});
|
|
228
|
+
it('should correctly apply templates to initialMessages', async () => {
|
|
229
|
+
const definition = createTestDefinition();
|
|
230
|
+
// Override promptConfig to use initialMessages instead of systemPrompt
|
|
231
|
+
definition.promptConfig = {
|
|
232
|
+
initialMessages: [
|
|
233
|
+
{ role: 'user', parts: [{ text: 'Goal: ${goal}' }] },
|
|
234
|
+
{ role: 'model', parts: [{ text: 'OK, starting on ${goal}.' }] },
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
const inputs = { goal: 'TestGoal' };
|
|
238
|
+
// Mock a response to prevent the loop from running forever
|
|
239
|
+
mockModelResponse([
|
|
240
|
+
{
|
|
241
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
242
|
+
args: { finalResult: 'done' },
|
|
243
|
+
id: 'call1',
|
|
244
|
+
},
|
|
245
|
+
]);
|
|
246
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
247
|
+
await executor.run(inputs, signal);
|
|
248
|
+
const chatConstructorArgs = MockedGeminiChat.mock.calls[0];
|
|
249
|
+
const startHistory = chatConstructorArgs[3]; // history is the 4th arg
|
|
250
|
+
expect(startHistory).toBeDefined();
|
|
251
|
+
expect(startHistory).toHaveLength(2);
|
|
252
|
+
// Perform checks on defined objects to satisfy TS
|
|
253
|
+
const firstPart = startHistory?.[0]?.parts?.[0];
|
|
254
|
+
expect(firstPart?.text).toBe('Goal: TestGoal');
|
|
255
|
+
const secondPart = startHistory?.[1]?.parts?.[0];
|
|
256
|
+
expect(secondPart?.text).toBe('OK, starting on TestGoal.');
|
|
257
|
+
});
|
|
120
258
|
});
|
|
121
259
|
describe('run (Execution Loop and Logic)', () => {
|
|
122
|
-
it('should
|
|
260
|
+
it('should log AgentFinish with error if run throws', async () => {
|
|
261
|
+
const definition = createTestDefinition();
|
|
262
|
+
// Make the definition invalid to cause an error during run
|
|
263
|
+
definition.inputConfig.inputs = {
|
|
264
|
+
goal: { type: 'string', required: true, description: 'goal' },
|
|
265
|
+
};
|
|
266
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
267
|
+
// Run without inputs to trigger validation error
|
|
268
|
+
await expect(executor.run({}, signal)).rejects.toThrow(/Missing required input parameters/);
|
|
269
|
+
expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
|
|
270
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
|
|
271
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
|
|
272
|
+
terminate_reason: AgentTerminateMode.ERROR,
|
|
273
|
+
}));
|
|
274
|
+
});
|
|
275
|
+
it('should execute successfully when model calls complete_task with output (Happy Path with Output)', async () => {
|
|
123
276
|
const definition = createTestDefinition();
|
|
124
277
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
125
278
|
const inputs = { goal: 'Find files' };
|
|
126
279
|
// Turn 1: Model calls ls
|
|
127
|
-
mockModelResponse([{ name:
|
|
280
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' }], 'T1: Listing');
|
|
128
281
|
mockExecuteToolCall.mockResolvedValueOnce({
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
282
|
+
status: 'success',
|
|
283
|
+
request: {
|
|
284
|
+
callId: 'call1',
|
|
285
|
+
name: LS_TOOL_NAME,
|
|
286
|
+
args: { path: '.' },
|
|
287
|
+
isClientInitiated: false,
|
|
288
|
+
prompt_id: 'test-prompt',
|
|
289
|
+
},
|
|
290
|
+
tool: {},
|
|
291
|
+
invocation: {},
|
|
292
|
+
response: {
|
|
293
|
+
callId: 'call1',
|
|
294
|
+
resultDisplay: 'file1.txt',
|
|
295
|
+
responseParts: [
|
|
296
|
+
{
|
|
297
|
+
functionResponse: {
|
|
298
|
+
name: LS_TOOL_NAME,
|
|
299
|
+
response: { result: 'file1.txt' },
|
|
300
|
+
id: 'call1',
|
|
301
|
+
},
|
|
137
302
|
},
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
303
|
+
],
|
|
304
|
+
error: undefined,
|
|
305
|
+
errorType: undefined,
|
|
306
|
+
contentLength: undefined,
|
|
307
|
+
},
|
|
141
308
|
});
|
|
142
|
-
// Turn 2: Model
|
|
143
|
-
mockModelResponse([
|
|
144
|
-
|
|
145
|
-
|
|
309
|
+
// Turn 2: Model calls complete_task with required output
|
|
310
|
+
mockModelResponse([
|
|
311
|
+
{
|
|
312
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
313
|
+
args: { finalResult: 'Found file1.txt' },
|
|
314
|
+
id: 'call2',
|
|
315
|
+
},
|
|
316
|
+
], 'T2: Done');
|
|
146
317
|
const output = await executor.run(inputs, signal);
|
|
147
|
-
expect(mockSendMessageStream).toHaveBeenCalledTimes(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
expect(
|
|
153
|
-
|
|
154
|
-
expect(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
expect(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
},
|
|
166
|
-
]),
|
|
167
|
-
config: expect.objectContaining({ tools: undefined }), // No tools in extraction
|
|
168
|
-
}), expect.stringContaining('#extraction'));
|
|
169
|
-
expect(output.result).toBe('Result: file1.txt.');
|
|
318
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
319
|
+
const systemInstruction = MockedGeminiChat.mock.calls[0][1];
|
|
320
|
+
expect(systemInstruction).toContain(`MUST call the \`${TASK_COMPLETE_TOOL_NAME}\` tool`);
|
|
321
|
+
expect(systemInstruction).toContain('Mocked Environment Context');
|
|
322
|
+
expect(systemInstruction).toContain('You are running in a non-interactive mode');
|
|
323
|
+
expect(systemInstruction).toContain('Always use absolute paths');
|
|
324
|
+
const { modelConfigKey } = getMockMessageParams(0);
|
|
325
|
+
expect(modelConfigKey.model).toBe(getModelConfigAlias(definition));
|
|
326
|
+
const call = mockSetTools.mock.calls[0];
|
|
327
|
+
const sentTools = call[0][0].functionDeclarations;
|
|
328
|
+
expect(sentTools).toBeDefined();
|
|
329
|
+
expect(sentTools).toEqual(expect.arrayContaining([
|
|
330
|
+
expect.objectContaining({ name: LS_TOOL_NAME }),
|
|
331
|
+
expect.objectContaining({ name: TASK_COMPLETE_TOOL_NAME }),
|
|
332
|
+
]));
|
|
333
|
+
const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
|
|
334
|
+
expect(completeToolDef?.parameters?.required).toContain('finalResult');
|
|
335
|
+
expect(output.result).toBe('Found file1.txt');
|
|
170
336
|
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
171
|
-
//
|
|
337
|
+
// Telemetry checks
|
|
338
|
+
expect(mockedLogAgentStart).toHaveBeenCalledTimes(1);
|
|
339
|
+
expect(mockedLogAgentStart).toHaveBeenCalledWith(mockConfig, expect.any(AgentStartEvent));
|
|
340
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledTimes(1);
|
|
341
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.any(AgentFinishEvent));
|
|
342
|
+
const finishEvent = mockedLogAgentFinish.mock.calls[0][1];
|
|
343
|
+
expect(finishEvent.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
344
|
+
// Context checks
|
|
345
|
+
expect(mockedPromptIdContext.run).toHaveBeenCalledTimes(2); // Two turns
|
|
346
|
+
const agentId = executor['agentId'];
|
|
347
|
+
expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(1, `${agentId}#0`, expect.any(Function));
|
|
348
|
+
expect(mockedPromptIdContext.run).toHaveBeenNthCalledWith(2, `${agentId}#1`, expect.any(Function));
|
|
172
349
|
expect(activities).toEqual(expect.arrayContaining([
|
|
173
|
-
// Thought subjects are extracted by the executor (parseThought)
|
|
174
350
|
expect.objectContaining({
|
|
175
351
|
type: 'THOUGHT_CHUNK',
|
|
176
352
|
data: { text: 'T1: Listing' },
|
|
177
353
|
}),
|
|
178
354
|
expect.objectContaining({
|
|
179
|
-
type: '
|
|
180
|
-
data: { name:
|
|
355
|
+
type: 'TOOL_CALL_END',
|
|
356
|
+
data: { name: LS_TOOL_NAME, output: 'file1.txt' },
|
|
181
357
|
}),
|
|
182
358
|
expect.objectContaining({
|
|
183
|
-
type: '
|
|
184
|
-
data: {
|
|
359
|
+
type: 'TOOL_CALL_START',
|
|
360
|
+
data: {
|
|
361
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
362
|
+
args: { finalResult: 'Found file1.txt' },
|
|
363
|
+
},
|
|
185
364
|
}),
|
|
186
365
|
expect.objectContaining({
|
|
187
|
-
type: '
|
|
188
|
-
data: {
|
|
366
|
+
type: 'TOOL_CALL_END',
|
|
367
|
+
data: {
|
|
368
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
369
|
+
output: expect.stringContaining('Output submitted'),
|
|
370
|
+
},
|
|
189
371
|
}),
|
|
190
372
|
]));
|
|
191
373
|
});
|
|
192
|
-
it('should execute
|
|
193
|
-
const definition = createTestDefinition([
|
|
374
|
+
it('should execute successfully when model calls complete_task without output (Happy Path No Output)', async () => {
|
|
375
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {}, 'none');
|
|
194
376
|
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}`,
|
|
377
|
+
mockModelResponse([
|
|
378
|
+
{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
|
|
379
|
+
]);
|
|
380
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
381
|
+
status: 'success',
|
|
382
|
+
request: {
|
|
383
|
+
callId: 'call1',
|
|
384
|
+
name: LS_TOOL_NAME,
|
|
385
|
+
args: { path: '.' },
|
|
386
|
+
isClientInitiated: false,
|
|
387
|
+
prompt_id: 'test-prompt',
|
|
388
|
+
},
|
|
389
|
+
tool: {},
|
|
390
|
+
invocation: {},
|
|
391
|
+
response: {
|
|
392
|
+
callId: 'call1',
|
|
393
|
+
resultDisplay: 'ok',
|
|
220
394
|
responseParts: [
|
|
221
395
|
{
|
|
222
396
|
functionResponse: {
|
|
223
|
-
name:
|
|
397
|
+
name: LS_TOOL_NAME,
|
|
224
398
|
response: {},
|
|
225
|
-
id:
|
|
399
|
+
id: 'call1',
|
|
226
400
|
},
|
|
227
401
|
},
|
|
228
402
|
],
|
|
229
403
|
error: undefined,
|
|
230
|
-
|
|
404
|
+
errorType: undefined,
|
|
405
|
+
contentLength: undefined,
|
|
406
|
+
},
|
|
231
407
|
});
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
expect(
|
|
241
|
-
expect(
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const turn2Input = mockSendMessageStream.mock.calls[1][1];
|
|
245
|
-
const turn2Parts = turn2Input.message;
|
|
246
|
-
// Promise.all preserves the order of the input array.
|
|
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
|
-
}));
|
|
408
|
+
mockModelResponse([{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' }], 'Task finished.');
|
|
409
|
+
const output = await executor.run({ goal: 'Do work' }, signal);
|
|
410
|
+
const { modelConfigKey } = getMockMessageParams(0);
|
|
411
|
+
expect(modelConfigKey.model).toBe(getModelConfigAlias(definition));
|
|
412
|
+
const call = mockSetTools.mock.calls[0];
|
|
413
|
+
const sentTools = call[0][0].functionDeclarations;
|
|
414
|
+
expect(sentTools).toBeDefined();
|
|
415
|
+
const completeToolDef = sentTools.find((t) => t.name === TASK_COMPLETE_TOOL_NAME);
|
|
416
|
+
expect(completeToolDef?.parameters?.required).toEqual([]);
|
|
417
|
+
expect(completeToolDef?.description).toContain('signal that you have completed');
|
|
418
|
+
expect(output.result).toBe('Task completed successfully.');
|
|
419
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
254
420
|
});
|
|
255
|
-
it('should
|
|
256
|
-
const definition = createTestDefinition(
|
|
421
|
+
it('should error immediately if the model stops tools without calling complete_task (Protocol Violation)', async () => {
|
|
422
|
+
const definition = createTestDefinition();
|
|
257
423
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
258
|
-
// Turn 1: Model calls ls, but it fails
|
|
259
424
|
mockModelResponse([
|
|
260
|
-
{ name:
|
|
425
|
+
{ name: LS_TOOL_NAME, args: { path: '.' }, id: 'call1' },
|
|
261
426
|
]);
|
|
262
|
-
const errorMessage = 'Internal failure.';
|
|
263
427
|
mockExecuteToolCall.mockResolvedValueOnce({
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
428
|
+
status: 'success',
|
|
429
|
+
request: {
|
|
430
|
+
callId: 'call1',
|
|
431
|
+
name: LS_TOOL_NAME,
|
|
432
|
+
args: { path: '.' },
|
|
433
|
+
isClientInitiated: false,
|
|
434
|
+
prompt_id: 'test-prompt',
|
|
435
|
+
},
|
|
436
|
+
tool: {},
|
|
437
|
+
invocation: {},
|
|
438
|
+
response: {
|
|
439
|
+
callId: 'call1',
|
|
440
|
+
resultDisplay: 'ok',
|
|
441
|
+
responseParts: [
|
|
442
|
+
{
|
|
443
|
+
functionResponse: {
|
|
444
|
+
name: LS_TOOL_NAME,
|
|
445
|
+
response: {},
|
|
446
|
+
id: 'call1',
|
|
447
|
+
},
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
error: undefined,
|
|
451
|
+
errorType: undefined,
|
|
452
|
+
contentLength: undefined,
|
|
453
|
+
},
|
|
268
454
|
});
|
|
269
|
-
// Turn 2
|
|
270
|
-
mockModelResponse([]);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
455
|
+
// Turn 2 (protocol violation)
|
|
456
|
+
mockModelResponse([], 'I think I am done.');
|
|
457
|
+
// Turn 3 (recovery turn - also fails)
|
|
458
|
+
mockModelResponse([], 'I still give up.');
|
|
459
|
+
const output = await executor.run({ goal: 'Strict test' }, signal);
|
|
460
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
|
|
461
|
+
const expectedError = `Agent stopped calling tools but did not call '${TASK_COMPLETE_TOOL_NAME}'.`;
|
|
462
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL);
|
|
463
|
+
expect(output.result).toBe(expectedError);
|
|
464
|
+
// Telemetry check for error
|
|
465
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
|
|
466
|
+
terminate_reason: AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL,
|
|
467
|
+
}));
|
|
468
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
469
|
+
type: 'ERROR',
|
|
470
|
+
data: expect.objectContaining({
|
|
471
|
+
context: 'protocol_violation',
|
|
472
|
+
error: expectedError,
|
|
473
|
+
}),
|
|
474
|
+
}));
|
|
475
|
+
});
|
|
476
|
+
it('should report an error if complete_task is called with missing required arguments', async () => {
|
|
477
|
+
const definition = createTestDefinition();
|
|
478
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
479
|
+
// Turn 1: Missing arg
|
|
480
|
+
mockModelResponse([
|
|
481
|
+
{
|
|
482
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
483
|
+
args: { wrongArg: 'oops' },
|
|
484
|
+
id: 'call1',
|
|
485
|
+
},
|
|
486
|
+
]);
|
|
487
|
+
// Turn 2: Corrected
|
|
488
|
+
mockModelResponse([
|
|
489
|
+
{
|
|
490
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
491
|
+
args: { finalResult: 'Corrected result' },
|
|
492
|
+
id: 'call2',
|
|
493
|
+
},
|
|
494
|
+
]);
|
|
495
|
+
const output = await executor.run({ goal: 'Error test' }, signal);
|
|
496
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
497
|
+
const expectedError = "Missing required argument 'finalResult' for completion.";
|
|
274
498
|
expect(activities).toContainEqual(expect.objectContaining({
|
|
275
499
|
type: 'ERROR',
|
|
276
500
|
data: {
|
|
277
|
-
error: errorMessage,
|
|
278
501
|
context: 'tool_call',
|
|
279
|
-
name:
|
|
502
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
503
|
+
error: expectedError,
|
|
280
504
|
},
|
|
281
505
|
}));
|
|
282
|
-
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
expect(turn2Parts).
|
|
506
|
+
const turn2Params = getMockMessageParams(1);
|
|
507
|
+
const turn2Parts = turn2Params.message;
|
|
508
|
+
expect(turn2Parts).toBeDefined();
|
|
509
|
+
expect(turn2Parts).toHaveLength(1);
|
|
510
|
+
expect(turn2Parts[0]).toEqual(expect.objectContaining({
|
|
511
|
+
functionResponse: expect.objectContaining({
|
|
512
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
513
|
+
response: { error: expectedError },
|
|
514
|
+
id: 'call1',
|
|
515
|
+
}),
|
|
516
|
+
}));
|
|
517
|
+
expect(output.result).toBe('Corrected result');
|
|
518
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
519
|
+
});
|
|
520
|
+
it('should handle multiple calls to complete_task in the same turn (accept first, block rest)', async () => {
|
|
521
|
+
const definition = createTestDefinition([], {}, 'none');
|
|
522
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
523
|
+
// Turn 1: Duplicate calls
|
|
524
|
+
mockModelResponse([
|
|
525
|
+
{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call1' },
|
|
526
|
+
{ name: TASK_COMPLETE_TOOL_NAME, args: {}, id: 'call2' },
|
|
527
|
+
]);
|
|
528
|
+
const output = await executor.run({ goal: 'Dup test' }, signal);
|
|
529
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
|
|
530
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
531
|
+
const completions = activities.filter((a) => a.type === 'TOOL_CALL_END' &&
|
|
532
|
+
a.data['name'] === TASK_COMPLETE_TOOL_NAME);
|
|
533
|
+
const errors = activities.filter((a) => a.type === 'ERROR' && a.data['name'] === TASK_COMPLETE_TOOL_NAME);
|
|
534
|
+
expect(completions).toHaveLength(1);
|
|
535
|
+
expect(errors).toHaveLength(1);
|
|
536
|
+
expect(errors[0].data['error']).toContain('Task already marked complete in this turn');
|
|
537
|
+
});
|
|
538
|
+
it('should execute parallel tool calls and then complete', async () => {
|
|
539
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
540
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
541
|
+
const call1 = {
|
|
542
|
+
name: LS_TOOL_NAME,
|
|
543
|
+
args: { path: '/a' },
|
|
544
|
+
id: 'c1',
|
|
545
|
+
};
|
|
546
|
+
const call2 = {
|
|
547
|
+
name: LS_TOOL_NAME,
|
|
548
|
+
args: { path: '/b' },
|
|
549
|
+
id: 'c2',
|
|
550
|
+
};
|
|
551
|
+
// Turn 1: Parallel calls
|
|
552
|
+
mockModelResponse([call1, call2]);
|
|
553
|
+
// Concurrency mock
|
|
554
|
+
let callsStarted = 0;
|
|
555
|
+
let resolveCalls;
|
|
556
|
+
const bothStarted = new Promise((r) => {
|
|
557
|
+
resolveCalls = r;
|
|
558
|
+
});
|
|
559
|
+
mockExecuteToolCall.mockImplementation(async (_ctx, reqInfo) => {
|
|
560
|
+
callsStarted++;
|
|
561
|
+
if (callsStarted === 2)
|
|
562
|
+
resolveCalls();
|
|
563
|
+
await vi.advanceTimersByTimeAsync(100);
|
|
564
|
+
return {
|
|
565
|
+
status: 'success',
|
|
566
|
+
request: reqInfo,
|
|
567
|
+
tool: {},
|
|
568
|
+
invocation: {},
|
|
569
|
+
response: {
|
|
570
|
+
callId: reqInfo.callId,
|
|
571
|
+
resultDisplay: 'ok',
|
|
572
|
+
responseParts: [
|
|
573
|
+
{
|
|
574
|
+
functionResponse: {
|
|
575
|
+
name: reqInfo.name,
|
|
576
|
+
response: {},
|
|
577
|
+
id: reqInfo.callId,
|
|
578
|
+
},
|
|
579
|
+
},
|
|
580
|
+
],
|
|
581
|
+
error: undefined,
|
|
582
|
+
errorType: undefined,
|
|
583
|
+
contentLength: undefined,
|
|
584
|
+
},
|
|
585
|
+
};
|
|
586
|
+
});
|
|
587
|
+
// Turn 2: Completion
|
|
588
|
+
mockModelResponse([
|
|
286
589
|
{
|
|
287
|
-
|
|
590
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
591
|
+
args: { finalResult: 'done' },
|
|
592
|
+
id: 'c3',
|
|
288
593
|
},
|
|
289
594
|
]);
|
|
595
|
+
const runPromise = executor.run({ goal: 'Parallel' }, signal);
|
|
596
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
597
|
+
await bothStarted;
|
|
598
|
+
await vi.advanceTimersByTimeAsync(150);
|
|
599
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
600
|
+
const output = await runPromise;
|
|
601
|
+
expect(mockExecuteToolCall).toHaveBeenCalledTimes(2);
|
|
602
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
603
|
+
// Safe access to message parts
|
|
604
|
+
const turn2Params = getMockMessageParams(1);
|
|
605
|
+
const parts = turn2Params.message;
|
|
606
|
+
expect(parts).toBeDefined();
|
|
607
|
+
expect(parts).toHaveLength(2);
|
|
608
|
+
expect(parts).toEqual(expect.arrayContaining([
|
|
609
|
+
expect.objectContaining({
|
|
610
|
+
functionResponse: expect.objectContaining({ id: 'c1' }),
|
|
611
|
+
}),
|
|
612
|
+
expect.objectContaining({
|
|
613
|
+
functionResponse: expect.objectContaining({ id: 'c2' }),
|
|
614
|
+
}),
|
|
615
|
+
]));
|
|
290
616
|
});
|
|
291
|
-
it('SECURITY: should block
|
|
292
|
-
|
|
293
|
-
const definition = createTestDefinition([LSTool.Name]);
|
|
617
|
+
it('SECURITY: should block unauthorized tools and provide explicit failure to model', async () => {
|
|
618
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
294
619
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
295
|
-
// Turn 1: Model
|
|
296
|
-
|
|
620
|
+
// Turn 1: Model tries to use a tool not in its config
|
|
621
|
+
const badCallId = 'bad_call_1';
|
|
297
622
|
mockModelResponse([
|
|
298
623
|
{
|
|
299
|
-
name:
|
|
300
|
-
args: { path: '
|
|
301
|
-
id:
|
|
624
|
+
name: READ_FILE_TOOL_NAME,
|
|
625
|
+
args: { path: 'secret.txt' },
|
|
626
|
+
id: badCallId,
|
|
627
|
+
},
|
|
628
|
+
]);
|
|
629
|
+
// Turn 2: Model gives up and completes
|
|
630
|
+
mockModelResponse([
|
|
631
|
+
{
|
|
632
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
633
|
+
args: { finalResult: 'Could not read file.' },
|
|
634
|
+
id: 'c2',
|
|
302
635
|
},
|
|
303
636
|
]);
|
|
304
|
-
// Turn 2: Model stops
|
|
305
|
-
mockModelResponse([]);
|
|
306
|
-
// Extraction
|
|
307
|
-
mockModelResponse([], undefined, 'Done.');
|
|
308
637
|
const consoleWarnSpy = vi
|
|
309
|
-
.spyOn(
|
|
638
|
+
.spyOn(debugLogger, 'warn')
|
|
310
639
|
.mockImplementation(() => { });
|
|
311
|
-
await executor.run({ goal: '
|
|
312
|
-
// Verify
|
|
640
|
+
await executor.run({ goal: 'Sec test' }, signal);
|
|
641
|
+
// Verify external executor was not called (Security held)
|
|
313
642
|
expect(mockExecuteToolCall).not.toHaveBeenCalled();
|
|
314
|
-
|
|
643
|
+
// 2. Verify console warning
|
|
644
|
+
expect(consoleWarnSpy).toHaveBeenCalledWith(expect.stringContaining(`[AgentExecutor] Blocked call:`));
|
|
315
645
|
consoleWarnSpy.mockRestore();
|
|
316
|
-
// Verify
|
|
317
|
-
const
|
|
318
|
-
const
|
|
319
|
-
expect(
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
646
|
+
// Verify specific error was sent back to model
|
|
647
|
+
const turn2Params = getMockMessageParams(1);
|
|
648
|
+
const parts = turn2Params.message;
|
|
649
|
+
expect(parts).toBeDefined();
|
|
650
|
+
expect(parts[0]).toEqual(expect.objectContaining({
|
|
651
|
+
functionResponse: expect.objectContaining({
|
|
652
|
+
id: badCallId,
|
|
653
|
+
name: READ_FILE_TOOL_NAME,
|
|
654
|
+
response: {
|
|
655
|
+
error: expect.stringContaining('Unauthorized tool call'),
|
|
656
|
+
},
|
|
657
|
+
}),
|
|
658
|
+
}));
|
|
659
|
+
// Verify Activity Stream reported the error
|
|
660
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
661
|
+
type: 'ERROR',
|
|
662
|
+
data: expect.objectContaining({
|
|
663
|
+
context: 'tool_call_unauthorized',
|
|
664
|
+
name: READ_FILE_TOOL_NAME,
|
|
665
|
+
}),
|
|
666
|
+
}));
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
describe('Edge Cases and Error Handling', () => {
|
|
670
|
+
it('should report an error if complete_task output fails schema validation', async () => {
|
|
671
|
+
const definition = createTestDefinition([], {}, 'default', z.string().min(10));
|
|
672
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
673
|
+
// Turn 1: Invalid arg (too short)
|
|
674
|
+
mockModelResponse([
|
|
675
|
+
{
|
|
676
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
677
|
+
args: { finalResult: 'short' },
|
|
678
|
+
id: 'call1',
|
|
679
|
+
},
|
|
680
|
+
]);
|
|
681
|
+
// Turn 2: Corrected
|
|
682
|
+
mockModelResponse([
|
|
683
|
+
{
|
|
684
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
685
|
+
args: { finalResult: 'This is a much longer and valid result' },
|
|
686
|
+
id: 'call2',
|
|
687
|
+
},
|
|
688
|
+
]);
|
|
689
|
+
const output = await executor.run({ goal: 'Validation test' }, signal);
|
|
690
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
691
|
+
const expectedError = 'Output validation failed: {"formErrors":["String must contain at least 10 character(s)"],"fieldErrors":{}}';
|
|
692
|
+
// Check that the error was reported in the activity stream
|
|
693
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
694
|
+
type: 'ERROR',
|
|
695
|
+
data: {
|
|
696
|
+
context: 'tool_call',
|
|
697
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
698
|
+
error: expect.stringContaining('Output validation failed'),
|
|
699
|
+
},
|
|
700
|
+
}));
|
|
701
|
+
// Check that the error was sent back to the model for the next turn
|
|
702
|
+
const turn2Params = getMockMessageParams(1);
|
|
703
|
+
const turn2Parts = turn2Params.message;
|
|
704
|
+
expect(turn2Parts).toEqual([
|
|
705
|
+
expect.objectContaining({
|
|
706
|
+
functionResponse: expect.objectContaining({
|
|
707
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
708
|
+
response: { error: expectedError },
|
|
709
|
+
id: 'call1',
|
|
710
|
+
}),
|
|
711
|
+
}),
|
|
712
|
+
]);
|
|
713
|
+
// Check that the agent eventually succeeded
|
|
714
|
+
expect(output.result).toContain('This is a much longer and valid result');
|
|
715
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
716
|
+
});
|
|
717
|
+
it('should throw and log if GeminiChat creation fails', async () => {
|
|
718
|
+
const definition = createTestDefinition();
|
|
719
|
+
const initError = new Error('Chat creation failed');
|
|
720
|
+
MockedGeminiChat.mockImplementationOnce(() => {
|
|
721
|
+
throw initError;
|
|
325
722
|
});
|
|
723
|
+
// We expect the error to be thrown during the run, not creation
|
|
724
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
725
|
+
await expect(executor.run({ goal: 'test' }, signal)).rejects.toThrow(`Failed to create chat object: ${initError}`);
|
|
726
|
+
// Ensure the error was reported via the activity callback
|
|
727
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
728
|
+
type: 'ERROR',
|
|
729
|
+
data: expect.objectContaining({
|
|
730
|
+
error: `Error: Failed to create chat object: ${initError}`,
|
|
731
|
+
}),
|
|
732
|
+
}));
|
|
733
|
+
// Ensure the agent run was logged as a failure
|
|
734
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
|
|
735
|
+
terminate_reason: AgentTerminateMode.ERROR,
|
|
736
|
+
}));
|
|
737
|
+
});
|
|
738
|
+
it('should handle a failed tool call and feed the error to the model', async () => {
|
|
739
|
+
const definition = createTestDefinition([LS_TOOL_NAME]);
|
|
326
740
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
741
|
+
const toolErrorMessage = 'Tool failed spectacularly';
|
|
742
|
+
// Turn 1: Model calls a tool that will fail
|
|
743
|
+
mockModelResponse([
|
|
744
|
+
{ name: LS_TOOL_NAME, args: { path: '/fake' }, id: 'call1' },
|
|
745
|
+
]);
|
|
746
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
747
|
+
status: 'error',
|
|
748
|
+
request: {
|
|
749
|
+
callId: 'call1',
|
|
750
|
+
name: LS_TOOL_NAME,
|
|
751
|
+
args: { path: '/fake' },
|
|
752
|
+
isClientInitiated: false,
|
|
753
|
+
prompt_id: 'test-prompt',
|
|
754
|
+
},
|
|
755
|
+
tool: {},
|
|
756
|
+
invocation: {},
|
|
757
|
+
response: {
|
|
758
|
+
callId: 'call1',
|
|
759
|
+
resultDisplay: '',
|
|
760
|
+
responseParts: [
|
|
761
|
+
{
|
|
762
|
+
functionResponse: {
|
|
763
|
+
name: LS_TOOL_NAME,
|
|
764
|
+
response: { error: toolErrorMessage },
|
|
765
|
+
id: 'call1',
|
|
766
|
+
},
|
|
767
|
+
},
|
|
768
|
+
],
|
|
769
|
+
error: {
|
|
770
|
+
type: 'ToolError',
|
|
771
|
+
message: toolErrorMessage,
|
|
772
|
+
},
|
|
773
|
+
errorType: 'ToolError',
|
|
774
|
+
contentLength: 0,
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
// Turn 2: Model sees the error and completes
|
|
778
|
+
mockModelResponse([
|
|
779
|
+
{
|
|
780
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
781
|
+
args: { finalResult: 'Aborted due to tool failure.' },
|
|
782
|
+
id: 'call2',
|
|
783
|
+
},
|
|
784
|
+
]);
|
|
785
|
+
const output = await executor.run({ goal: 'Tool failure test' }, signal);
|
|
786
|
+
expect(mockExecuteToolCall).toHaveBeenCalledTimes(1);
|
|
787
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
788
|
+
// Verify the error was reported in the activity stream
|
|
789
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
790
|
+
type: 'ERROR',
|
|
791
|
+
data: {
|
|
792
|
+
context: 'tool_call',
|
|
793
|
+
name: LS_TOOL_NAME,
|
|
794
|
+
error: toolErrorMessage,
|
|
795
|
+
},
|
|
796
|
+
}));
|
|
797
|
+
// Verify the error was sent back to the model
|
|
798
|
+
const turn2Params = getMockMessageParams(1);
|
|
799
|
+
const parts = turn2Params.message;
|
|
800
|
+
expect(parts).toEqual([
|
|
801
|
+
expect.objectContaining({
|
|
802
|
+
functionResponse: expect.objectContaining({
|
|
803
|
+
name: LS_TOOL_NAME,
|
|
804
|
+
id: 'call1',
|
|
805
|
+
response: {
|
|
806
|
+
error: toolErrorMessage,
|
|
807
|
+
},
|
|
808
|
+
}),
|
|
809
|
+
}),
|
|
810
|
+
]);
|
|
811
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
812
|
+
expect(output.result).toBe('Aborted due to tool failure.');
|
|
340
813
|
});
|
|
341
814
|
});
|
|
342
815
|
describe('run (Termination Conditions)', () => {
|
|
343
|
-
const
|
|
344
|
-
mockModelResponse([{ name:
|
|
345
|
-
mockExecuteToolCall.
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
816
|
+
const mockWorkResponse = (id) => {
|
|
817
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
|
818
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
819
|
+
status: 'success',
|
|
820
|
+
request: {
|
|
821
|
+
callId: id,
|
|
822
|
+
name: LS_TOOL_NAME,
|
|
823
|
+
args: { path: '.' },
|
|
824
|
+
isClientInitiated: false,
|
|
825
|
+
prompt_id: 'test-prompt',
|
|
826
|
+
},
|
|
827
|
+
tool: {},
|
|
828
|
+
invocation: {},
|
|
829
|
+
response: {
|
|
830
|
+
callId: id,
|
|
831
|
+
resultDisplay: 'ok',
|
|
832
|
+
responseParts: [
|
|
833
|
+
{ functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
|
|
834
|
+
],
|
|
835
|
+
error: undefined,
|
|
836
|
+
errorType: undefined,
|
|
837
|
+
contentLength: undefined,
|
|
838
|
+
},
|
|
352
839
|
});
|
|
353
840
|
};
|
|
354
841
|
it('should terminate when max_turns is reached', async () => {
|
|
355
|
-
const
|
|
356
|
-
const definition = createTestDefinition([
|
|
357
|
-
max_turns:
|
|
842
|
+
const MAX = 2;
|
|
843
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
844
|
+
max_turns: MAX,
|
|
358
845
|
});
|
|
359
|
-
const executor = await AgentExecutor.create(definition, mockConfig
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
const output = await executor.run({ goal: '
|
|
846
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
847
|
+
mockWorkResponse('t1');
|
|
848
|
+
mockWorkResponse('t2');
|
|
849
|
+
// Recovery turn
|
|
850
|
+
mockModelResponse([], 'I give up');
|
|
851
|
+
const output = await executor.run({ goal: 'Turns test' }, signal);
|
|
365
852
|
expect(output.terminate_reason).toBe(AgentTerminateMode.MAX_TURNS);
|
|
366
|
-
expect(mockSendMessageStream).toHaveBeenCalledTimes(
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
const definition = createTestDefinition([LSTool.Name], {
|
|
372
|
-
max_time_minutes: 5,
|
|
373
|
-
max_turns: 100,
|
|
853
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX + 1);
|
|
854
|
+
});
|
|
855
|
+
it('should terminate with TIMEOUT if a model call takes too long', async () => {
|
|
856
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
857
|
+
max_time_minutes: 0.5, // 30 seconds
|
|
374
858
|
});
|
|
375
859
|
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
//
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
860
|
+
// Mock a model call that is interruptible by an abort signal.
|
|
861
|
+
mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
|
|
862
|
+
// eslint-disable-next-line require-yield
|
|
863
|
+
(async function* () {
|
|
864
|
+
await new Promise((resolve) => {
|
|
865
|
+
// This promise resolves when aborted, ending the generator.
|
|
866
|
+
signal?.addEventListener('abort', () => {
|
|
867
|
+
resolve();
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
})());
|
|
871
|
+
// Recovery turn
|
|
872
|
+
mockModelResponse([], 'I give up');
|
|
873
|
+
const runPromise = executor.run({ goal: 'Timeout test' }, signal);
|
|
874
|
+
// Advance time past the timeout to trigger the abort.
|
|
875
|
+
await vi.advanceTimersByTimeAsync(31 * 1000);
|
|
876
|
+
const output = await runPromise;
|
|
877
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
|
|
878
|
+
expect(output.result).toContain('Agent timed out after 0.5 minutes.');
|
|
879
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
880
|
+
// Verify activity stream reported the timeout
|
|
881
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
882
|
+
type: 'ERROR',
|
|
883
|
+
data: expect.objectContaining({
|
|
884
|
+
context: 'timeout',
|
|
885
|
+
error: 'Agent timed out after 0.5 minutes.',
|
|
886
|
+
}),
|
|
887
|
+
}));
|
|
888
|
+
// Verify telemetry
|
|
889
|
+
expect(mockedLogAgentFinish).toHaveBeenCalledWith(mockConfig, expect.objectContaining({
|
|
890
|
+
terminate_reason: AgentTerminateMode.TIMEOUT,
|
|
891
|
+
}));
|
|
892
|
+
});
|
|
893
|
+
it('should terminate with TIMEOUT if a tool call takes too long', async () => {
|
|
894
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
895
|
+
max_time_minutes: 1,
|
|
896
|
+
});
|
|
897
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
898
|
+
mockModelResponse([
|
|
899
|
+
{ name: LS_TOOL_NAME, args: { path: '.' }, id: 't1' },
|
|
900
|
+
]);
|
|
901
|
+
// Long running tool
|
|
902
|
+
mockExecuteToolCall.mockImplementationOnce(async (_ctx, reqInfo) => {
|
|
903
|
+
await vi.advanceTimersByTimeAsync(61 * 1000);
|
|
382
904
|
return {
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
905
|
+
status: 'success',
|
|
906
|
+
request: reqInfo,
|
|
907
|
+
tool: {},
|
|
908
|
+
invocation: {},
|
|
909
|
+
response: {
|
|
910
|
+
callId: 't1',
|
|
911
|
+
resultDisplay: 'ok',
|
|
912
|
+
responseParts: [],
|
|
913
|
+
error: undefined,
|
|
914
|
+
errorType: undefined,
|
|
915
|
+
contentLength: undefined,
|
|
916
|
+
},
|
|
391
917
|
};
|
|
392
918
|
});
|
|
393
|
-
|
|
919
|
+
// Recovery turn
|
|
920
|
+
mockModelResponse([], 'I give up');
|
|
921
|
+
const output = await executor.run({ goal: 'Timeout test' }, signal);
|
|
394
922
|
expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
|
|
395
|
-
|
|
396
|
-
expect(mockSendMessageStream).toHaveBeenCalledTimes(1);
|
|
923
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
397
924
|
});
|
|
398
|
-
it('should terminate when AbortSignal is triggered
|
|
925
|
+
it('should terminate when AbortSignal is triggered', async () => {
|
|
399
926
|
const definition = createTestDefinition();
|
|
400
|
-
const executor = await AgentExecutor.create(definition, mockConfig
|
|
401
|
-
|
|
402
|
-
mockSendMessageStream.mockImplementation(async () => (async function* () {
|
|
403
|
-
// Yield the first chunk
|
|
927
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
928
|
+
mockSendMessageStream.mockImplementationOnce(async () => (async function* () {
|
|
404
929
|
yield {
|
|
405
930
|
type: StreamEventType.CHUNK,
|
|
406
931
|
value: createMockResponseChunk([
|
|
407
|
-
{ text: '
|
|
932
|
+
{ text: 'Thinking...', thought: true },
|
|
408
933
|
]),
|
|
409
934
|
};
|
|
410
|
-
// Simulate abort happening mid-stream
|
|
411
935
|
abortController.abort();
|
|
412
|
-
// The loop in callModel should break immediately due to signal check.
|
|
413
936
|
})());
|
|
414
|
-
const output = await executor.run({ goal: '
|
|
937
|
+
const output = await executor.run({ goal: 'Abort test' }, signal);
|
|
415
938
|
expect(output.terminate_reason).toBe(AgentTerminateMode.ABORTED);
|
|
416
939
|
});
|
|
417
940
|
});
|
|
941
|
+
describe('run (Recovery Turns)', () => {
|
|
942
|
+
const mockWorkResponse = (id) => {
|
|
943
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
|
944
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
945
|
+
status: 'success',
|
|
946
|
+
request: {
|
|
947
|
+
callId: id,
|
|
948
|
+
name: LS_TOOL_NAME,
|
|
949
|
+
args: { path: '.' },
|
|
950
|
+
isClientInitiated: false,
|
|
951
|
+
prompt_id: 'test-prompt',
|
|
952
|
+
},
|
|
953
|
+
tool: {},
|
|
954
|
+
invocation: {},
|
|
955
|
+
response: {
|
|
956
|
+
callId: id,
|
|
957
|
+
resultDisplay: 'ok',
|
|
958
|
+
responseParts: [
|
|
959
|
+
{ functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
|
|
960
|
+
],
|
|
961
|
+
error: undefined,
|
|
962
|
+
errorType: undefined,
|
|
963
|
+
contentLength: undefined,
|
|
964
|
+
},
|
|
965
|
+
});
|
|
966
|
+
};
|
|
967
|
+
it('should recover successfully if complete_task is called during the grace turn after MAX_TURNS', async () => {
|
|
968
|
+
const MAX = 1;
|
|
969
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
970
|
+
max_turns: MAX,
|
|
971
|
+
});
|
|
972
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
973
|
+
// Turn 1 (hits max_turns)
|
|
974
|
+
mockWorkResponse('t1');
|
|
975
|
+
// Recovery Turn (succeeds)
|
|
976
|
+
mockModelResponse([
|
|
977
|
+
{
|
|
978
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
979
|
+
args: { finalResult: 'Recovered!' },
|
|
980
|
+
id: 't2',
|
|
981
|
+
},
|
|
982
|
+
], 'Recovering from max turns');
|
|
983
|
+
const output = await executor.run({ goal: 'Turns recovery' }, signal);
|
|
984
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
985
|
+
expect(output.result).toBe('Recovered!');
|
|
986
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX + 1); // 1 regular + 1 recovery
|
|
987
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
988
|
+
type: 'THOUGHT_CHUNK',
|
|
989
|
+
data: {
|
|
990
|
+
text: 'Execution limit reached (MAX_TURNS). Attempting one final recovery turn with a grace period.',
|
|
991
|
+
},
|
|
992
|
+
}));
|
|
993
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
994
|
+
type: 'THOUGHT_CHUNK',
|
|
995
|
+
data: { text: 'Graceful recovery succeeded.' },
|
|
996
|
+
}));
|
|
997
|
+
});
|
|
998
|
+
it('should fail if complete_task is NOT called during the grace turn after MAX_TURNS', async () => {
|
|
999
|
+
const MAX = 1;
|
|
1000
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
1001
|
+
max_turns: MAX,
|
|
1002
|
+
});
|
|
1003
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1004
|
+
// Turn 1 (hits max_turns)
|
|
1005
|
+
mockWorkResponse('t1');
|
|
1006
|
+
// Recovery Turn (fails by calling no tools)
|
|
1007
|
+
mockModelResponse([], 'I give up again.');
|
|
1008
|
+
const output = await executor.run({ goal: 'Turns recovery fail' }, signal);
|
|
1009
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.MAX_TURNS);
|
|
1010
|
+
expect(output.result).toContain('Agent reached max turns limit');
|
|
1011
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(MAX + 1);
|
|
1012
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
1013
|
+
type: 'ERROR',
|
|
1014
|
+
data: expect.objectContaining({
|
|
1015
|
+
context: 'recovery_turn',
|
|
1016
|
+
error: 'Graceful recovery attempt failed. Reason: stop',
|
|
1017
|
+
}),
|
|
1018
|
+
}));
|
|
1019
|
+
});
|
|
1020
|
+
it('should recover successfully from a protocol violation (no complete_task)', async () => {
|
|
1021
|
+
const definition = createTestDefinition();
|
|
1022
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1023
|
+
// Turn 1: Normal work
|
|
1024
|
+
mockWorkResponse('t1');
|
|
1025
|
+
// Turn 2: Protocol violation (no tool calls)
|
|
1026
|
+
mockModelResponse([], 'I think I am done, but I forgot the right tool.');
|
|
1027
|
+
// Turn 3: Recovery turn (succeeds)
|
|
1028
|
+
mockModelResponse([
|
|
1029
|
+
{
|
|
1030
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1031
|
+
args: { finalResult: 'Recovered from violation!' },
|
|
1032
|
+
id: 't3',
|
|
1033
|
+
},
|
|
1034
|
+
], 'My mistake, here is the completion.');
|
|
1035
|
+
const output = await executor.run({ goal: 'Violation recovery' }, signal);
|
|
1036
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
|
|
1037
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
1038
|
+
expect(output.result).toBe('Recovered from violation!');
|
|
1039
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
1040
|
+
type: 'THOUGHT_CHUNK',
|
|
1041
|
+
data: {
|
|
1042
|
+
text: 'Execution limit reached (ERROR_NO_COMPLETE_TASK_CALL). Attempting one final recovery turn with a grace period.',
|
|
1043
|
+
},
|
|
1044
|
+
}));
|
|
1045
|
+
});
|
|
1046
|
+
it('should fail recovery from a protocol violation if it violates again', async () => {
|
|
1047
|
+
const definition = createTestDefinition();
|
|
1048
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1049
|
+
// Turn 1: Normal work
|
|
1050
|
+
mockWorkResponse('t1');
|
|
1051
|
+
// Turn 2: Protocol violation (no tool calls)
|
|
1052
|
+
mockModelResponse([], 'I think I am done, but I forgot the right tool.');
|
|
1053
|
+
// Turn 3: Recovery turn (fails again)
|
|
1054
|
+
mockModelResponse([], 'I still dont know what to do.');
|
|
1055
|
+
const output = await executor.run({ goal: 'Violation recovery fail' }, signal);
|
|
1056
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(3);
|
|
1057
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.ERROR_NO_COMPLETE_TASK_CALL);
|
|
1058
|
+
expect(output.result).toContain(`Agent stopped calling tools but did not call '${TASK_COMPLETE_TOOL_NAME}'`);
|
|
1059
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
1060
|
+
type: 'ERROR',
|
|
1061
|
+
data: expect.objectContaining({
|
|
1062
|
+
context: 'recovery_turn',
|
|
1063
|
+
error: 'Graceful recovery attempt failed. Reason: stop',
|
|
1064
|
+
}),
|
|
1065
|
+
}));
|
|
1066
|
+
});
|
|
1067
|
+
it('should recover successfully from a TIMEOUT', async () => {
|
|
1068
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
1069
|
+
max_time_minutes: 0.5, // 30 seconds
|
|
1070
|
+
});
|
|
1071
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1072
|
+
// Mock a model call that gets interrupted by the timeout.
|
|
1073
|
+
mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
|
|
1074
|
+
// eslint-disable-next-line require-yield
|
|
1075
|
+
(async function* () {
|
|
1076
|
+
// This promise never resolves, it waits for abort.
|
|
1077
|
+
await new Promise((resolve) => {
|
|
1078
|
+
signal?.addEventListener('abort', () => resolve());
|
|
1079
|
+
});
|
|
1080
|
+
})());
|
|
1081
|
+
// Recovery turn (succeeds)
|
|
1082
|
+
mockModelResponse([
|
|
1083
|
+
{
|
|
1084
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1085
|
+
args: { finalResult: 'Recovered from timeout!' },
|
|
1086
|
+
id: 't2',
|
|
1087
|
+
},
|
|
1088
|
+
], 'Apologies for the delay, finishing up.');
|
|
1089
|
+
const runPromise = executor.run({ goal: 'Timeout recovery' }, signal);
|
|
1090
|
+
// Advance time past the timeout to trigger the abort and recovery.
|
|
1091
|
+
await vi.advanceTimersByTimeAsync(31 * 1000);
|
|
1092
|
+
const output = await runPromise;
|
|
1093
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2); // 1 failed + 1 recovery
|
|
1094
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.GOAL);
|
|
1095
|
+
expect(output.result).toBe('Recovered from timeout!');
|
|
1096
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
1097
|
+
type: 'THOUGHT_CHUNK',
|
|
1098
|
+
data: {
|
|
1099
|
+
text: 'Execution limit reached (TIMEOUT). Attempting one final recovery turn with a grace period.',
|
|
1100
|
+
},
|
|
1101
|
+
}));
|
|
1102
|
+
});
|
|
1103
|
+
it('should fail recovery from a TIMEOUT if the grace period also times out', async () => {
|
|
1104
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
1105
|
+
max_time_minutes: 0.5, // 30 seconds
|
|
1106
|
+
});
|
|
1107
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1108
|
+
mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
|
|
1109
|
+
// eslint-disable-next-line require-yield
|
|
1110
|
+
(async function* () {
|
|
1111
|
+
await new Promise((resolve) => signal?.addEventListener('abort', () => resolve()));
|
|
1112
|
+
})());
|
|
1113
|
+
// Mock the recovery call to also be long-running
|
|
1114
|
+
mockSendMessageStream.mockImplementationOnce(async (_key, _message, _promptId, signal) =>
|
|
1115
|
+
// eslint-disable-next-line require-yield
|
|
1116
|
+
(async function* () {
|
|
1117
|
+
await new Promise((resolve) => signal?.addEventListener('abort', () => resolve()));
|
|
1118
|
+
})());
|
|
1119
|
+
const runPromise = executor.run({ goal: 'Timeout recovery fail' }, signal);
|
|
1120
|
+
// 1. Trigger the main timeout
|
|
1121
|
+
await vi.advanceTimersByTimeAsync(31 * 1000);
|
|
1122
|
+
// 2. Let microtasks run (start recovery turn)
|
|
1123
|
+
await vi.advanceTimersByTimeAsync(1);
|
|
1124
|
+
// 3. Trigger the grace period timeout (60s)
|
|
1125
|
+
await vi.advanceTimersByTimeAsync(61 * 1000);
|
|
1126
|
+
const output = await runPromise;
|
|
1127
|
+
expect(mockSendMessageStream).toHaveBeenCalledTimes(2);
|
|
1128
|
+
expect(output.terminate_reason).toBe(AgentTerminateMode.TIMEOUT);
|
|
1129
|
+
expect(output.result).toContain('Agent timed out after 0.5 minutes.');
|
|
1130
|
+
expect(activities).toContainEqual(expect.objectContaining({
|
|
1131
|
+
type: 'ERROR',
|
|
1132
|
+
data: expect.objectContaining({
|
|
1133
|
+
context: 'recovery_turn',
|
|
1134
|
+
error: 'Graceful recovery attempt failed. Reason: stop',
|
|
1135
|
+
}),
|
|
1136
|
+
}));
|
|
1137
|
+
});
|
|
1138
|
+
});
|
|
1139
|
+
describe('Telemetry and Logging', () => {
|
|
1140
|
+
const mockWorkResponse = (id) => {
|
|
1141
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
|
1142
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
1143
|
+
status: 'success',
|
|
1144
|
+
request: {
|
|
1145
|
+
callId: id,
|
|
1146
|
+
name: LS_TOOL_NAME,
|
|
1147
|
+
args: { path: '.' },
|
|
1148
|
+
isClientInitiated: false,
|
|
1149
|
+
prompt_id: 'test-prompt',
|
|
1150
|
+
},
|
|
1151
|
+
tool: {},
|
|
1152
|
+
invocation: {},
|
|
1153
|
+
response: {
|
|
1154
|
+
callId: id,
|
|
1155
|
+
resultDisplay: 'ok',
|
|
1156
|
+
responseParts: [
|
|
1157
|
+
{ functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
|
|
1158
|
+
],
|
|
1159
|
+
error: undefined,
|
|
1160
|
+
errorType: undefined,
|
|
1161
|
+
contentLength: undefined,
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
};
|
|
1165
|
+
beforeEach(() => {
|
|
1166
|
+
mockedLogRecoveryAttempt.mockClear();
|
|
1167
|
+
});
|
|
1168
|
+
it('should log a RecoveryAttemptEvent when a recoverable error occurs and recovery fails', async () => {
|
|
1169
|
+
const MAX = 1;
|
|
1170
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
1171
|
+
max_turns: MAX,
|
|
1172
|
+
});
|
|
1173
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
1174
|
+
// Turn 1 (hits max_turns)
|
|
1175
|
+
mockWorkResponse('t1');
|
|
1176
|
+
// Recovery Turn (fails by calling no tools)
|
|
1177
|
+
mockModelResponse([], 'I give up again.');
|
|
1178
|
+
await executor.run({ goal: 'Turns recovery fail' }, signal);
|
|
1179
|
+
expect(mockedLogRecoveryAttempt).toHaveBeenCalledTimes(1);
|
|
1180
|
+
const recoveryEvent = mockedLogRecoveryAttempt.mock.calls[0][1];
|
|
1181
|
+
expect(recoveryEvent).toBeInstanceOf(RecoveryAttemptEvent);
|
|
1182
|
+
expect(recoveryEvent.agent_name).toBe(definition.name);
|
|
1183
|
+
expect(recoveryEvent.reason).toBe(AgentTerminateMode.MAX_TURNS);
|
|
1184
|
+
expect(recoveryEvent.success).toBe(false);
|
|
1185
|
+
expect(recoveryEvent.turn_count).toBe(1);
|
|
1186
|
+
expect(recoveryEvent.duration_ms).toBeGreaterThanOrEqual(0);
|
|
1187
|
+
});
|
|
1188
|
+
it('should log a successful RecoveryAttemptEvent when recovery succeeds', async () => {
|
|
1189
|
+
const MAX = 1;
|
|
1190
|
+
const definition = createTestDefinition([LS_TOOL_NAME], {
|
|
1191
|
+
max_turns: MAX,
|
|
1192
|
+
});
|
|
1193
|
+
const executor = await AgentExecutor.create(definition, mockConfig);
|
|
1194
|
+
// Turn 1 (hits max_turns)
|
|
1195
|
+
mockWorkResponse('t1');
|
|
1196
|
+
// Recovery Turn (succeeds)
|
|
1197
|
+
mockModelResponse([
|
|
1198
|
+
{
|
|
1199
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1200
|
+
args: { finalResult: 'Recovered!' },
|
|
1201
|
+
id: 't2',
|
|
1202
|
+
},
|
|
1203
|
+
], 'Recovering from max turns');
|
|
1204
|
+
await executor.run({ goal: 'Turns recovery success' }, signal);
|
|
1205
|
+
expect(mockedLogRecoveryAttempt).toHaveBeenCalledTimes(1);
|
|
1206
|
+
const recoveryEvent = mockedLogRecoveryAttempt.mock.calls[0][1];
|
|
1207
|
+
expect(recoveryEvent).toBeInstanceOf(RecoveryAttemptEvent);
|
|
1208
|
+
expect(recoveryEvent.success).toBe(true);
|
|
1209
|
+
expect(recoveryEvent.reason).toBe(AgentTerminateMode.MAX_TURNS);
|
|
1210
|
+
});
|
|
1211
|
+
});
|
|
1212
|
+
describe('Chat Compression', () => {
|
|
1213
|
+
const mockWorkResponse = (id) => {
|
|
1214
|
+
mockModelResponse([{ name: LS_TOOL_NAME, args: { path: '.' }, id }]);
|
|
1215
|
+
mockExecuteToolCall.mockResolvedValueOnce({
|
|
1216
|
+
status: 'success',
|
|
1217
|
+
request: {
|
|
1218
|
+
callId: id,
|
|
1219
|
+
name: LS_TOOL_NAME,
|
|
1220
|
+
args: { path: '.' },
|
|
1221
|
+
isClientInitiated: false,
|
|
1222
|
+
prompt_id: 'test-prompt',
|
|
1223
|
+
},
|
|
1224
|
+
tool: {},
|
|
1225
|
+
invocation: {},
|
|
1226
|
+
response: {
|
|
1227
|
+
callId: id,
|
|
1228
|
+
resultDisplay: 'ok',
|
|
1229
|
+
responseParts: [
|
|
1230
|
+
{ functionResponse: { name: LS_TOOL_NAME, response: {}, id } },
|
|
1231
|
+
],
|
|
1232
|
+
error: undefined,
|
|
1233
|
+
errorType: undefined,
|
|
1234
|
+
contentLength: undefined,
|
|
1235
|
+
},
|
|
1236
|
+
});
|
|
1237
|
+
};
|
|
1238
|
+
it('should attempt to compress chat history on each turn', async () => {
|
|
1239
|
+
const definition = createTestDefinition();
|
|
1240
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1241
|
+
// Mock compression to do nothing
|
|
1242
|
+
mockCompress.mockResolvedValue({
|
|
1243
|
+
newHistory: null,
|
|
1244
|
+
info: { compressionStatus: CompressionStatus.NOOP },
|
|
1245
|
+
});
|
|
1246
|
+
// Turn 1
|
|
1247
|
+
mockWorkResponse('t1');
|
|
1248
|
+
// Turn 2: Complete
|
|
1249
|
+
mockModelResponse([
|
|
1250
|
+
{
|
|
1251
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1252
|
+
args: { finalResult: 'Done' },
|
|
1253
|
+
id: 'call2',
|
|
1254
|
+
},
|
|
1255
|
+
], 'T2');
|
|
1256
|
+
await executor.run({ goal: 'Compress test' }, signal);
|
|
1257
|
+
expect(mockCompress).toHaveBeenCalledTimes(2);
|
|
1258
|
+
});
|
|
1259
|
+
it('should update chat history when compression is successful', async () => {
|
|
1260
|
+
const definition = createTestDefinition();
|
|
1261
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1262
|
+
const compressedHistory = [
|
|
1263
|
+
{ role: 'user', parts: [{ text: 'compressed' }] },
|
|
1264
|
+
];
|
|
1265
|
+
mockCompress.mockResolvedValue({
|
|
1266
|
+
newHistory: compressedHistory,
|
|
1267
|
+
info: { compressionStatus: CompressionStatus.COMPRESSED },
|
|
1268
|
+
});
|
|
1269
|
+
// Turn 1: Complete
|
|
1270
|
+
mockModelResponse([
|
|
1271
|
+
{
|
|
1272
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1273
|
+
args: { finalResult: 'Done' },
|
|
1274
|
+
id: 'call1',
|
|
1275
|
+
},
|
|
1276
|
+
], 'T1');
|
|
1277
|
+
await executor.run({ goal: 'Compress success' }, signal);
|
|
1278
|
+
expect(mockCompress).toHaveBeenCalledTimes(1);
|
|
1279
|
+
expect(mockSetHistory).toHaveBeenCalledTimes(1);
|
|
1280
|
+
expect(mockSetHistory).toHaveBeenCalledWith(compressedHistory);
|
|
1281
|
+
});
|
|
1282
|
+
it('should pass hasFailedCompressionAttempt=true to compression after a failure', async () => {
|
|
1283
|
+
const definition = createTestDefinition();
|
|
1284
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1285
|
+
// First call fails
|
|
1286
|
+
mockCompress.mockResolvedValueOnce({
|
|
1287
|
+
newHistory: null,
|
|
1288
|
+
info: {
|
|
1289
|
+
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
1290
|
+
},
|
|
1291
|
+
});
|
|
1292
|
+
// Second call is neutral
|
|
1293
|
+
mockCompress.mockResolvedValueOnce({
|
|
1294
|
+
newHistory: null,
|
|
1295
|
+
info: { compressionStatus: CompressionStatus.NOOP },
|
|
1296
|
+
});
|
|
1297
|
+
// Turn 1
|
|
1298
|
+
mockWorkResponse('t1');
|
|
1299
|
+
// Turn 2: Complete
|
|
1300
|
+
mockModelResponse([
|
|
1301
|
+
{
|
|
1302
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1303
|
+
args: { finalResult: 'Done' },
|
|
1304
|
+
id: 't2',
|
|
1305
|
+
},
|
|
1306
|
+
], 'T2');
|
|
1307
|
+
await executor.run({ goal: 'Compress fail' }, signal);
|
|
1308
|
+
expect(mockCompress).toHaveBeenCalledTimes(2);
|
|
1309
|
+
// First call, hasFailedCompressionAttempt is false
|
|
1310
|
+
expect(mockCompress.mock.calls[0][5]).toBe(false);
|
|
1311
|
+
// Second call, hasFailedCompressionAttempt is true
|
|
1312
|
+
expect(mockCompress.mock.calls[1][5]).toBe(true);
|
|
1313
|
+
});
|
|
1314
|
+
it('should reset hasFailedCompressionAttempt flag after a successful compression', async () => {
|
|
1315
|
+
const definition = createTestDefinition();
|
|
1316
|
+
const executor = await AgentExecutor.create(definition, mockConfig, onActivity);
|
|
1317
|
+
const compressedHistory = [
|
|
1318
|
+
{ role: 'user', parts: [{ text: 'compressed' }] },
|
|
1319
|
+
];
|
|
1320
|
+
// Turn 1: Fails
|
|
1321
|
+
mockCompress.mockResolvedValueOnce({
|
|
1322
|
+
newHistory: null,
|
|
1323
|
+
info: {
|
|
1324
|
+
compressionStatus: CompressionStatus.COMPRESSION_FAILED_INFLATED_TOKEN_COUNT,
|
|
1325
|
+
},
|
|
1326
|
+
});
|
|
1327
|
+
// Turn 2: Succeeds
|
|
1328
|
+
mockCompress.mockResolvedValueOnce({
|
|
1329
|
+
newHistory: compressedHistory,
|
|
1330
|
+
info: { compressionStatus: CompressionStatus.COMPRESSED },
|
|
1331
|
+
});
|
|
1332
|
+
// Turn 3: Neutral
|
|
1333
|
+
mockCompress.mockResolvedValueOnce({
|
|
1334
|
+
newHistory: null,
|
|
1335
|
+
info: { compressionStatus: CompressionStatus.NOOP },
|
|
1336
|
+
});
|
|
1337
|
+
// Turn 1
|
|
1338
|
+
mockWorkResponse('t1');
|
|
1339
|
+
// Turn 2
|
|
1340
|
+
mockWorkResponse('t2');
|
|
1341
|
+
// Turn 3: Complete
|
|
1342
|
+
mockModelResponse([
|
|
1343
|
+
{
|
|
1344
|
+
name: TASK_COMPLETE_TOOL_NAME,
|
|
1345
|
+
args: { finalResult: 'Done' },
|
|
1346
|
+
id: 't3',
|
|
1347
|
+
},
|
|
1348
|
+
], 'T3');
|
|
1349
|
+
await executor.run({ goal: 'Compress reset' }, signal);
|
|
1350
|
+
expect(mockCompress).toHaveBeenCalledTimes(3);
|
|
1351
|
+
// Call 1: hasFailed... is false
|
|
1352
|
+
expect(mockCompress.mock.calls[0][5]).toBe(false);
|
|
1353
|
+
// Call 2: hasFailed... is true
|
|
1354
|
+
expect(mockCompress.mock.calls[1][5]).toBe(true);
|
|
1355
|
+
// Call 3: hasFailed... is false again
|
|
1356
|
+
expect(mockCompress.mock.calls[2][5]).toBe(false);
|
|
1357
|
+
expect(mockSetHistory).toHaveBeenCalledTimes(1);
|
|
1358
|
+
expect(mockSetHistory).toHaveBeenCalledWith(compressedHistory);
|
|
1359
|
+
});
|
|
1360
|
+
});
|
|
418
1361
|
});
|
|
419
1362
|
//# sourceMappingURL=executor.test.js.map
|