agent-relay 3.2.3 → 3.2.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.cjs +265 -108
- package/package.json +11 -10
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/ADAPTER_REVIEW.md +109 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.js +220 -0
- package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
- package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.js +209 -0
- package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.js +7 -0
- package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
- package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
- package/packages/sdk/dist/communicate/core.d.ts +58 -0
- package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/core.js +128 -0
- package/packages/sdk/dist/communicate/core.js.map +1 -0
- package/packages/sdk/dist/communicate/index.d.ts +4 -0
- package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/index.js +4 -0
- package/packages/sdk/dist/communicate/index.js.map +1 -0
- package/packages/sdk/dist/communicate/transport.d.ts +36 -0
- package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/transport.js +371 -0
- package/packages/sdk/dist/communicate/transport.js.map +1 -0
- package/packages/sdk/dist/communicate/types.d.ts +58 -0
- package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/types.js +66 -0
- package/packages/sdk/dist/communicate/types.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +35 -5
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +81 -7
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +14 -1
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +95 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +11 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
- package/packages/sdk/examples/communicate/pi_example.ts +8 -0
- package/packages/sdk/package.json +48 -2
- package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
- package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
- package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
- package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
- package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
- package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
- package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
- package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
- package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
- package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
- package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
- package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
- package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
- package/packages/sdk/src/communicate/a2a-server.ts +277 -0
- package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
- package/packages/sdk/src/communicate/a2a-types.ts +338 -0
- package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
- package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
- package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
- package/packages/sdk/src/communicate/adapters/index.ts +6 -0
- package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
- package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
- package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
- package/packages/sdk/src/communicate/core.ts +157 -0
- package/packages/sdk/src/communicate/index.ts +3 -0
- package/packages/sdk/src/communicate/transport.ts +489 -0
- package/packages/sdk/src/communicate/types.ts +106 -0
- package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
- package/packages/sdk/src/workflows/builder.ts +97 -9
- package/packages/sdk/src/workflows/cli.ts +16 -1
- package/packages/sdk/src/workflows/runner.ts +110 -1
- package/packages/sdk/src/workflows/types.ts +14 -0
- package/packages/sdk/tsconfig.build.json +1 -7
- package/packages/sdk/tsconfig.json +1 -7
- package/packages/sdk-py/README.md +67 -25
- package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
- package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
- package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
- package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
- package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
- package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
- package/packages/sdk-py/pyproject.toml +12 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
- package/packages/sdk-py/src/agent_relay/builder.py +65 -26
- package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
- package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
- package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
- package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
- package/packages/sdk-py/src/agent_relay/types.py +2 -1
- package/packages/sdk-py/tests/communicate/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
- package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
- package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
- package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
- package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
- package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
- package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
- package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
- package/packages/sdk-py/tests/communicate/conftest.py +555 -0
- package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
- package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
- package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
- package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
- package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
- package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
- package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
- package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
- package/packages/sdk-py/tests/communicate/test_core.py +331 -0
- package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
- package/packages/sdk-py/tests/communicate/test_types.py +285 -0
- package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.js +0 -305
- package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.js +0 -205
- package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.js +0 -20
- package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
- package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.js +0 -357
- package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
- package/packages/sdk-py/agent_relay/__init__.py +0 -21
- package/packages/sdk-py/agent_relay/models.py +0 -398
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""Tests for the Claude Agent SDK Python adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import sys
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
# Mock claude_agent_sdk module before it's imported in the adapter
|
|
12
|
+
claude_mock = MagicMock()
|
|
13
|
+
sys.modules["claude_agent_sdk"] = claude_mock
|
|
14
|
+
sys.modules["claude_agent_sdk.types"] = claude_mock.types
|
|
15
|
+
|
|
16
|
+
class MockHookResult:
|
|
17
|
+
def __init__(self, system_message=None, should_continue=False):
|
|
18
|
+
self.system_message = system_message
|
|
19
|
+
self.should_continue = should_continue
|
|
20
|
+
|
|
21
|
+
claude_mock.types.HookResult = MockHookResult
|
|
22
|
+
|
|
23
|
+
def _adapter_module():
|
|
24
|
+
import importlib
|
|
25
|
+
# Ensure module is reloaded to pick up mocks if needed
|
|
26
|
+
if "agent_relay.communicate.adapters.claude_sdk" in sys.modules:
|
|
27
|
+
importlib.reload(sys.modules["agent_relay.communicate.adapters.claude_sdk"])
|
|
28
|
+
return importlib.import_module("agent_relay.communicate.adapters.claude_sdk")
|
|
29
|
+
|
|
30
|
+
@pytest.fixture
|
|
31
|
+
def mock_relay():
|
|
32
|
+
relay = MagicMock()
|
|
33
|
+
relay.agent_name = "TestAgent"
|
|
34
|
+
relay.inbox = AsyncMock(return_value=[])
|
|
35
|
+
return relay
|
|
36
|
+
|
|
37
|
+
@pytest.fixture
|
|
38
|
+
def mock_options():
|
|
39
|
+
# Mocking ClaudeAgentOptions structure
|
|
40
|
+
class MockOptions:
|
|
41
|
+
def __init__(self):
|
|
42
|
+
self.mcp_servers = []
|
|
43
|
+
self.hooks = MagicMock(spec=["post_tool_use", "stop"])
|
|
44
|
+
self.hooks.post_tool_use = None
|
|
45
|
+
self.hooks.stop = None
|
|
46
|
+
return MockOptions()
|
|
47
|
+
|
|
48
|
+
def test_on_relay_injects_mcp_server(mock_relay, mock_options):
|
|
49
|
+
adapter = _adapter_module()
|
|
50
|
+
|
|
51
|
+
result = adapter.on_relay("TestAgent", mock_options, relay=mock_relay)
|
|
52
|
+
|
|
53
|
+
assert result is mock_options
|
|
54
|
+
assert len(mock_options.mcp_servers) == 1
|
|
55
|
+
server = mock_options.mcp_servers[0]
|
|
56
|
+
assert server["name"] == "relaycast"
|
|
57
|
+
# It should probably have some command/args for the relaycast MCP server
|
|
58
|
+
assert "command" in server
|
|
59
|
+
|
|
60
|
+
@pytest.mark.asyncio
|
|
61
|
+
async def test_post_tool_use_hook_drains_inbox(mock_relay, mock_options):
|
|
62
|
+
adapter = _adapter_module()
|
|
63
|
+
from agent_relay.communicate.types import Message
|
|
64
|
+
|
|
65
|
+
mock_relay.inbox.return_value = [
|
|
66
|
+
Message(sender="Other", text="Hello", message_id="1")
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
adapter.on_relay("TestAgent", mock_options, relay=mock_relay)
|
|
70
|
+
post_tool_use = mock_options.hooks.post_tool_use
|
|
71
|
+
|
|
72
|
+
assert post_tool_use is not None
|
|
73
|
+
|
|
74
|
+
# Call the hook
|
|
75
|
+
hook_result = await post_tool_use()
|
|
76
|
+
|
|
77
|
+
assert mock_relay.inbox.called
|
|
78
|
+
assert hook_result is not None
|
|
79
|
+
assert "Relay message from Other" in hook_result.system_message
|
|
80
|
+
assert "Hello" in hook_result.system_message
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def test_post_tool_use_hook_returns_none_if_inbox_empty(mock_relay, mock_options):
|
|
84
|
+
adapter = _adapter_module()
|
|
85
|
+
mock_relay.inbox.return_value = []
|
|
86
|
+
|
|
87
|
+
adapter.on_relay("TestAgent", mock_options, relay=mock_relay)
|
|
88
|
+
hook_result = await mock_options.hooks.post_tool_use()
|
|
89
|
+
|
|
90
|
+
assert hook_result is None
|
|
91
|
+
|
|
92
|
+
@pytest.mark.asyncio
|
|
93
|
+
async def test_stop_hook_drains_inbox_and_continues(mock_relay, mock_options):
|
|
94
|
+
adapter = _adapter_module()
|
|
95
|
+
from agent_relay.communicate.types import Message
|
|
96
|
+
|
|
97
|
+
mock_relay.inbox.return_value = [
|
|
98
|
+
Message(sender="Other", text="Wait!", message_id="2")
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
adapter.on_relay("TestAgent", mock_options, relay=mock_relay)
|
|
102
|
+
stop_hook = mock_options.hooks.stop
|
|
103
|
+
|
|
104
|
+
assert stop_hook is not None
|
|
105
|
+
|
|
106
|
+
hook_result = await stop_hook()
|
|
107
|
+
|
|
108
|
+
assert hook_result is not None
|
|
109
|
+
assert "Relay message from Other" in hook_result.system_message
|
|
110
|
+
assert hook_result.should_continue is True
|
|
111
|
+
|
|
112
|
+
@pytest.mark.asyncio
|
|
113
|
+
async def test_stop_hook_returns_none_if_inbox_empty(mock_relay, mock_options):
|
|
114
|
+
adapter = _adapter_module()
|
|
115
|
+
mock_relay.inbox.return_value = []
|
|
116
|
+
|
|
117
|
+
adapter.on_relay("TestAgent", mock_options, relay=mock_relay)
|
|
118
|
+
hook_result = await mock_options.hooks.stop()
|
|
119
|
+
|
|
120
|
+
assert hook_result is None
|
|
121
|
+
|
|
122
|
+
@pytest.mark.asyncio
|
|
123
|
+
async def test_hooks_chaining(mock_relay, mock_options):
|
|
124
|
+
adapter = _adapter_module()
|
|
125
|
+
|
|
126
|
+
original_post_tool_use = AsyncMock(return_value=MagicMock(system_message="Original"))
|
|
127
|
+
mock_options.hooks.post_tool_use = original_post_tool_use
|
|
128
|
+
|
|
129
|
+
adapter.on_relay("TestAgent", mock_options, relay=mock_relay)
|
|
130
|
+
|
|
131
|
+
# When inbox is empty, it should return original result
|
|
132
|
+
mock_relay.inbox.return_value = []
|
|
133
|
+
hook_result = await mock_options.hooks.post_tool_use()
|
|
134
|
+
assert original_post_tool_use.called
|
|
135
|
+
assert hook_result.system_message == "Original"
|
|
136
|
+
|
|
137
|
+
# When inbox has messages, it should combine?
|
|
138
|
+
# Or as per requirement: "returns systemMessage if non-empty, None if empty"
|
|
139
|
+
# Wait, if it's chained, it should probably combine them.
|
|
140
|
+
# The requirement says: "Chaining with existing hooks (existing PostToolUse/Stop preserved)"
|
|
141
|
+
|
|
142
|
+
from agent_relay.communicate.types import Message
|
|
143
|
+
mock_relay.inbox.return_value = [Message(sender="A", text="B")]
|
|
144
|
+
|
|
145
|
+
hook_result = await mock_options.hooks.post_tool_use()
|
|
146
|
+
assert "Original" in hook_result.system_message
|
|
147
|
+
assert "Relay message from A" in hook_result.system_message
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Tests for the CrewAI Python adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from inspect import isawaitable
|
|
6
|
+
from types import ModuleType
|
|
7
|
+
import sys
|
|
8
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
def _adapter_module():
|
|
13
|
+
import importlib
|
|
14
|
+
if "agent_relay.communicate.adapters.crewai" in sys.modules:
|
|
15
|
+
importlib.reload(sys.modules["agent_relay.communicate.adapters.crewai"])
|
|
16
|
+
return importlib.import_module("agent_relay.communicate.adapters.crewai")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _install_crewai_tools(monkeypatch):
|
|
20
|
+
crewai_module = ModuleType("crewai")
|
|
21
|
+
crewai_tools_module = ModuleType("crewai.tools")
|
|
22
|
+
|
|
23
|
+
def mock_tool_decorator(func):
|
|
24
|
+
tool = MagicMock()
|
|
25
|
+
tool.__name__ = func.__name__
|
|
26
|
+
tool.func = func
|
|
27
|
+
return tool
|
|
28
|
+
|
|
29
|
+
crewai_tools_module.tool = mock_tool_decorator
|
|
30
|
+
crewai_module.tools = crewai_tools_module
|
|
31
|
+
monkeypatch.setitem(sys.modules, "crewai", crewai_module)
|
|
32
|
+
monkeypatch.setitem(sys.modules, "crewai.tools", crewai_tools_module)
|
|
33
|
+
return crewai_tools_module
|
|
34
|
+
|
|
35
|
+
@pytest.fixture
|
|
36
|
+
def mock_relay():
|
|
37
|
+
relay = MagicMock()
|
|
38
|
+
relay.agent_name = "TestAgent"
|
|
39
|
+
relay.inbox = AsyncMock(return_value=[])
|
|
40
|
+
relay.inbox_sync = MagicMock(return_value=[])
|
|
41
|
+
relay.send = AsyncMock()
|
|
42
|
+
relay.post = AsyncMock()
|
|
43
|
+
relay.agents = AsyncMock(return_value=[])
|
|
44
|
+
return relay
|
|
45
|
+
|
|
46
|
+
@pytest.fixture
|
|
47
|
+
def mock_agent():
|
|
48
|
+
agent = MagicMock()
|
|
49
|
+
agent.tools = []
|
|
50
|
+
agent.backstory = "Expert researcher."
|
|
51
|
+
agent.goal = "Find answers."
|
|
52
|
+
return agent
|
|
53
|
+
|
|
54
|
+
def test_on_relay_adds_tools(monkeypatch, mock_relay, mock_agent):
|
|
55
|
+
_install_crewai_tools(monkeypatch)
|
|
56
|
+
adapter = _adapter_module()
|
|
57
|
+
|
|
58
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
59
|
+
|
|
60
|
+
tools = {t.__name__: t for t in mock_agent.tools}
|
|
61
|
+
assert "relay_send" in tools
|
|
62
|
+
assert "relay_inbox" in tools
|
|
63
|
+
|
|
64
|
+
@pytest.mark.asyncio
|
|
65
|
+
async def test_tool_execution(monkeypatch, mock_relay, mock_agent):
|
|
66
|
+
_install_crewai_tools(monkeypatch)
|
|
67
|
+
adapter = _adapter_module()
|
|
68
|
+
|
|
69
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
70
|
+
tools = {t.__name__: t for t in mock_agent.tools}
|
|
71
|
+
|
|
72
|
+
# Test relay_send
|
|
73
|
+
mock_relay.send = AsyncMock()
|
|
74
|
+
await tools["relay_send"].func("Alice", "Hi")
|
|
75
|
+
mock_relay.send.assert_called_with("Alice", "Hi")
|
|
76
|
+
|
|
77
|
+
# Test relay_inbox
|
|
78
|
+
from agent_relay.communicate.types import Message
|
|
79
|
+
mock_relay.inbox.return_value = [Message(sender="Bob", text="Hey")]
|
|
80
|
+
inbox_res = await tools["relay_inbox"].func()
|
|
81
|
+
assert "Bob: Hey" in inbox_res
|
|
82
|
+
|
|
83
|
+
# Test relay_post
|
|
84
|
+
mock_relay.post = AsyncMock()
|
|
85
|
+
await tools["relay_post"].func("general", "Update")
|
|
86
|
+
mock_relay.post.assert_called_with("general", "Update")
|
|
87
|
+
|
|
88
|
+
# Test relay_agents
|
|
89
|
+
mock_relay.agents.return_value = ["Alice", "Bob"]
|
|
90
|
+
agents_res = await tools["relay_agents"].func()
|
|
91
|
+
assert "Alice, Bob" in agents_res
|
|
92
|
+
|
|
93
|
+
@pytest.mark.asyncio
|
|
94
|
+
async def test_backstory_wrapping(monkeypatch, mock_relay, mock_agent):
|
|
95
|
+
_install_crewai_tools(monkeypatch)
|
|
96
|
+
adapter = _adapter_module()
|
|
97
|
+
from agent_relay.communicate.types import Message
|
|
98
|
+
|
|
99
|
+
msg = Message(sender="Other", text="Crew message", message_id="1")
|
|
100
|
+
mock_relay.inbox.return_value = [msg]
|
|
101
|
+
mock_relay.inbox_sync.return_value = [msg]
|
|
102
|
+
|
|
103
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
104
|
+
|
|
105
|
+
backstory = mock_agent.backstory
|
|
106
|
+
if isawaitable(backstory):
|
|
107
|
+
backstory = await backstory
|
|
108
|
+
elif callable(backstory):
|
|
109
|
+
backstory = backstory()
|
|
110
|
+
|
|
111
|
+
assert "Expert researcher." in backstory
|
|
112
|
+
assert "Other: Crew message" in backstory
|
|
113
|
+
|
|
114
|
+
@pytest.mark.asyncio
|
|
115
|
+
async def test_backstory_wrapping_no_messages(monkeypatch, mock_relay, mock_agent):
|
|
116
|
+
"""When inbox is empty, backstory is just the original."""
|
|
117
|
+
_install_crewai_tools(monkeypatch)
|
|
118
|
+
adapter = _adapter_module()
|
|
119
|
+
|
|
120
|
+
mock_relay.inbox_sync.return_value = []
|
|
121
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
122
|
+
|
|
123
|
+
backstory = mock_agent.backstory
|
|
124
|
+
if isawaitable(backstory):
|
|
125
|
+
backstory = await backstory
|
|
126
|
+
elif callable(backstory):
|
|
127
|
+
backstory = backstory()
|
|
128
|
+
assert backstory == "Expert researcher."
|
|
129
|
+
|
|
130
|
+
def test_on_relay_returns_agent(monkeypatch, mock_relay, mock_agent):
|
|
131
|
+
"""on_relay() returns the modified agent."""
|
|
132
|
+
_install_crewai_tools(monkeypatch)
|
|
133
|
+
adapter = _adapter_module()
|
|
134
|
+
|
|
135
|
+
result = adapter.on_relay(mock_agent, mock_relay)
|
|
136
|
+
assert result is mock_agent
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Tests for the Google ADK Python adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
import sys
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
def _adapter_module():
|
|
12
|
+
import importlib
|
|
13
|
+
if "agent_relay.communicate.adapters.google_adk" in sys.modules:
|
|
14
|
+
importlib.reload(sys.modules["agent_relay.communicate.adapters.google_adk"])
|
|
15
|
+
return importlib.import_module("agent_relay.communicate.adapters.google_adk")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _install_google_modules(monkeypatch):
|
|
19
|
+
google_module = ModuleType("google")
|
|
20
|
+
google_adk_module = ModuleType("google.adk")
|
|
21
|
+
google_adk_agents_module = ModuleType("google.adk.agents")
|
|
22
|
+
google_genai_module = ModuleType("google.genai")
|
|
23
|
+
google_genai_types_module = ModuleType("google.genai.types")
|
|
24
|
+
|
|
25
|
+
class Part:
|
|
26
|
+
def __init__(self, text: str):
|
|
27
|
+
self.text = text
|
|
28
|
+
|
|
29
|
+
def __repr__(self) -> str:
|
|
30
|
+
return f"Part(text={self.text!r})"
|
|
31
|
+
|
|
32
|
+
class Content:
|
|
33
|
+
def __init__(self, role: str, parts: list[Part]):
|
|
34
|
+
self.role = role
|
|
35
|
+
self.parts = parts
|
|
36
|
+
|
|
37
|
+
def __repr__(self) -> str:
|
|
38
|
+
return f"Content(role={self.role!r}, parts={self.parts!r})"
|
|
39
|
+
|
|
40
|
+
google_module.adk = google_adk_module
|
|
41
|
+
google_module.genai = google_genai_module
|
|
42
|
+
google_adk_module.agents = google_adk_agents_module
|
|
43
|
+
google_genai_module.types = google_genai_types_module
|
|
44
|
+
google_genai_types_module.Content = Content
|
|
45
|
+
google_genai_types_module.Part = Part
|
|
46
|
+
|
|
47
|
+
monkeypatch.setitem(sys.modules, "google", google_module)
|
|
48
|
+
monkeypatch.setitem(sys.modules, "google.adk", google_adk_module)
|
|
49
|
+
monkeypatch.setitem(sys.modules, "google.adk.agents", google_adk_agents_module)
|
|
50
|
+
monkeypatch.setitem(sys.modules, "google.genai", google_genai_module)
|
|
51
|
+
monkeypatch.setitem(sys.modules, "google.genai.types", google_genai_types_module)
|
|
52
|
+
|
|
53
|
+
@pytest.fixture
|
|
54
|
+
def mock_relay():
|
|
55
|
+
relay = MagicMock()
|
|
56
|
+
relay.agent_name = "TestAgent"
|
|
57
|
+
relay.inbox = AsyncMock(return_value=[])
|
|
58
|
+
relay.send = AsyncMock()
|
|
59
|
+
relay.post = AsyncMock()
|
|
60
|
+
relay.agents = AsyncMock()
|
|
61
|
+
return relay
|
|
62
|
+
|
|
63
|
+
@pytest.fixture
|
|
64
|
+
def mock_agent():
|
|
65
|
+
agent = MagicMock()
|
|
66
|
+
agent.tools = []
|
|
67
|
+
agent.before_model_callback = None
|
|
68
|
+
return agent
|
|
69
|
+
|
|
70
|
+
def test_on_relay_adds_tools(monkeypatch, mock_relay, mock_agent):
|
|
71
|
+
_install_google_modules(monkeypatch)
|
|
72
|
+
adapter = _adapter_module()
|
|
73
|
+
|
|
74
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
75
|
+
|
|
76
|
+
tool_names = [t.__name__ for t in mock_agent.tools]
|
|
77
|
+
assert "relay_send" in tool_names
|
|
78
|
+
assert "relay_inbox" in tool_names
|
|
79
|
+
assert "relay_post" in tool_names
|
|
80
|
+
assert "relay_agents" in tool_names
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def test_before_model_callback_drains_inbox(monkeypatch, mock_relay, mock_agent):
|
|
84
|
+
_install_google_modules(monkeypatch)
|
|
85
|
+
adapter = _adapter_module()
|
|
86
|
+
from agent_relay.communicate.types import Message
|
|
87
|
+
|
|
88
|
+
mock_relay.inbox.return_value = [
|
|
89
|
+
Message(sender="Other", text="Hello", message_id="1")
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
93
|
+
callback = mock_agent.before_model_callback
|
|
94
|
+
|
|
95
|
+
assert callback is not None
|
|
96
|
+
|
|
97
|
+
# Mock LLM request object
|
|
98
|
+
llm_request = MagicMock()
|
|
99
|
+
llm_request.contents = []
|
|
100
|
+
|
|
101
|
+
# Call the callback
|
|
102
|
+
await callback(llm_request)
|
|
103
|
+
|
|
104
|
+
assert mock_relay.inbox.called
|
|
105
|
+
assert len(llm_request.contents) == 1
|
|
106
|
+
content = llm_request.contents[0]
|
|
107
|
+
assert content.role == "user"
|
|
108
|
+
assert content.parts[0].text == "[Relay] Other: Hello"
|
|
109
|
+
|
|
110
|
+
@pytest.mark.asyncio
|
|
111
|
+
async def test_before_model_callback_chains_existing(monkeypatch, mock_relay, mock_agent):
|
|
112
|
+
_install_google_modules(monkeypatch)
|
|
113
|
+
adapter = _adapter_module()
|
|
114
|
+
|
|
115
|
+
original_callback = AsyncMock()
|
|
116
|
+
mock_agent.before_model_callback = original_callback
|
|
117
|
+
|
|
118
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
119
|
+
|
|
120
|
+
llm_request = MagicMock()
|
|
121
|
+
llm_request.contents = []
|
|
122
|
+
|
|
123
|
+
await mock_agent.before_model_callback(llm_request)
|
|
124
|
+
|
|
125
|
+
assert original_callback.called
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Tests for the OpenAI Agents Python adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from types import ModuleType
|
|
6
|
+
import sys
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
def _adapter_module():
|
|
12
|
+
import importlib
|
|
13
|
+
if "agent_relay.communicate.adapters.openai_agents" in sys.modules:
|
|
14
|
+
importlib.reload(sys.modules["agent_relay.communicate.adapters.openai_agents"])
|
|
15
|
+
return importlib.import_module("agent_relay.communicate.adapters.openai_agents")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _install_agents_module(monkeypatch):
|
|
19
|
+
agents_module = ModuleType("agents")
|
|
20
|
+
agents_module.Agent = type("Agent", (), {})
|
|
21
|
+
agents_module.function_tool = MagicMock(side_effect=lambda func: func)
|
|
22
|
+
monkeypatch.setitem(sys.modules, "agents", agents_module)
|
|
23
|
+
return agents_module
|
|
24
|
+
|
|
25
|
+
@pytest.fixture
|
|
26
|
+
def mock_relay():
|
|
27
|
+
relay = MagicMock()
|
|
28
|
+
relay.agent_name = "TestAgent"
|
|
29
|
+
relay.inbox = AsyncMock(return_value=[])
|
|
30
|
+
relay.peek = AsyncMock(return_value=[])
|
|
31
|
+
return relay
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def mock_agent():
|
|
35
|
+
agent = MagicMock()
|
|
36
|
+
agent.tools = []
|
|
37
|
+
agent.instructions = "Be helpful."
|
|
38
|
+
return agent
|
|
39
|
+
|
|
40
|
+
def test_on_relay_adds_tools(monkeypatch, mock_relay, mock_agent):
|
|
41
|
+
agents_module = _install_agents_module(monkeypatch)
|
|
42
|
+
adapter = _adapter_module()
|
|
43
|
+
|
|
44
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
45
|
+
|
|
46
|
+
assert agents_module.function_tool.call_count == 4
|
|
47
|
+
called_args = [call.args[0].__name__ for call in agents_module.function_tool.call_args_list]
|
|
48
|
+
assert "relay_send" in called_args
|
|
49
|
+
|
|
50
|
+
@pytest.mark.asyncio
|
|
51
|
+
async def test_instructions_wrapping_string(monkeypatch, mock_relay, mock_agent):
|
|
52
|
+
_install_agents_module(monkeypatch)
|
|
53
|
+
adapter = _adapter_module()
|
|
54
|
+
from agent_relay.communicate.types import Message
|
|
55
|
+
|
|
56
|
+
mock_relay.peek.return_value = [
|
|
57
|
+
Message(sender="Other", text="Hello", message_id="1")
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
61
|
+
|
|
62
|
+
assert callable(mock_agent.instructions)
|
|
63
|
+
|
|
64
|
+
result = await mock_agent.instructions()
|
|
65
|
+
assert "Be helpful." in result
|
|
66
|
+
assert "Other: Hello" in result
|
|
67
|
+
|
|
68
|
+
@pytest.mark.asyncio
|
|
69
|
+
async def test_instructions_wrapping_callable(monkeypatch, mock_relay, mock_agent):
|
|
70
|
+
_install_agents_module(monkeypatch)
|
|
71
|
+
adapter = _adapter_module()
|
|
72
|
+
|
|
73
|
+
original_instructions = MagicMock(return_value="Original context.")
|
|
74
|
+
mock_agent.instructions = original_instructions
|
|
75
|
+
|
|
76
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
77
|
+
|
|
78
|
+
result = await mock_agent.instructions()
|
|
79
|
+
assert "Original context." in result
|
|
80
|
+
assert original_instructions.called
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@pytest.mark.asyncio
|
|
84
|
+
async def test_instructions_wrapping_async_callable(monkeypatch, mock_relay, mock_agent):
|
|
85
|
+
_install_agents_module(monkeypatch)
|
|
86
|
+
adapter = _adapter_module()
|
|
87
|
+
from agent_relay.communicate.types import Message
|
|
88
|
+
|
|
89
|
+
async def original_instructions():
|
|
90
|
+
return "Async context."
|
|
91
|
+
|
|
92
|
+
mock_agent.instructions = original_instructions
|
|
93
|
+
mock_relay.peek.return_value = [Message(sender="Other", text="Hello", message_id="1")]
|
|
94
|
+
|
|
95
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
96
|
+
|
|
97
|
+
result = await mock_agent.instructions()
|
|
98
|
+
assert result.startswith("\n\nNew messages from other agents:\n Other: Hello\n")
|
|
99
|
+
assert result.endswith("\n\nAsync context.")
|