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,270 @@
1
+ """Tests for the Pi RPC Python adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pytest
9
+
10
+
11
+ def _adapter_module():
12
+ import importlib
13
+ import sys
14
+
15
+ if "agent_relay.communicate.adapters.pi" in sys.modules:
16
+ importlib.reload(sys.modules["agent_relay.communicate.adapters.pi"])
17
+ return importlib.import_module("agent_relay.communicate.adapters.pi")
18
+
19
+
20
+ @pytest.fixture
21
+ def mock_relay():
22
+ relay = MagicMock()
23
+ relay.agent_name = "PiTester"
24
+ relay.on_message = MagicMock(return_value=lambda: None)
25
+ return relay
26
+
27
+
28
+ def _make_mock_proc():
29
+ proc = MagicMock()
30
+ proc.stdin = MagicMock()
31
+ proc.stdout = iter([])
32
+ proc.stderr = MagicMock()
33
+ proc.poll = MagicMock(return_value=None)
34
+ proc.terminate = MagicMock()
35
+ proc.wait = MagicMock()
36
+ return proc
37
+
38
+
39
+ class TestOnRelay:
40
+ """Test the on_relay() function."""
41
+
42
+ @patch("subprocess.Popen")
43
+ def test_spawns_pi_in_rpc_mode(self, mock_popen, mock_relay):
44
+ adapter = _adapter_module()
45
+ mock_popen.return_value = _make_mock_proc()
46
+
47
+ adapter.on_relay("PiTester", relay=mock_relay)
48
+
49
+ mock_popen.assert_called_once()
50
+ cmd = mock_popen.call_args[0][0]
51
+ assert "pi" in cmd
52
+ assert "--mode" in cmd
53
+ assert "rpc" in cmd
54
+ assert "--no-session" in cmd
55
+
56
+ @patch("subprocess.Popen")
57
+ def test_passes_model_and_provider_flags(self, mock_popen, mock_relay):
58
+ adapter = _adapter_module()
59
+ mock_popen.return_value = _make_mock_proc()
60
+
61
+ adapter.on_relay(
62
+ "PiTester",
63
+ config={"model": "claude-sonnet", "provider": "anthropic"},
64
+ relay=mock_relay,
65
+ )
66
+
67
+ cmd = mock_popen.call_args[0][0]
68
+ assert "--model" in cmd
69
+ assert "claude-sonnet" in cmd
70
+ assert "--provider" in cmd
71
+ assert "anthropic" in cmd
72
+
73
+ @patch("subprocess.Popen")
74
+ def test_returns_pi_rpc_session(self, mock_popen, mock_relay):
75
+ adapter = _adapter_module()
76
+ mock_popen.return_value = _make_mock_proc()
77
+
78
+ session = adapter.on_relay("PiTester", relay=mock_relay)
79
+
80
+ assert isinstance(session, adapter.PiRpcSession)
81
+
82
+ @patch("subprocess.Popen")
83
+ def test_registers_relay_on_message_callback(self, mock_popen, mock_relay):
84
+ adapter = _adapter_module()
85
+ mock_popen.return_value = _make_mock_proc()
86
+
87
+ adapter.on_relay("PiTester", relay=mock_relay)
88
+
89
+ assert mock_relay.on_message.called
90
+
91
+
92
+ class TestPiRpcSession:
93
+ """Test the PiRpcSession class."""
94
+
95
+ def test_send_command_writes_jsonl(self, mock_relay):
96
+ adapter = _adapter_module()
97
+ proc = _make_mock_proc()
98
+ session = adapter.PiRpcSession(proc, mock_relay)
99
+
100
+ session.send_command({"type": "prompt", "message": "Hello"})
101
+
102
+ proc.stdin.write.assert_called_once()
103
+ written = proc.stdin.write.call_args[0][0]
104
+ parsed = json.loads(written.strip())
105
+ assert parsed["type"] == "prompt"
106
+ assert parsed["message"] == "Hello"
107
+ proc.stdin.flush.assert_called_once()
108
+
109
+ def test_prompt_sends_prompt_command(self, mock_relay):
110
+ adapter = _adapter_module()
111
+ proc = _make_mock_proc()
112
+ session = adapter.PiRpcSession(proc, mock_relay)
113
+
114
+ session.prompt("Build a feature")
115
+
116
+ written = proc.stdin.write.call_args[0][0]
117
+ parsed = json.loads(written.strip())
118
+ assert parsed["type"] == "prompt"
119
+ assert parsed["message"] == "Build a feature"
120
+ assert "streamingBehavior" not in parsed
121
+
122
+ def test_steer_sends_steer_command(self, mock_relay):
123
+ adapter = _adapter_module()
124
+ proc = _make_mock_proc()
125
+ session = adapter.PiRpcSession(proc, mock_relay)
126
+
127
+ session.steer("Change direction")
128
+
129
+ written = proc.stdin.write.call_args[0][0]
130
+ parsed = json.loads(written.strip())
131
+ assert parsed["type"] == "prompt"
132
+ assert parsed["message"] == "Change direction"
133
+ assert parsed["streamingBehavior"] == "steer"
134
+
135
+ def test_follow_up_sends_follow_up_command(self, mock_relay):
136
+ adapter = _adapter_module()
137
+ proc = _make_mock_proc()
138
+ session = adapter.PiRpcSession(proc, mock_relay)
139
+
140
+ session.follow_up("Next task")
141
+
142
+ written = proc.stdin.write.call_args[0][0]
143
+ parsed = json.loads(written.strip())
144
+ assert parsed["type"] == "prompt"
145
+ assert parsed["message"] == "Next task"
146
+ assert parsed["streamingBehavior"] == "followUp"
147
+
148
+ def test_abort_sends_abort_command(self, mock_relay):
149
+ adapter = _adapter_module()
150
+ proc = _make_mock_proc()
151
+ session = adapter.PiRpcSession(proc, mock_relay)
152
+
153
+ session.abort()
154
+
155
+ written = proc.stdin.write.call_args[0][0]
156
+ parsed = json.loads(written.strip())
157
+ assert parsed["type"] == "abort"
158
+
159
+ def test_close_terminates_subprocess(self, mock_relay):
160
+ adapter = _adapter_module()
161
+ proc = _make_mock_proc()
162
+ session = adapter.PiRpcSession(proc, mock_relay)
163
+
164
+ session.close()
165
+
166
+ proc.terminate.assert_called_once()
167
+ proc.wait.assert_called_once()
168
+
169
+ def test_close_kills_on_timeout(self, mock_relay):
170
+ import subprocess as sp
171
+
172
+ adapter = _adapter_module()
173
+ proc = _make_mock_proc()
174
+ proc.wait.side_effect = sp.TimeoutExpired(cmd="pi", timeout=5)
175
+ session = adapter.PiRpcSession(proc, mock_relay)
176
+
177
+ session.close()
178
+
179
+ proc.terminate.assert_called_once()
180
+ proc.kill.assert_called_once()
181
+
182
+ def test_close_is_idempotent(self, mock_relay):
183
+ adapter = _adapter_module()
184
+ proc = _make_mock_proc()
185
+ session = adapter.PiRpcSession(proc, mock_relay)
186
+
187
+ session.close()
188
+ session.close()
189
+
190
+ proc.terminate.assert_called_once()
191
+
192
+ def test_close_unsubscribes_relay(self, mock_relay):
193
+ adapter = _adapter_module()
194
+ proc = _make_mock_proc()
195
+ unsub = MagicMock()
196
+ session = adapter.PiRpcSession(proc, mock_relay)
197
+ session._unsubscribe = unsub
198
+
199
+ session.close()
200
+
201
+ unsub.assert_called_once()
202
+
203
+
204
+ class TestRelayMessageRouting:
205
+ """Test that incoming relay messages are routed to Pi."""
206
+
207
+ @patch("subprocess.Popen")
208
+ def test_relay_message_routes_follow_up_when_idle(self, mock_popen, mock_relay):
209
+ adapter = _adapter_module()
210
+ proc = _make_mock_proc()
211
+ mock_popen.return_value = proc
212
+
213
+ session = adapter.on_relay("PiTester", relay=mock_relay)
214
+ session._is_streaming = False
215
+
216
+ callback = mock_relay.on_message.call_args[0][0]
217
+ from agent_relay.communicate.types import Message
218
+
219
+ callback(Message(sender="Lead", text="Need status"))
220
+
221
+ written = proc.stdin.write.call_args[0][0]
222
+ parsed = json.loads(written.strip())
223
+ assert parsed["streamingBehavior"] == "followUp"
224
+ assert "Lead" in parsed["message"]
225
+ assert "Need status" in parsed["message"]
226
+
227
+ @patch("subprocess.Popen")
228
+ def test_relay_message_routes_steer_when_streaming(self, mock_popen, mock_relay):
229
+ adapter = _adapter_module()
230
+ proc = _make_mock_proc()
231
+ mock_popen.return_value = proc
232
+
233
+ session = adapter.on_relay("PiTester", relay=mock_relay)
234
+ session._is_streaming = True
235
+
236
+ callback = mock_relay.on_message.call_args[0][0]
237
+ from agent_relay.communicate.types import Message
238
+
239
+ callback(Message(sender="Review", text="Waiting on Gate 2.3"))
240
+
241
+ written = proc.stdin.write.call_args[0][0]
242
+ parsed = json.loads(written.strip())
243
+ assert parsed["streamingBehavior"] == "steer"
244
+ assert "Review" in parsed["message"]
245
+ assert "Waiting on Gate 2.3" in parsed["message"]
246
+
247
+
248
+ class TestEventHandling:
249
+ """Test stdout event processing."""
250
+
251
+ def test_on_event_registers_callback(self, mock_relay):
252
+ adapter = _adapter_module()
253
+ proc = _make_mock_proc()
254
+ session = adapter.PiRpcSession(proc, mock_relay)
255
+
256
+ events = []
257
+ session.on_event(lambda e: events.append(e))
258
+
259
+ assert len(session._event_callbacks) == 1
260
+
261
+ def test_on_event_returns_unsubscribe(self, mock_relay):
262
+ adapter = _adapter_module()
263
+ proc = _make_mock_proc()
264
+ session = adapter.PiRpcSession(proc, mock_relay)
265
+
266
+ unsub = session.on_event(lambda e: None)
267
+ assert len(session._event_callbacks) == 1
268
+
269
+ unsub()
270
+ assert len(session._event_callbacks) == 0
@@ -0,0 +1,113 @@
1
+ """Tests for the Swarms 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 swarms before it's imported in the adapter
12
+ swarms_mock = MagicMock()
13
+ sys.modules["swarms"] = swarms_mock
14
+
15
+ def _adapter_module():
16
+ import importlib
17
+ if "agent_relay.communicate.adapters.swarms" in sys.modules:
18
+ importlib.reload(sys.modules["agent_relay.communicate.adapters.swarms"])
19
+ return importlib.import_module("agent_relay.communicate.adapters.swarms")
20
+
21
+ @pytest.fixture
22
+ def mock_relay():
23
+ relay = MagicMock()
24
+ relay.agent_name = "TestAgent"
25
+ relay.inbox = AsyncMock(return_value=[])
26
+ relay.send = AsyncMock()
27
+ relay.post = AsyncMock()
28
+ relay.agents = AsyncMock(return_value=[])
29
+ relay.on_message = MagicMock(return_value=lambda: None)
30
+ return relay
31
+
32
+ @pytest.fixture
33
+ def mock_agent():
34
+ agent = MagicMock()
35
+ agent.tools = []
36
+ agent.receive_message = MagicMock()
37
+ return agent
38
+
39
+ def test_on_relay_adds_tools(mock_relay, mock_agent):
40
+ adapter = _adapter_module()
41
+
42
+ adapter.on_relay(mock_agent, mock_relay)
43
+
44
+ tools = {t.__name__: t for t in mock_agent.tools}
45
+ assert "relay_send" in tools
46
+ assert "relay_inbox" in tools
47
+ assert "relay_post" in tools
48
+ assert "relay_agents" in tools
49
+
50
+ @pytest.mark.asyncio
51
+ async def test_tool_execution(mock_relay, mock_agent):
52
+ adapter = _adapter_module()
53
+ adapter.on_relay(mock_agent, mock_relay)
54
+ tools = {t.__name__: t for t in mock_agent.tools}
55
+
56
+ # Test relay_send
57
+ mock_relay.send = AsyncMock()
58
+ await tools["relay_send"]("Alice", "Hi")
59
+ mock_relay.send.assert_called_with("Alice", "Hi")
60
+
61
+ # Test relay_inbox
62
+ from agent_relay.communicate.types import Message
63
+ mock_relay.inbox.return_value = [Message(sender="Bob", text="Hey")]
64
+ inbox_res = await tools["relay_inbox"]()
65
+ assert "Bob: Hey" in inbox_res
66
+
67
+ # Test relay_post
68
+ mock_relay.post = AsyncMock()
69
+ await tools["relay_post"]("general", "Update")
70
+ mock_relay.post.assert_called_with("general", "Update")
71
+
72
+ # Test relay_agents
73
+ mock_relay.agents.return_value = ["Alice", "Bob"]
74
+ agents_res = await tools["relay_agents"]()
75
+ assert "Alice, Bob" in agents_res
76
+
77
+ def test_on_relay_registers_callback(mock_relay, mock_agent):
78
+ adapter = _adapter_module()
79
+ from agent_relay.communicate.types import Message
80
+
81
+ adapter.on_relay(mock_agent, mock_relay)
82
+
83
+ assert mock_relay.on_message.called
84
+ callback = mock_relay.on_message.call_args[0][0]
85
+
86
+ # Simulate an incoming message
87
+ msg = Message(sender="Lead", text="Status?", channel="general")
88
+ callback(msg)
89
+
90
+ assert mock_agent.receive_message.called
91
+ # Swarms receive_message usually takes (sender, text)
92
+ mock_agent.receive_message.assert_called_with("Lead", "Status?")
93
+
94
+ def test_on_relay_returns_agent(mock_relay, mock_agent):
95
+ """on_relay() returns the modified agent."""
96
+ adapter = _adapter_module()
97
+ result = adapter.on_relay(mock_agent, mock_relay)
98
+ assert result is mock_agent
99
+
100
+ def test_callback_multiple_messages(mock_relay, mock_agent):
101
+ """Multiple incoming messages each trigger receive_message."""
102
+ adapter = _adapter_module()
103
+ from agent_relay.communicate.types import Message
104
+
105
+ adapter.on_relay(mock_agent, mock_relay)
106
+ callback = mock_relay.on_message.call_args[0][0]
107
+
108
+ callback(Message(sender="A", text="first"))
109
+ callback(Message(sender="B", text="second"))
110
+
111
+ assert mock_agent.receive_message.call_count == 2
112
+ mock_agent.receive_message.assert_any_call("A", "first")
113
+ mock_agent.receive_message.assert_any_call("B", "second")