@google/gemini-cli-core 0.42.0 → 0.43.0-preview.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/docs/changelogs/index.md +14 -0
- package/dist/docs/changelogs/latest.md +108 -166
- package/dist/docs/changelogs/preview.md +227 -103
- package/dist/docs/cli/auto-memory.md +60 -38
- package/dist/docs/cli/settings.md +1 -1
- package/dist/docs/cli/tutorials/memory-management.md +1 -1
- package/dist/docs/extensions/releasing.md +58 -24
- package/dist/docs/reference/configuration.md +14 -1
- package/dist/docs/reference/keyboard-shortcuts.md +23 -0
- package/dist/google-gemini-cli-core-0.43.0-preview.0.tgz +0 -0
- package/dist/src/agent/content-utils.js +2 -0
- package/dist/src/agent/content-utils.js.map +1 -1
- package/dist/src/agent/content-utils.test.js +5 -1
- package/dist/src/agent/content-utils.test.js.map +1 -1
- package/dist/src/agent/event-translator.js +7 -6
- package/dist/src/agent/event-translator.js.map +1 -1
- package/dist/src/agent/legacy-agent-session.js +4 -0
- package/dist/src/agent/legacy-agent-session.js.map +1 -1
- package/dist/src/agent/legacy-agent-session.test.js +9 -1
- package/dist/src/agent/legacy-agent-session.test.js.map +1 -1
- package/dist/src/agent/tool-display-utils.d.ts +3 -2
- package/dist/src/agent/tool-display-utils.js +3 -2
- package/dist/src/agent/tool-display-utils.js.map +1 -1
- package/dist/src/agent/types.d.ts +33 -3
- package/dist/src/agents/a2aUtils.d.ts +1 -1
- package/dist/src/agents/a2aUtils.js +4 -3
- package/dist/src/agents/a2aUtils.js.map +1 -1
- package/dist/src/agents/agentLoader.d.ts +2 -2
- package/dist/src/agents/browser/browserAgentInvocation.js +24 -19
- package/dist/src/agents/browser/browserAgentInvocation.js.map +1 -1
- package/dist/src/agents/local-executor.d.ts +1 -0
- package/dist/src/agents/local-executor.js +46 -32
- package/dist/src/agents/local-executor.js.map +1 -1
- package/dist/src/agents/local-executor.test.js +127 -0
- package/dist/src/agents/local-executor.test.js.map +1 -1
- package/dist/src/agents/local-invocation.js +24 -20
- package/dist/src/agents/local-invocation.js.map +1 -1
- package/dist/src/agents/local-invocation.test.js +9 -9
- package/dist/src/agents/local-invocation.test.js.map +1 -1
- package/dist/src/agents/local-subagent-protocol.d.ts +18 -0
- package/dist/src/agents/local-subagent-protocol.js +357 -0
- package/dist/src/agents/local-subagent-protocol.js.map +1 -0
- package/dist/src/agents/local-subagent-protocol.test.d.ts +6 -0
- package/dist/src/agents/local-subagent-protocol.test.js +676 -0
- package/dist/src/agents/local-subagent-protocol.test.js.map +1 -0
- package/dist/src/agents/remote-invocation.js +6 -6
- package/dist/src/agents/remote-invocation.js.map +1 -1
- package/dist/src/agents/remote-invocation.test.js +23 -12
- package/dist/src/agents/remote-invocation.test.js.map +1 -1
- package/dist/src/agents/remote-subagent-protocol.d.ts +31 -0
- package/dist/src/agents/remote-subagent-protocol.js +330 -0
- package/dist/src/agents/remote-subagent-protocol.js.map +1 -0
- package/dist/src/agents/remote-subagent-protocol.test.d.ts +6 -0
- package/dist/src/agents/remote-subagent-protocol.test.js +652 -0
- package/dist/src/agents/remote-subagent-protocol.test.js.map +1 -0
- package/dist/src/agents/skill-extraction-agent.js +1 -0
- package/dist/src/agents/skill-extraction-agent.js.map +1 -1
- package/dist/src/agents/skill-extraction-agent.test.js +1 -0
- package/dist/src/agents/skill-extraction-agent.test.js.map +1 -1
- package/dist/src/agents/types.d.ts +13 -2
- package/dist/src/agents/types.js +7 -0
- package/dist/src/agents/types.js.map +1 -1
- package/dist/src/availability/modelAvailabilityService.d.ts +6 -6
- package/dist/src/availability/modelAvailabilityService.js +14 -7
- package/dist/src/availability/modelAvailabilityService.js.map +1 -1
- package/dist/src/availability/modelAvailabilityService.test.js +34 -0
- package/dist/src/availability/modelAvailabilityService.test.js.map +1 -1
- package/dist/src/availability/policyHelpers.js +24 -12
- package/dist/src/availability/policyHelpers.js.map +1 -1
- package/dist/src/availability/policyHelpers.test.js +3 -2
- package/dist/src/availability/policyHelpers.test.js.map +1 -1
- package/dist/src/code_assist/oauth2.js +3 -0
- package/dist/src/code_assist/oauth2.js.map +1 -1
- package/dist/src/code_assist/setup.d.ts +3 -0
- package/dist/src/code_assist/setup.js +9 -0
- package/dist/src/code_assist/setup.js.map +1 -1
- package/dist/src/code_assist/setup.test.js +9 -1
- package/dist/src/code_assist/setup.test.js.map +1 -1
- package/dist/src/commands/memory.d.ts +5 -14
- package/dist/src/commands/memory.js +19 -141
- package/dist/src/commands/memory.js.map +1 -1
- package/dist/src/commands/memory.test.js +62 -0
- package/dist/src/commands/memory.test.js.map +1 -1
- package/dist/src/config/config.d.ts +6 -2
- package/dist/src/config/config.js +63 -8
- package/dist/src/config/config.js.map +1 -1
- package/dist/src/config/config.test.js +48 -0
- package/dist/src/config/config.test.js.map +1 -1
- package/dist/src/config/defaultModelConfigs.js +13 -0
- package/dist/src/config/defaultModelConfigs.js.map +1 -1
- package/dist/src/config/models.js +1 -1
- package/dist/src/config/models.js.map +1 -1
- package/dist/src/config/projectRegistry.d.ts +1 -0
- package/dist/src/config/projectRegistry.js +13 -2
- package/dist/src/config/projectRegistry.js.map +1 -1
- package/dist/src/config/projectRegistry.test.js +43 -0
- package/dist/src/config/projectRegistry.test.js.map +1 -1
- package/dist/src/context/config/profiles.js +4 -0
- package/dist/src/context/config/profiles.js.map +1 -1
- package/dist/src/context/contextManager.barrier.test.js +4 -3
- package/dist/src/context/contextManager.barrier.test.js.map +1 -1
- package/dist/src/context/contextManager.d.ts +9 -2
- package/dist/src/context/contextManager.hotstart.test.d.ts +6 -0
- package/dist/src/context/contextManager.hotstart.test.js +61 -0
- package/dist/src/context/contextManager.hotstart.test.js.map +1 -0
- package/dist/src/context/contextManager.js +96 -21
- package/dist/src/context/contextManager.js.map +1 -1
- package/dist/src/context/eventBus.d.ts +6 -0
- package/dist/src/context/eventBus.js +6 -0
- package/dist/src/context/eventBus.js.map +1 -1
- package/dist/src/context/graph/render.d.ts +3 -1
- package/dist/src/context/graph/render.js +33 -9
- package/dist/src/context/graph/render.js.map +1 -1
- package/dist/src/context/graph/render.test.d.ts +6 -0
- package/dist/src/context/graph/render.test.js +203 -0
- package/dist/src/context/graph/render.test.js.map +1 -0
- package/dist/src/context/graph/toGraph.js +3 -3
- package/dist/src/context/graph/toGraph.js.map +1 -1
- package/dist/src/context/graph/toGraph.test.d.ts +6 -0
- package/dist/src/context/graph/toGraph.test.js +35 -0
- package/dist/src/context/graph/toGraph.test.js.map +1 -0
- package/dist/src/context/initializer.js +11 -2
- package/dist/src/context/initializer.js.map +1 -1
- package/dist/src/context/pipeline/contextWorkingBuffer.d.ts +5 -7
- package/dist/src/context/pipeline/contextWorkingBuffer.js +105 -29
- package/dist/src/context/pipeline/contextWorkingBuffer.js.map +1 -1
- package/dist/src/context/pipeline/contextWorkingBuffer.test.js +67 -0
- package/dist/src/context/pipeline/contextWorkingBuffer.test.js.map +1 -1
- package/dist/src/context/pipeline/environment.d.ts +4 -0
- package/dist/src/context/pipeline/environmentImpl.d.ts +6 -5
- package/dist/src/context/pipeline/environmentImpl.js +6 -8
- package/dist/src/context/pipeline/environmentImpl.js.map +1 -1
- package/dist/src/context/pipeline/environmentImpl.test.js +5 -1
- package/dist/src/context/pipeline/environmentImpl.test.js.map +1 -1
- package/dist/src/context/pipeline/orchestrator.d.ts +1 -0
- package/dist/src/context/pipeline/orchestrator.js +59 -24
- package/dist/src/context/pipeline/orchestrator.js.map +1 -1
- package/dist/src/context/processors/blobDegradationProcessor.test.js +2 -2
- package/dist/src/context/processors/rollingSummaryProcessor.js +2 -15
- package/dist/src/context/processors/rollingSummaryProcessor.js.map +1 -1
- package/dist/src/context/processors/stateSnapshotAsyncProcessor.d.ts +7 -0
- package/dist/src/context/processors/stateSnapshotAsyncProcessor.js +33 -21
- package/dist/src/context/processors/stateSnapshotAsyncProcessor.js.map +1 -1
- package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js +31 -7
- package/dist/src/context/processors/stateSnapshotAsyncProcessor.test.js.map +1 -1
- package/dist/src/context/processors/stateSnapshotProcessor.d.ts +2 -0
- package/dist/src/context/processors/stateSnapshotProcessor.js +28 -4
- package/dist/src/context/processors/stateSnapshotProcessor.js.map +1 -1
- package/dist/src/context/processors/stateSnapshotProcessor.test.js +40 -1
- package/dist/src/context/processors/stateSnapshotProcessor.test.js.map +1 -1
- package/dist/src/context/system-tests/hysteresis.test.d.ts +6 -0
- package/dist/src/context/system-tests/hysteresis.test.js +98 -0
- package/dist/src/context/system-tests/hysteresis.test.js.map +1 -0
- package/dist/src/context/system-tests/lifecycle.golden.test.js +90 -69
- package/dist/src/context/system-tests/lifecycle.golden.test.js.map +1 -1
- package/dist/src/context/system-tests/simulationHarness.d.ts +1 -4
- package/dist/src/context/system-tests/simulationHarness.js +16 -30
- package/dist/src/context/system-tests/simulationHarness.js.map +1 -1
- package/dist/src/context/testing/contextTestUtils.d.ts +1 -0
- package/dist/src/context/testing/contextTestUtils.js +48 -25
- package/dist/src/context/testing/contextTestUtils.js.map +1 -1
- package/dist/src/context/utils/adaptiveTokenCalculator.d.ts +61 -0
- package/dist/src/context/utils/adaptiveTokenCalculator.js +116 -0
- package/dist/src/context/utils/adaptiveTokenCalculator.js.map +1 -0
- package/dist/src/context/utils/adaptiveTokenCalculator.test.d.ts +6 -0
- package/dist/src/context/utils/adaptiveTokenCalculator.test.js +85 -0
- package/dist/src/context/utils/adaptiveTokenCalculator.test.js.map +1 -0
- package/dist/src/context/utils/contextTokenCalculator.d.ts +47 -1
- package/dist/src/context/utils/contextTokenCalculator.js +20 -3
- package/dist/src/context/utils/contextTokenCalculator.js.map +1 -1
- package/dist/src/context/utils/contextTokenCalculator.test.js +8 -8
- package/dist/src/context/utils/contextTokenCalculator.test.js.map +1 -1
- package/dist/src/context/utils/formatNodesForLlm.d.ts +21 -0
- package/dist/src/context/utils/formatNodesForLlm.js +69 -0
- package/dist/src/context/utils/formatNodesForLlm.js.map +1 -0
- package/dist/src/context/utils/formatNodesForLlm.test.d.ts +6 -0
- package/dist/src/context/utils/formatNodesForLlm.test.js +110 -0
- package/dist/src/context/utils/formatNodesForLlm.test.js.map +1 -0
- package/dist/src/context/utils/snapshotGenerator.d.ts +23 -1
- package/dist/src/context/utils/snapshotGenerator.js +249 -31
- package/dist/src/context/utils/snapshotGenerator.js.map +1 -1
- package/dist/src/context/utils/snapshotGenerator.test.d.ts +6 -0
- package/dist/src/context/utils/snapshotGenerator.test.js +255 -0
- package/dist/src/context/utils/snapshotGenerator.test.js.map +1 -0
- package/dist/src/context/utils/tokenCalibration.d.ts +9 -0
- package/dist/src/context/utils/tokenCalibration.js +30 -0
- package/dist/src/context/utils/tokenCalibration.js.map +1 -0
- package/dist/src/core/baseLlmClient.d.ts +8 -0
- package/dist/src/core/baseLlmClient.js +14 -0
- package/dist/src/core/baseLlmClient.js.map +1 -1
- package/dist/src/core/client.js +12 -1
- package/dist/src/core/client.js.map +1 -1
- package/dist/src/core/client.test.js +4 -4
- package/dist/src/core/contentGenerator.js +12 -10
- package/dist/src/core/contentGenerator.js.map +1 -1
- package/dist/src/core/geminiChat.d.ts +2 -0
- package/dist/src/core/geminiChat.js +60 -5
- package/dist/src/core/geminiChat.js.map +1 -1
- package/dist/src/core/geminiChat.test.js +195 -2
- package/dist/src/core/geminiChat.test.js.map +1 -1
- package/dist/src/core/turn.js +30 -2
- package/dist/src/core/turn.js.map +1 -1
- package/dist/src/core/turn.test.js +13 -8
- package/dist/src/core/turn.test.js.map +1 -1
- package/dist/src/generated/git-commit.d.ts +2 -2
- package/dist/src/generated/git-commit.js +2 -2
- package/dist/src/generated/git-commit.js.map +1 -1
- package/dist/src/hooks/hookEventHandler.js +3 -2
- package/dist/src/hooks/hookEventHandler.js.map +1 -1
- package/dist/src/hooks/hookEventHandler.test.js +80 -0
- package/dist/src/hooks/hookEventHandler.test.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/prompts/snippets.js +1 -1
- package/dist/src/prompts/snippets.js.map +1 -1
- package/dist/src/routing/strategies/approvalModeStrategy.js +5 -3
- package/dist/src/routing/strategies/approvalModeStrategy.js.map +1 -1
- package/dist/src/routing/strategies/approvalModeStrategy.test.js +2 -0
- package/dist/src/routing/strategies/approvalModeStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.js +8 -1
- package/dist/src/routing/strategies/classifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/classifierStrategy.test.js +4 -0
- package/dist/src/routing/strategies/classifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/gemmaClassifierStrategy.js +7 -1
- package/dist/src/routing/strategies/gemmaClassifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js +4 -0
- package/dist/src/routing/strategies/gemmaClassifierStrategy.test.js.map +1 -1
- package/dist/src/routing/strategies/numericalClassifierStrategy.d.ts +1 -0
- package/dist/src/routing/strategies/numericalClassifierStrategy.js +22 -3
- package/dist/src/routing/strategies/numericalClassifierStrategy.js.map +1 -1
- package/dist/src/routing/strategies/numericalClassifierStrategy.test.js +168 -23
- package/dist/src/routing/strategies/numericalClassifierStrategy.test.js.map +1 -1
- package/dist/src/scheduler/scheduler.js +11 -0
- package/dist/src/scheduler/scheduler.js.map +1 -1
- package/dist/src/scheduler/scheduler.test.js +1 -1
- package/dist/src/scheduler/scheduler.test.js.map +1 -1
- package/dist/src/scheduler/state-manager.js +5 -1
- package/dist/src/scheduler/state-manager.js.map +1 -1
- package/dist/src/scheduler/tool-executor.js +7 -4
- package/dist/src/scheduler/tool-executor.js.map +1 -1
- package/dist/src/scheduler/types.d.ts +5 -1
- package/dist/src/scheduler/types.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.js +2 -1
- package/dist/src/services/fileDiscoveryService.js.map +1 -1
- package/dist/src/services/fileDiscoveryService.test.js +36 -0
- package/dist/src/services/fileDiscoveryService.test.js.map +1 -1
- package/dist/src/services/gitService.js +8 -1
- package/dist/src/services/gitService.js.map +1 -1
- package/dist/src/services/gitService.test.js +104 -0
- package/dist/src/services/gitService.test.js.map +1 -1
- package/dist/src/services/keychainService.js +14 -5
- package/dist/src/services/keychainService.js.map +1 -1
- package/dist/src/services/memoryPatchUtils.d.ts +66 -4
- package/dist/src/services/memoryPatchUtils.js +267 -5
- package/dist/src/services/memoryPatchUtils.js.map +1 -1
- package/dist/src/services/memoryService.js +25 -1
- package/dist/src/services/memoryService.js.map +1 -1
- package/dist/src/services/memoryService.test.js +61 -1
- package/dist/src/services/memoryService.test.js.map +1 -1
- package/dist/src/services/test-data/resolved-aliases-retry.golden.json +11 -0
- package/dist/src/services/test-data/resolved-aliases.golden.json +11 -0
- package/dist/src/telemetry/gcp-exporters.d.ts +2 -0
- package/dist/src/telemetry/gcp-exporters.js +69 -5
- package/dist/src/telemetry/gcp-exporters.js.map +1 -1
- package/dist/src/telemetry/gcp-exporters.test.js +52 -0
- package/dist/src/telemetry/gcp-exporters.test.js.map +1 -1
- package/dist/src/telemetry/metrics.js +13 -2
- package/dist/src/telemetry/metrics.js.map +1 -1
- package/dist/src/telemetry/metrics.test.js +61 -1
- package/dist/src/telemetry/metrics.test.js.map +1 -1
- package/dist/src/tools/definitions/model-family-sets/default-legacy.js +5 -2
- package/dist/src/tools/definitions/model-family-sets/default-legacy.js.map +1 -1
- package/dist/src/tools/definitions/model-family-sets/gemini-3.js +7 -4
- package/dist/src/tools/definitions/model-family-sets/gemini-3.js.map +1 -1
- package/dist/src/tools/edit.js +19 -0
- package/dist/src/tools/edit.js.map +1 -1
- package/dist/src/tools/edit.test.js +9 -0
- package/dist/src/tools/edit.test.js.map +1 -1
- package/dist/src/tools/grep.js +13 -1
- package/dist/src/tools/grep.js.map +1 -1
- package/dist/src/tools/ls.js +5 -0
- package/dist/src/tools/ls.js.map +1 -1
- package/dist/src/tools/mcp-client.js +17 -3
- package/dist/src/tools/mcp-client.js.map +1 -1
- package/dist/src/tools/mcp-client.test.js +24 -0
- package/dist/src/tools/mcp-client.test.js.map +1 -1
- package/dist/src/tools/read-file.js +11 -6
- package/dist/src/tools/read-file.js.map +1 -1
- package/dist/src/tools/read-file.test.js +20 -8
- package/dist/src/tools/read-file.test.js.map +1 -1
- package/dist/src/tools/ripGrep.js +13 -1
- package/dist/src/tools/ripGrep.js.map +1 -1
- package/dist/src/tools/shell.js +14 -0
- package/dist/src/tools/shell.js.map +1 -1
- package/dist/src/tools/shell.test.js +5 -0
- package/dist/src/tools/shell.test.js.map +1 -1
- package/dist/src/tools/tools.d.ts +6 -0
- package/dist/src/tools/tools.js.map +1 -1
- package/dist/src/tools/topicTool.js +5 -0
- package/dist/src/tools/topicTool.js.map +1 -1
- package/dist/src/tools/write-file.js +13 -0
- package/dist/src/tools/write-file.js.map +1 -1
- package/dist/src/tools/write-file.test.js +8 -0
- package/dist/src/tools/write-file.test.js.map +1 -1
- package/dist/src/utils/errors.js +3 -8
- package/dist/src/utils/errors.js.map +1 -1
- package/dist/src/utils/filesearch/ignore.js +4 -1
- package/dist/src/utils/filesearch/ignore.js.map +1 -1
- package/dist/src/utils/ignoreFileParser.js +1 -1
- package/dist/src/utils/ignoreFileParser.js.map +1 -1
- package/dist/src/utils/ignorePatterns.js +2 -0
- package/dist/src/utils/ignorePatterns.js.map +1 -1
- package/dist/src/utils/ignorePatterns.test.js +1 -0
- package/dist/src/utils/ignorePatterns.test.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.js +55 -40
- package/dist/src/utils/memoryDiscovery.js.map +1 -1
- package/dist/src/utils/memoryDiscovery.test.js +77 -9
- package/dist/src/utils/memoryDiscovery.test.js.map +1 -1
- package/dist/src/utils/modelUtils.d.ts +14 -0
- package/dist/src/utils/modelUtils.js +17 -0
- package/dist/src/utils/modelUtils.js.map +1 -0
- package/dist/src/utils/modelUtils.test.d.ts +6 -0
- package/dist/src/utils/modelUtils.test.js +23 -0
- package/dist/src/utils/modelUtils.test.js.map +1 -0
- package/dist/src/utils/paths.d.ts +15 -1
- package/dist/src/utils/paths.js +22 -7
- package/dist/src/utils/paths.js.map +1 -1
- package/dist/src/utils/paths.test.js +25 -1
- package/dist/src/utils/paths.test.js.map +1 -1
- package/dist/src/utils/quotaErrorDetection.js +23 -12
- package/dist/src/utils/quotaErrorDetection.js.map +1 -1
- package/dist/src/utils/shell-utils.js +9 -1
- package/dist/src/utils/shell-utils.js.map +1 -1
- package/dist/src/utils/shell-utils.test.js +7 -3
- package/dist/src/utils/shell-utils.test.js.map +1 -1
- package/dist/src/utils/tokenCalculation.js +2 -1
- package/dist/src/utils/tokenCalculation.js.map +1 -1
- package/dist/src/utils/tokenCalculation.test.js +15 -0
- package/dist/src/utils/tokenCalculation.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
import { describe, it, expect, vi, beforeEach, afterEach, } from 'vitest';
|
|
7
|
+
import { RemoteSubagentSession } from './remote-subagent-protocol.js';
|
|
8
|
+
import { A2AAuthProviderFactory } from './auth-provider/factory.js';
|
|
9
|
+
import { createMockMessageBus } from '../test-utils/mock-message-bus.js';
|
|
10
|
+
// Mock A2AClientManager at module level
|
|
11
|
+
vi.mock('./a2a-client-manager.js', () => ({
|
|
12
|
+
A2AClientManager: vi.fn().mockImplementation(() => ({
|
|
13
|
+
getClient: vi.fn(),
|
|
14
|
+
loadAgent: vi.fn(),
|
|
15
|
+
sendMessageStream: vi.fn(),
|
|
16
|
+
})),
|
|
17
|
+
}));
|
|
18
|
+
// Mock A2AAuthProviderFactory
|
|
19
|
+
vi.mock('./auth-provider/factory.js', () => ({
|
|
20
|
+
A2AAuthProviderFactory: {
|
|
21
|
+
create: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
const mockDefinition = {
|
|
25
|
+
name: 'test-remote-agent',
|
|
26
|
+
kind: 'remote',
|
|
27
|
+
agentCardUrl: 'http://test-agent/card',
|
|
28
|
+
displayName: 'Test Remote Agent',
|
|
29
|
+
description: 'A test remote agent',
|
|
30
|
+
inputConfig: {
|
|
31
|
+
inputSchema: { type: 'object' },
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
function makeChunk(text) {
|
|
35
|
+
return {
|
|
36
|
+
kind: 'message',
|
|
37
|
+
messageId: `msg-${Math.random()}`,
|
|
38
|
+
role: 'agent',
|
|
39
|
+
parts: [{ kind: 'text', text }],
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
describe('RemoteSubagentSession (protocol)', () => {
|
|
43
|
+
let mockClientManager;
|
|
44
|
+
let mockContext;
|
|
45
|
+
let mockMessageBus;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
vi.clearAllMocks();
|
|
48
|
+
// Each test creates fresh session instances. contextId/taskId persist as
|
|
49
|
+
// instance fields within a session, not via static state.
|
|
50
|
+
mockClientManager = {
|
|
51
|
+
getClient: vi.fn().mockReturnValue(undefined), // client not yet loaded
|
|
52
|
+
loadAgent: vi.fn().mockResolvedValue(undefined),
|
|
53
|
+
sendMessageStream: vi.fn(),
|
|
54
|
+
};
|
|
55
|
+
const mockConfig = {
|
|
56
|
+
getA2AClientManager: vi.fn().mockReturnValue(mockClientManager),
|
|
57
|
+
injectionService: {
|
|
58
|
+
getLatestInjectionIndex: vi.fn().mockReturnValue(0),
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
mockContext = { config: mockConfig };
|
|
62
|
+
mockMessageBus = createMockMessageBus();
|
|
63
|
+
// Default: sendMessageStream yields one chunk with "Hello"
|
|
64
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
65
|
+
yield makeChunk('Hello');
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
afterEach(() => {
|
|
69
|
+
vi.restoreAllMocks();
|
|
70
|
+
});
|
|
71
|
+
// Helper: run a session with the default or custom stream and collect events
|
|
72
|
+
async function runSession(definition = mockDefinition, query = 'test query') {
|
|
73
|
+
const session = new RemoteSubagentSession(definition, mockContext, mockMessageBus);
|
|
74
|
+
const events = [];
|
|
75
|
+
session.subscribe((e) => events.push(e));
|
|
76
|
+
await session.send({
|
|
77
|
+
message: { content: [{ type: 'text', text: query }] },
|
|
78
|
+
});
|
|
79
|
+
const result = await session.getResult();
|
|
80
|
+
return { session, events, result };
|
|
81
|
+
}
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
// Lifecycle events
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
describe('lifecycle events', () => {
|
|
86
|
+
it('emits agent_start then agent_end(completed) on success', async () => {
|
|
87
|
+
const { events } = await runSession();
|
|
88
|
+
const types = events.map((e) => e.type);
|
|
89
|
+
expect(types[0]).toBe('agent_start');
|
|
90
|
+
expect(types[types.length - 1]).toBe('agent_end');
|
|
91
|
+
const end = events[events.length - 1];
|
|
92
|
+
if (end.type === 'agent_end') {
|
|
93
|
+
expect(end.reason).toBe('completed');
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
it('emits agent_start exactly once', async () => {
|
|
97
|
+
const { events } = await runSession();
|
|
98
|
+
expect(events.filter((e) => e.type === 'agent_start')).toHaveLength(1);
|
|
99
|
+
});
|
|
100
|
+
it('emits agent_end exactly once on error path', async () => {
|
|
101
|
+
mockClientManager.sendMessageStream.mockReturnValue({
|
|
102
|
+
[Symbol.asyncIterator]() {
|
|
103
|
+
return {
|
|
104
|
+
async next() {
|
|
105
|
+
throw new Error('stream error');
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
111
|
+
const events = [];
|
|
112
|
+
session.subscribe((e) => events.push(e));
|
|
113
|
+
await session.send({
|
|
114
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
115
|
+
});
|
|
116
|
+
await expect(session.getResult()).rejects.toThrow('stream error');
|
|
117
|
+
expect(events.filter((e) => e.type === 'agent_end')).toHaveLength(1);
|
|
118
|
+
});
|
|
119
|
+
it('all events share the same streamId', async () => {
|
|
120
|
+
const { events } = await runSession();
|
|
121
|
+
const streamIds = new Set(events.map((e) => e.streamId));
|
|
122
|
+
expect(streamIds.size).toBe(1);
|
|
123
|
+
});
|
|
124
|
+
it('message returns a non-null streamId; unsupported payload returns null', async () => {
|
|
125
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
126
|
+
const updateResult = await session.send({
|
|
127
|
+
update: { config: { key: 'val' } },
|
|
128
|
+
});
|
|
129
|
+
expect(updateResult.streamId).toBeNull();
|
|
130
|
+
const messageResult = await session.send({
|
|
131
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
132
|
+
});
|
|
133
|
+
expect(messageResult.streamId).not.toBeNull();
|
|
134
|
+
// complete the session to avoid dangling execution
|
|
135
|
+
await session.getResult();
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
// ---------------------------------------------------------------------------
|
|
139
|
+
// Chunk → AgentEvent translation
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
describe('chunk → AgentEvent translation', () => {
|
|
142
|
+
it('each A2A chunk produces a message event with incremental delta text', async () => {
|
|
143
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
144
|
+
yield makeChunk('Hello');
|
|
145
|
+
yield makeChunk(' world');
|
|
146
|
+
});
|
|
147
|
+
const { events } = await runSession();
|
|
148
|
+
const msgEvents = events.filter((e) => e.type === 'message');
|
|
149
|
+
expect(msgEvents).toHaveLength(2);
|
|
150
|
+
// Each message event contains only the delta, not accumulated text
|
|
151
|
+
if (msgEvents[0]?.type === 'message') {
|
|
152
|
+
const text = msgEvents[0].content.find((c) => c.type === 'text');
|
|
153
|
+
expect(text?.type === 'text' && text.text).toBe('Hello');
|
|
154
|
+
}
|
|
155
|
+
if (msgEvents[1]?.type === 'message') {
|
|
156
|
+
const text = msgEvents[1].content.find((c) => c.type === 'text');
|
|
157
|
+
expect(text?.type === 'text' && text.text).toBe(' world');
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
it('getLatestProgress() is updated per chunk with state running', async () => {
|
|
161
|
+
let capturedProgress;
|
|
162
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
163
|
+
yield makeChunk('Partial');
|
|
164
|
+
});
|
|
165
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
166
|
+
session.subscribe((e) => {
|
|
167
|
+
if (e.type === 'message') {
|
|
168
|
+
capturedProgress = session.getLatestProgress();
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
await session.send({
|
|
172
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
173
|
+
});
|
|
174
|
+
await session.getResult();
|
|
175
|
+
// During streaming, progress should be 'running'
|
|
176
|
+
expect(capturedProgress).toBeDefined();
|
|
177
|
+
// Note: by the time we check, progress may be 'completed'.
|
|
178
|
+
// During the message event, it was 'running'.
|
|
179
|
+
expect(capturedProgress?.isSubagentProgress).toBe(true);
|
|
180
|
+
expect(capturedProgress?.agentName).toBe('Test Remote Agent');
|
|
181
|
+
});
|
|
182
|
+
it('getLatestProgress() state is completed after getResult() resolves', async () => {
|
|
183
|
+
const { session } = await runSession();
|
|
184
|
+
const progress = session.getLatestProgress();
|
|
185
|
+
expect(progress?.state).toBe('completed');
|
|
186
|
+
expect(progress?.result).toBe('Hello');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
// getResult() promise
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
describe('getResult()', () => {
|
|
193
|
+
it('resolves with ToolResult containing llmContent and SubagentProgress returnDisplay', async () => {
|
|
194
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
195
|
+
yield makeChunk('Result text');
|
|
196
|
+
});
|
|
197
|
+
const { result } = await runSession();
|
|
198
|
+
expect(result.llmContent).toEqual([{ text: 'Result text' }]);
|
|
199
|
+
const display = result.returnDisplay;
|
|
200
|
+
expect(display.isSubagentProgress).toBe(true);
|
|
201
|
+
expect(display.state).toBe('completed');
|
|
202
|
+
expect(display.result).toBe('Result text');
|
|
203
|
+
expect(display.agentName).toBe('Test Remote Agent');
|
|
204
|
+
});
|
|
205
|
+
it('rejects when stream throws a non-A2A error', async () => {
|
|
206
|
+
mockClientManager.sendMessageStream.mockReturnValue({
|
|
207
|
+
[Symbol.asyncIterator]() {
|
|
208
|
+
return {
|
|
209
|
+
async next() {
|
|
210
|
+
throw new Error('network failure');
|
|
211
|
+
},
|
|
212
|
+
};
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
216
|
+
await session.send({
|
|
217
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
218
|
+
});
|
|
219
|
+
await expect(session.getResult()).rejects.toThrow();
|
|
220
|
+
});
|
|
221
|
+
it('resolves even with empty stream (empty final output)', async () => {
|
|
222
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
223
|
+
// yield nothing
|
|
224
|
+
});
|
|
225
|
+
const { result } = await runSession();
|
|
226
|
+
expect(result.llmContent).toEqual([{ text: '' }]);
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
230
|
+
// Session state persistence
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
describe('session state persistence', () => {
|
|
233
|
+
it('second send reuses contextId captured from first send', async () => {
|
|
234
|
+
let callCount = 0;
|
|
235
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* (_name, _query, opts) {
|
|
236
|
+
callCount++;
|
|
237
|
+
if (callCount === 1) {
|
|
238
|
+
yield {
|
|
239
|
+
kind: 'message',
|
|
240
|
+
messageId: 'msg-1',
|
|
241
|
+
role: 'agent',
|
|
242
|
+
contextId: 'ctx-from-server',
|
|
243
|
+
parts: [{ kind: 'text', text: 'First response' }],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
else {
|
|
247
|
+
// Second send on same session should pass the contextId
|
|
248
|
+
expect(opts.contextId).toBe('ctx-from-server');
|
|
249
|
+
yield makeChunk('Second response');
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
253
|
+
// First send — establishes contextId
|
|
254
|
+
await session.send({
|
|
255
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
256
|
+
});
|
|
257
|
+
await session.getResult();
|
|
258
|
+
// Second send on same session — should reuse contextId
|
|
259
|
+
await session.send({
|
|
260
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
261
|
+
});
|
|
262
|
+
await session.getResult();
|
|
263
|
+
expect(callCount).toBe(2);
|
|
264
|
+
});
|
|
265
|
+
it('separate session instances have independent state', async () => {
|
|
266
|
+
const capturedContextIds = [];
|
|
267
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* (_name, _query, opts) {
|
|
268
|
+
capturedContextIds.push(opts.contextId);
|
|
269
|
+
yield {
|
|
270
|
+
kind: 'message',
|
|
271
|
+
messageId: 'msg-1',
|
|
272
|
+
role: 'agent',
|
|
273
|
+
contextId: 'ctx-from-server',
|
|
274
|
+
parts: [{ kind: 'text', text: 'ok' }],
|
|
275
|
+
};
|
|
276
|
+
});
|
|
277
|
+
// Two separate sessions for the same agent — state is NOT shared
|
|
278
|
+
const session1 = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
279
|
+
await session1.send({
|
|
280
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
281
|
+
});
|
|
282
|
+
await session1.getResult();
|
|
283
|
+
const session2 = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
284
|
+
await session2.send({
|
|
285
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
286
|
+
});
|
|
287
|
+
await session2.getResult();
|
|
288
|
+
// Both start with no contextId — separate instances, no shared state
|
|
289
|
+
expect(capturedContextIds[0]).toBeUndefined();
|
|
290
|
+
expect(capturedContextIds[1]).toBeUndefined();
|
|
291
|
+
});
|
|
292
|
+
it('taskId is cleared when a terminal-state task chunk is received', async () => {
|
|
293
|
+
let callCount = 0;
|
|
294
|
+
const capturedTaskIds = [];
|
|
295
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* (_n, _q, opts) {
|
|
296
|
+
callCount++;
|
|
297
|
+
capturedTaskIds.push(opts.taskId);
|
|
298
|
+
if (callCount === 1) {
|
|
299
|
+
yield {
|
|
300
|
+
kind: 'task',
|
|
301
|
+
id: 'task-123',
|
|
302
|
+
contextId: 'ctx-1',
|
|
303
|
+
status: { state: 'completed' },
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
yield makeChunk('done');
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
// Use same session for multi-send
|
|
311
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
312
|
+
await session.send({
|
|
313
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
314
|
+
});
|
|
315
|
+
await session.getResult();
|
|
316
|
+
await session.send({
|
|
317
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
318
|
+
});
|
|
319
|
+
await session.getResult();
|
|
320
|
+
expect(callCount).toBe(2);
|
|
321
|
+
// First call starts with no taskId
|
|
322
|
+
expect(capturedTaskIds[0]).toBeUndefined();
|
|
323
|
+
// Second call: taskId was cleared because terminal-state task chunk was received
|
|
324
|
+
expect(capturedTaskIds[1]).toBeUndefined();
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
// Auth setup
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
describe('auth setup', () => {
|
|
331
|
+
it('no auth → loadAgent called without auth handler', async () => {
|
|
332
|
+
await runSession();
|
|
333
|
+
expect(mockClientManager.loadAgent).toHaveBeenCalledWith('test-remote-agent', { type: 'url', url: 'http://test-agent/card' }, undefined);
|
|
334
|
+
});
|
|
335
|
+
it('definition.auth present → A2AAuthProviderFactory.create called', async () => {
|
|
336
|
+
const authDef = {
|
|
337
|
+
...mockDefinition,
|
|
338
|
+
name: 'auth-agent',
|
|
339
|
+
auth: {
|
|
340
|
+
type: 'http',
|
|
341
|
+
scheme: 'Bearer',
|
|
342
|
+
token: 'secret',
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
const mockProvider = {
|
|
346
|
+
type: 'http',
|
|
347
|
+
headers: vi.fn().mockResolvedValue({ Authorization: 'Bearer secret' }),
|
|
348
|
+
shouldRetryWithHeaders: vi.fn(),
|
|
349
|
+
};
|
|
350
|
+
A2AAuthProviderFactory.create.mockResolvedValue(mockProvider);
|
|
351
|
+
await runSession(authDef, 'q');
|
|
352
|
+
expect(A2AAuthProviderFactory.create).toHaveBeenCalledWith(expect.objectContaining({
|
|
353
|
+
agentName: 'auth-agent',
|
|
354
|
+
agentCardUrl: 'http://test-agent/card',
|
|
355
|
+
}));
|
|
356
|
+
expect(mockClientManager.loadAgent).toHaveBeenCalledWith('auth-agent', expect.any(Object), mockProvider);
|
|
357
|
+
});
|
|
358
|
+
it('auth factory returns undefined → throws error that rejects getResult()', async () => {
|
|
359
|
+
const authDef = {
|
|
360
|
+
...mockDefinition,
|
|
361
|
+
name: 'failing-auth-agent',
|
|
362
|
+
auth: {
|
|
363
|
+
type: 'http',
|
|
364
|
+
scheme: 'Bearer',
|
|
365
|
+
token: 'secret',
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
A2AAuthProviderFactory.create.mockResolvedValue(undefined);
|
|
369
|
+
const session = new RemoteSubagentSession(authDef, mockContext, mockMessageBus);
|
|
370
|
+
await session.send({
|
|
371
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
372
|
+
});
|
|
373
|
+
await expect(session.getResult()).rejects.toThrow("Failed to create auth provider for agent 'failing-auth-agent'");
|
|
374
|
+
});
|
|
375
|
+
it('agent already loaded → loadAgent not called again', async () => {
|
|
376
|
+
// Return a client object (truthy) so getClient returns defined
|
|
377
|
+
mockClientManager.getClient.mockReturnValue({});
|
|
378
|
+
await runSession();
|
|
379
|
+
expect(mockClientManager.loadAgent).not.toHaveBeenCalled();
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// Error handling
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
describe('error handling', () => {
|
|
386
|
+
it('stream error → error event + agent_end(failed)', async () => {
|
|
387
|
+
mockClientManager.sendMessageStream.mockReturnValue({
|
|
388
|
+
[Symbol.asyncIterator]() {
|
|
389
|
+
return {
|
|
390
|
+
async next() {
|
|
391
|
+
throw new Error('network error');
|
|
392
|
+
},
|
|
393
|
+
};
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
397
|
+
const events = [];
|
|
398
|
+
session.subscribe((e) => events.push(e));
|
|
399
|
+
await session.send({
|
|
400
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
401
|
+
});
|
|
402
|
+
await expect(session.getResult()).rejects.toThrow();
|
|
403
|
+
const errEvent = events.find((e) => e.type === 'error');
|
|
404
|
+
expect(errEvent).toBeDefined();
|
|
405
|
+
const endEvent = events.find((e) => e.type === 'agent_end');
|
|
406
|
+
expect(endEvent).toBeDefined();
|
|
407
|
+
if (endEvent?.type === 'agent_end') {
|
|
408
|
+
expect(endEvent.reason).toBe('failed');
|
|
409
|
+
}
|
|
410
|
+
});
|
|
411
|
+
it('missing A2AClientManager → rejects getResult()', async () => {
|
|
412
|
+
const mockConfig = {
|
|
413
|
+
getA2AClientManager: vi.fn().mockReturnValue(undefined),
|
|
414
|
+
injectionService: {
|
|
415
|
+
getLatestInjectionIndex: vi.fn().mockReturnValue(0),
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
const noClientContext = {
|
|
419
|
+
config: mockConfig,
|
|
420
|
+
};
|
|
421
|
+
const session = new RemoteSubagentSession(mockDefinition, noClientContext, mockMessageBus);
|
|
422
|
+
await session.send({
|
|
423
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
424
|
+
});
|
|
425
|
+
await expect(session.getResult()).rejects.toThrow('A2AClientManager not available');
|
|
426
|
+
});
|
|
427
|
+
});
|
|
428
|
+
// ---------------------------------------------------------------------------
|
|
429
|
+
// Subscription
|
|
430
|
+
// ---------------------------------------------------------------------------
|
|
431
|
+
describe('subscription', () => {
|
|
432
|
+
it('unsubscribe stops event delivery', async () => {
|
|
433
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
434
|
+
const received = [];
|
|
435
|
+
const unsub = session.subscribe((e) => received.push(e));
|
|
436
|
+
unsub();
|
|
437
|
+
await session.send({
|
|
438
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
439
|
+
});
|
|
440
|
+
await session.getResult();
|
|
441
|
+
expect(received).toHaveLength(0);
|
|
442
|
+
});
|
|
443
|
+
it('multiple subscribers all receive events', async () => {
|
|
444
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
445
|
+
const events1 = [];
|
|
446
|
+
const events2 = [];
|
|
447
|
+
session.subscribe((e) => events1.push(e));
|
|
448
|
+
session.subscribe((e) => events2.push(e));
|
|
449
|
+
await session.send({
|
|
450
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
451
|
+
});
|
|
452
|
+
await session.getResult();
|
|
453
|
+
expect(events1.length).toBeGreaterThan(0);
|
|
454
|
+
expect(events1).toEqual(events2);
|
|
455
|
+
});
|
|
456
|
+
});
|
|
457
|
+
// ---------------------------------------------------------------------------
|
|
458
|
+
// Abort
|
|
459
|
+
// ---------------------------------------------------------------------------
|
|
460
|
+
describe('abort()', () => {
|
|
461
|
+
it('abort() causes agent_end(reason:aborted)', async () => {
|
|
462
|
+
let rejectWithAbort;
|
|
463
|
+
// Stream that blocks until aborted, then throws AbortError
|
|
464
|
+
mockClientManager.sendMessageStream.mockImplementation(
|
|
465
|
+
// eslint-disable-next-line require-yield
|
|
466
|
+
async function* () {
|
|
467
|
+
await new Promise((_resolve, reject) => {
|
|
468
|
+
rejectWithAbort = reject;
|
|
469
|
+
});
|
|
470
|
+
});
|
|
471
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
472
|
+
const events = [];
|
|
473
|
+
session.subscribe((e) => events.push(e));
|
|
474
|
+
void session.send({
|
|
475
|
+
message: { content: [{ type: 'text', text: 'q' }] },
|
|
476
|
+
});
|
|
477
|
+
// Wait for agent_start to be emitted before aborting
|
|
478
|
+
await vi.waitFor(() => {
|
|
479
|
+
expect(events.some((e) => e.type === 'agent_start')).toBe(true);
|
|
480
|
+
});
|
|
481
|
+
await session.abort();
|
|
482
|
+
// Simulate the transport throwing AbortError when signal fires
|
|
483
|
+
const abortErr = new Error('AbortError');
|
|
484
|
+
abortErr.name = 'AbortError';
|
|
485
|
+
rejectWithAbort?.(abortErr);
|
|
486
|
+
const result = await session.getResult();
|
|
487
|
+
expect(result.llmContent).toEqual([{ text: '' }]);
|
|
488
|
+
expect(result.returnDisplay).toBe('');
|
|
489
|
+
const endEvent = events.find((e) => e.type === 'agent_end');
|
|
490
|
+
expect(endEvent).toBeDefined();
|
|
491
|
+
if (endEvent?.type === 'agent_end') {
|
|
492
|
+
expect(endEvent.reason).toBe('aborted');
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
});
|
|
496
|
+
// ---------------------------------------------------------------------------
|
|
497
|
+
// sendMessageStream call args
|
|
498
|
+
// ---------------------------------------------------------------------------
|
|
499
|
+
describe('sendMessageStream call arguments', () => {
|
|
500
|
+
it('passes the query string from the message payload', async () => {
|
|
501
|
+
await runSession(mockDefinition, 'my specific query');
|
|
502
|
+
expect(mockClientManager.sendMessageStream).toHaveBeenCalledWith('test-remote-agent', 'my specific query', expect.objectContaining({ signal: expect.any(Object) }));
|
|
503
|
+
});
|
|
504
|
+
it('uses DEFAULT_QUERY_STRING when message text is empty', async () => {
|
|
505
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
506
|
+
await session.send({
|
|
507
|
+
message: { content: [{ type: 'text', text: '' }] },
|
|
508
|
+
});
|
|
509
|
+
await session.getResult();
|
|
510
|
+
// DEFAULT_QUERY_STRING = 'Get Started!'
|
|
511
|
+
expect(mockClientManager.sendMessageStream).toHaveBeenCalledWith('test-remote-agent', 'Get Started!', expect.objectContaining({ signal: expect.any(Object) }));
|
|
512
|
+
});
|
|
513
|
+
});
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
// Concurrent send() guard
|
|
516
|
+
// ---------------------------------------------------------------------------
|
|
517
|
+
describe('concurrent send() guard', () => {
|
|
518
|
+
it('calling send() while a stream is active throws', async () => {
|
|
519
|
+
let resolveChunk;
|
|
520
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
521
|
+
// Block until test releases the chunk
|
|
522
|
+
await new Promise((resolve) => {
|
|
523
|
+
resolveChunk = resolve;
|
|
524
|
+
});
|
|
525
|
+
yield makeChunk('late');
|
|
526
|
+
});
|
|
527
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
528
|
+
void session.send({
|
|
529
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
530
|
+
});
|
|
531
|
+
// Wait for the stream to actually start (agent_start emitted)
|
|
532
|
+
const events = [];
|
|
533
|
+
session.subscribe((e) => events.push(e));
|
|
534
|
+
await vi.waitFor(() => {
|
|
535
|
+
expect(events.some((e) => e.type === 'agent_start')).toBe(true);
|
|
536
|
+
});
|
|
537
|
+
// Second send() while first stream is active must throw
|
|
538
|
+
await expect(session.send({
|
|
539
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
540
|
+
})).rejects.toThrow('cannot be called while a stream is active');
|
|
541
|
+
// Clean up: release the blocked generator so getResult() can settle
|
|
542
|
+
resolveChunk();
|
|
543
|
+
await session.getResult().catch(() => { });
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
// ---------------------------------------------------------------------------
|
|
547
|
+
// Multi-send support
|
|
548
|
+
// ---------------------------------------------------------------------------
|
|
549
|
+
describe('multi-send', () => {
|
|
550
|
+
it('supports sequential sends after stream completion', async () => {
|
|
551
|
+
let callCount = 0;
|
|
552
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
553
|
+
callCount++;
|
|
554
|
+
yield makeChunk(`Response ${callCount}`);
|
|
555
|
+
});
|
|
556
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
557
|
+
// First send
|
|
558
|
+
const result1 = await session.send({
|
|
559
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
560
|
+
});
|
|
561
|
+
expect(result1.streamId).not.toBeNull();
|
|
562
|
+
const output1 = await session.getResult();
|
|
563
|
+
expect(output1.llmContent).toEqual([{ text: 'Response 1' }]);
|
|
564
|
+
// Second send — should work, not throw
|
|
565
|
+
const result2 = await session.send({
|
|
566
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
567
|
+
});
|
|
568
|
+
expect(result2.streamId).not.toBeNull();
|
|
569
|
+
expect(result2.streamId).not.toBe(result1.streamId);
|
|
570
|
+
const output2 = await session.getResult();
|
|
571
|
+
expect(output2.llmContent).toEqual([{ text: 'Response 2' }]);
|
|
572
|
+
});
|
|
573
|
+
it('getResult() returns the latest stream result', async () => {
|
|
574
|
+
let callCount = 0;
|
|
575
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* () {
|
|
576
|
+
callCount++;
|
|
577
|
+
yield makeChunk(`Result ${callCount}`);
|
|
578
|
+
});
|
|
579
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
580
|
+
await session.send({
|
|
581
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
582
|
+
});
|
|
583
|
+
const result1 = await session.getResult();
|
|
584
|
+
await session.send({
|
|
585
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
586
|
+
});
|
|
587
|
+
const result2 = await session.getResult();
|
|
588
|
+
expect(result1.llmContent).toEqual([{ text: 'Result 1' }]);
|
|
589
|
+
expect(result2.llmContent).toEqual([{ text: 'Result 2' }]);
|
|
590
|
+
});
|
|
591
|
+
it('contextId/taskId persist across sends within the same session', async () => {
|
|
592
|
+
let sendCallCount = 0;
|
|
593
|
+
mockClientManager.sendMessageStream.mockImplementation(async function* (_name, _query, _opts) {
|
|
594
|
+
sendCallCount++;
|
|
595
|
+
// First call returns ids; second call should receive them
|
|
596
|
+
yield {
|
|
597
|
+
kind: 'message',
|
|
598
|
+
messageId: `msg-${sendCallCount}`,
|
|
599
|
+
contextId: `ctx-${sendCallCount}`,
|
|
600
|
+
taskId: `task-${sendCallCount}`,
|
|
601
|
+
role: 'agent',
|
|
602
|
+
parts: [{ kind: 'text', text: `Response ${sendCallCount}` }],
|
|
603
|
+
};
|
|
604
|
+
});
|
|
605
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
606
|
+
// First send — establishes contextId/taskId
|
|
607
|
+
await session.send({
|
|
608
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
609
|
+
});
|
|
610
|
+
await session.getResult();
|
|
611
|
+
// Second send — should pass the persisted contextId/taskId
|
|
612
|
+
await session.send({
|
|
613
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
614
|
+
});
|
|
615
|
+
await session.getResult();
|
|
616
|
+
// Verify the second call received the contextId/taskId from first call
|
|
617
|
+
expect(mockClientManager.sendMessageStream).toHaveBeenCalledTimes(2);
|
|
618
|
+
const secondCallOpts = mockClientManager.sendMessageStream.mock.calls[1]?.[2];
|
|
619
|
+
expect(secondCallOpts).toHaveProperty('contextId', 'ctx-1');
|
|
620
|
+
expect(secondCallOpts).toHaveProperty('taskId', 'task-1');
|
|
621
|
+
});
|
|
622
|
+
it('getResult() rejects when called before any send', async () => {
|
|
623
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
624
|
+
await expect(session.getResult()).rejects.toThrow('No active or completed stream');
|
|
625
|
+
});
|
|
626
|
+
it('emits fresh agent_start/agent_end per stream', async () => {
|
|
627
|
+
const session = new RemoteSubagentSession(mockDefinition, mockContext, mockMessageBus);
|
|
628
|
+
const events = [];
|
|
629
|
+
session.subscribe((e) => events.push(e));
|
|
630
|
+
// First send
|
|
631
|
+
await session.send({
|
|
632
|
+
message: { content: [{ type: 'text', text: 'first' }] },
|
|
633
|
+
});
|
|
634
|
+
await session.getResult();
|
|
635
|
+
const firstStreamEvents = events.length;
|
|
636
|
+
expect(events[0]?.type).toBe('agent_start');
|
|
637
|
+
expect(events[firstStreamEvents - 1]?.type).toBe('agent_end');
|
|
638
|
+
// Second send
|
|
639
|
+
await session.send({
|
|
640
|
+
message: { content: [{ type: 'text', text: 'second' }] },
|
|
641
|
+
});
|
|
642
|
+
await session.getResult();
|
|
643
|
+
// Should have a second agent_start/agent_end pair
|
|
644
|
+
const secondStreamStart = events[firstStreamEvents];
|
|
645
|
+
const lastEvent = events[events.length - 1];
|
|
646
|
+
expect(secondStreamStart?.type).toBe('agent_start');
|
|
647
|
+
expect(lastEvent?.type).toBe('agent_end');
|
|
648
|
+
expect(secondStreamStart?.streamId).not.toBe(events[0]?.streamId);
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
//# sourceMappingURL=remote-subagent-protocol.test.js.map
|