agent-relay 3.2.2 → 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.
Files changed (246) hide show
  1. package/bin/agent-relay-broker-darwin-arm64 +0 -0
  2. package/bin/agent-relay-broker-darwin-x64 +0 -0
  3. package/bin/agent-relay-broker-linux-arm64 +0 -0
  4. package/bin/agent-relay-broker-linux-x64 +0 -0
  5. package/dist/index.cjs +1358 -941
  6. package/dist/src/cli/commands/agent-management.d.ts +2 -2
  7. package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
  8. package/dist/src/cli/commands/agent-management.js +41 -240
  9. package/dist/src/cli/commands/agent-management.js.map +1 -1
  10. package/dist/src/cli/commands/messaging.d.ts +1 -1
  11. package/dist/src/cli/commands/messaging.d.ts.map +1 -1
  12. package/dist/src/cli/commands/messaging.js +14 -5
  13. package/dist/src/cli/commands/messaging.js.map +1 -1
  14. package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
  15. package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
  16. package/dist/src/cli/lib/agent-management-listing.js +27 -2
  17. package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
  18. package/package.json +11 -10
  19. package/packages/acp-bridge/package.json +2 -2
  20. package/packages/config/package.json +1 -1
  21. package/packages/hooks/package.json +4 -4
  22. package/packages/memory/package.json +2 -2
  23. package/packages/openclaw/package.json +2 -2
  24. package/packages/policy/package.json +2 -2
  25. package/packages/sdk/ADAPTER_REVIEW.md +109 -0
  26. package/packages/sdk/dist/client.d.ts +66 -0
  27. package/packages/sdk/dist/client.d.ts.map +1 -1
  28. package/packages/sdk/dist/client.js +230 -0
  29. package/packages/sdk/dist/client.js.map +1 -1
  30. package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
  31. package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
  32. package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
  33. package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
  34. package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
  35. package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
  36. package/packages/sdk/dist/communicate/a2a-server.js +220 -0
  37. package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
  38. package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
  39. package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
  40. package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
  41. package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
  42. package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
  43. package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
  44. package/packages/sdk/dist/communicate/a2a-types.js +209 -0
  45. package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
  46. package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
  47. package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
  48. package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
  49. package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
  50. package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
  51. package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
  52. package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
  53. package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
  54. package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
  55. package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
  56. package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
  57. package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
  58. package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
  59. package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
  60. package/packages/sdk/dist/communicate/adapters/index.js +7 -0
  61. package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
  62. package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
  63. package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
  64. package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
  65. package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
  66. package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
  67. package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
  68. package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
  69. package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
  70. package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
  71. package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
  72. package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
  73. package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
  74. package/packages/sdk/dist/communicate/core.d.ts +58 -0
  75. package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
  76. package/packages/sdk/dist/communicate/core.js +128 -0
  77. package/packages/sdk/dist/communicate/core.js.map +1 -0
  78. package/packages/sdk/dist/communicate/index.d.ts +4 -0
  79. package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
  80. package/packages/sdk/dist/communicate/index.js +4 -0
  81. package/packages/sdk/dist/communicate/index.js.map +1 -0
  82. package/packages/sdk/dist/communicate/transport.d.ts +36 -0
  83. package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
  84. package/packages/sdk/dist/communicate/transport.js +371 -0
  85. package/packages/sdk/dist/communicate/transport.js.map +1 -0
  86. package/packages/sdk/dist/communicate/types.d.ts +58 -0
  87. package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
  88. package/packages/sdk/dist/communicate/types.js +66 -0
  89. package/packages/sdk/dist/communicate/types.js.map +1 -0
  90. package/packages/sdk/dist/workflows/builder.d.ts +35 -5
  91. package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
  92. package/packages/sdk/dist/workflows/builder.js +81 -7
  93. package/packages/sdk/dist/workflows/builder.js.map +1 -1
  94. package/packages/sdk/dist/workflows/cli.js +14 -1
  95. package/packages/sdk/dist/workflows/cli.js.map +1 -1
  96. package/packages/sdk/dist/workflows/runner.d.ts +10 -2
  97. package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
  98. package/packages/sdk/dist/workflows/runner.js +95 -1
  99. package/packages/sdk/dist/workflows/runner.js.map +1 -1
  100. package/packages/sdk/dist/workflows/types.d.ts +11 -0
  101. package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
  102. package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
  103. package/packages/sdk/examples/communicate/pi_example.ts +8 -0
  104. package/packages/sdk/package.json +48 -2
  105. package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
  106. package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
  107. package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
  108. package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
  109. package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
  110. package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
  111. package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
  112. package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
  113. package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
  114. package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
  115. package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
  116. package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
  117. package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
  118. package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
  119. package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
  120. package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
  121. package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
  122. package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
  123. package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
  124. package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
  125. package/packages/sdk/src/client.ts +301 -0
  126. package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
  127. package/packages/sdk/src/communicate/a2a-server.ts +277 -0
  128. package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
  129. package/packages/sdk/src/communicate/a2a-types.ts +338 -0
  130. package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
  131. package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
  132. package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
  133. package/packages/sdk/src/communicate/adapters/index.ts +6 -0
  134. package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
  135. package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
  136. package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
  137. package/packages/sdk/src/communicate/core.ts +157 -0
  138. package/packages/sdk/src/communicate/index.ts +3 -0
  139. package/packages/sdk/src/communicate/transport.ts +489 -0
  140. package/packages/sdk/src/communicate/types.ts +106 -0
  141. package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
  142. package/packages/sdk/src/workflows/builder.ts +97 -9
  143. package/packages/sdk/src/workflows/cli.ts +16 -1
  144. package/packages/sdk/src/workflows/runner.ts +110 -1
  145. package/packages/sdk/src/workflows/types.ts +14 -0
  146. package/packages/sdk/tsconfig.build.json +1 -7
  147. package/packages/sdk/tsconfig.json +1 -7
  148. package/packages/sdk-py/README.md +67 -25
  149. package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
  150. package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
  151. package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
  152. package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
  153. package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
  154. package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
  155. package/packages/sdk-py/pyproject.toml +12 -1
  156. package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
  157. package/packages/sdk-py/src/agent_relay/builder.py +65 -26
  158. package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
  159. package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
  160. package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
  161. package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
  162. package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
  163. package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
  164. package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
  165. package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
  166. package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
  167. package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
  168. package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
  169. package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
  170. package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
  171. package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
  172. package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
  173. package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
  174. package/packages/sdk-py/src/agent_relay/types.py +2 -1
  175. package/packages/sdk-py/tests/communicate/__init__.py +0 -0
  176. package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
  177. package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
  178. package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
  179. package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
  180. package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
  181. package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
  182. package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
  183. package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
  184. package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
  185. package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
  186. package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
  187. package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
  188. package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
  189. package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
  190. package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
  191. package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
  192. package/packages/sdk-py/tests/communicate/conftest.py +555 -0
  193. package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
  194. package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
  195. package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
  196. package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
  197. package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
  198. package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
  199. package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
  200. package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
  201. package/packages/sdk-py/tests/communicate/test_core.py +331 -0
  202. package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
  203. package/packages/sdk-py/tests/communicate/test_types.py +285 -0
  204. package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
  205. package/packages/telemetry/package.json +1 -1
  206. package/packages/trajectory/package.json +2 -2
  207. package/packages/user-directory/package.json +2 -2
  208. package/packages/utils/package.json +2 -2
  209. package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
  210. package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
  211. package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
  212. package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
  213. package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
  214. package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
  215. package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
  216. package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
  217. package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
  218. package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
  219. package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
  220. package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
  221. package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
  222. package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
  223. package/packages/sdk/dist/__tests__/facade.test.js +0 -305
  224. package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
  225. package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
  226. package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
  227. package/packages/sdk/dist/__tests__/integration.test.js +0 -205
  228. package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
  229. package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
  230. package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
  231. package/packages/sdk/dist/__tests__/pty.test.js +0 -20
  232. package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
  233. package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
  234. package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
  235. package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
  236. package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
  237. package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
  238. package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
  239. package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
  240. package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
  241. package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
  242. package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
  243. package/packages/sdk/dist/__tests__/unit.test.js +0 -357
  244. package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
  245. package/packages/sdk-py/agent_relay/__init__.py +0 -21
  246. 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.")