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,342 @@
1
+ """Tests for A2A data model types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+
7
+ import pytest
8
+
9
+ from agent_relay.communicate.a2a_types import (
10
+ A2AAgentCard,
11
+ A2AConfig,
12
+ A2AMessage,
13
+ A2APart,
14
+ A2ASkill,
15
+ A2ATask,
16
+ A2ATaskStatus,
17
+ VALID_TASK_STATES,
18
+ make_jsonrpc_error,
19
+ make_jsonrpc_request,
20
+ make_jsonrpc_response,
21
+ )
22
+
23
+
24
+ # === A2APart ===
25
+
26
+
27
+ class TestA2APart:
28
+ def test_text_part_roundtrip(self):
29
+ part = A2APart(text="hello")
30
+ d = part.to_dict()
31
+ assert d == {"text": "hello"}
32
+ restored = A2APart.from_dict(d)
33
+ assert restored.text == "hello"
34
+ assert restored.file is None
35
+ assert restored.data is None
36
+
37
+ def test_empty_part(self):
38
+ part = A2APart()
39
+ d = part.to_dict()
40
+ assert d == {}
41
+ restored = A2APart.from_dict(d)
42
+ assert restored.text is None
43
+
44
+ def test_file_part(self):
45
+ part = A2APart(file={"name": "test.txt", "bytes": "aGVsbG8="})
46
+ d = part.to_dict()
47
+ assert "file" in d
48
+ assert d["file"]["name"] == "test.txt"
49
+
50
+ def test_data_part(self):
51
+ part = A2APart(data={"key": "value"})
52
+ d = part.to_dict()
53
+ assert d["data"] == {"key": "value"}
54
+
55
+ def test_from_dict_ignores_unknown_keys(self):
56
+ part = A2APart.from_dict({"text": "hi", "unknown": True})
57
+ assert part.text == "hi"
58
+
59
+
60
+ # === A2AMessage ===
61
+
62
+
63
+ class TestA2AMessage:
64
+ def test_basic_message_roundtrip(self):
65
+ msg = A2AMessage(
66
+ role="user",
67
+ parts=[A2APart(text="hello world")],
68
+ messageId="msg-1",
69
+ )
70
+ d = msg.to_dict()
71
+ assert d["role"] == "user"
72
+ assert d["parts"] == [{"text": "hello world"}]
73
+ assert d["messageId"] == "msg-1"
74
+
75
+ restored = A2AMessage.from_dict(d)
76
+ assert restored.role == "user"
77
+ assert restored.parts[0].text == "hello world"
78
+ assert restored.messageId == "msg-1"
79
+
80
+ def test_auto_generates_message_id(self):
81
+ msg = A2AMessage(role="agent", parts=[A2APart(text="response")])
82
+ assert msg.messageId is not None
83
+ assert len(msg.messageId) > 0
84
+
85
+ def test_get_text_concatenates_parts(self):
86
+ msg = A2AMessage(
87
+ role="user",
88
+ parts=[A2APart(text="hello"), A2APart(text="world")],
89
+ )
90
+ assert msg.get_text() == "hello world"
91
+
92
+ def test_get_text_skips_non_text_parts(self):
93
+ msg = A2AMessage(
94
+ role="user",
95
+ parts=[A2APart(text="hello"), A2APart(data={"x": 1}), A2APart(text="world")],
96
+ )
97
+ assert msg.get_text() == "hello world"
98
+
99
+ def test_context_and_task_ids(self):
100
+ msg = A2AMessage(
101
+ role="user",
102
+ parts=[A2APart(text="test")],
103
+ contextId="ctx-1",
104
+ taskId="task-1",
105
+ )
106
+ d = msg.to_dict()
107
+ assert d["contextId"] == "ctx-1"
108
+ assert d["taskId"] == "task-1"
109
+
110
+ restored = A2AMessage.from_dict(d)
111
+ assert restored.contextId == "ctx-1"
112
+ assert restored.taskId == "task-1"
113
+
114
+ def test_optional_fields_omitted_when_none(self):
115
+ msg = A2AMessage(role="user", parts=[], messageId="m1")
116
+ d = msg.to_dict()
117
+ assert "contextId" not in d
118
+ assert "taskId" not in d
119
+
120
+ def test_from_dict_with_empty_parts(self):
121
+ msg = A2AMessage.from_dict({"role": "agent"})
122
+ assert msg.parts == []
123
+
124
+
125
+ # === A2ATaskStatus ===
126
+
127
+
128
+ class TestA2ATaskStatus:
129
+ def test_basic_roundtrip(self):
130
+ status = A2ATaskStatus(state="completed", timestamp="2025-01-01T00:00:00Z")
131
+ d = status.to_dict()
132
+ assert d["state"] == "completed"
133
+ assert d["timestamp"] == "2025-01-01T00:00:00Z"
134
+
135
+ restored = A2ATaskStatus.from_dict(d)
136
+ assert restored.state == "completed"
137
+
138
+ def test_auto_generates_timestamp(self):
139
+ status = A2ATaskStatus(state="submitted")
140
+ assert status.timestamp is not None
141
+
142
+ def test_with_message(self):
143
+ msg = A2AMessage(role="agent", parts=[A2APart(text="done")], messageId="m1")
144
+ status = A2ATaskStatus(state="completed", message=msg)
145
+ d = status.to_dict()
146
+ assert "message" in d
147
+ assert d["message"]["role"] == "agent"
148
+
149
+ restored = A2ATaskStatus.from_dict(d)
150
+ assert restored.message is not None
151
+ assert restored.message.role == "agent"
152
+
153
+ def test_valid_states(self):
154
+ assert "submitted" in VALID_TASK_STATES
155
+ assert "working" in VALID_TASK_STATES
156
+ assert "completed" in VALID_TASK_STATES
157
+ assert "failed" in VALID_TASK_STATES
158
+ assert "canceled" in VALID_TASK_STATES
159
+
160
+
161
+ # === A2ATask ===
162
+
163
+
164
+ class TestA2ATask:
165
+ def test_basic_roundtrip(self):
166
+ task = A2ATask(id="t1", contextId="ctx-1")
167
+ d = task.to_dict()
168
+ assert d["id"] == "t1"
169
+ assert d["contextId"] == "ctx-1"
170
+ assert d["status"]["state"] == "submitted"
171
+ assert d["messages"] == []
172
+ assert d["artifacts"] == []
173
+
174
+ restored = A2ATask.from_dict(d)
175
+ assert restored.id == "t1"
176
+ assert restored.contextId == "ctx-1"
177
+ assert restored.status.state == "submitted"
178
+
179
+ def test_with_messages(self):
180
+ msg = A2AMessage(role="user", parts=[A2APart(text="hello")], messageId="m1")
181
+ task = A2ATask(
182
+ id="t1",
183
+ status=A2ATaskStatus(state="working"),
184
+ messages=[msg],
185
+ )
186
+ d = task.to_dict()
187
+ assert len(d["messages"]) == 1
188
+ assert d["messages"][0]["parts"][0]["text"] == "hello"
189
+
190
+ restored = A2ATask.from_dict(d)
191
+ assert len(restored.messages) == 1
192
+ assert restored.messages[0].get_text() == "hello"
193
+
194
+ def test_with_artifacts(self):
195
+ task = A2ATask(
196
+ id="t1",
197
+ artifacts=[{"name": "result", "parts": [{"text": "output"}]}],
198
+ )
199
+ d = task.to_dict()
200
+ assert len(d["artifacts"]) == 1
201
+
202
+ def test_default_status(self):
203
+ task = A2ATask(id="t1")
204
+ assert task.status.state == "submitted"
205
+
206
+ def test_from_dict_without_status(self):
207
+ task = A2ATask.from_dict({"id": "t1"})
208
+ assert task.status.state == "submitted"
209
+
210
+
211
+ # === A2ASkill ===
212
+
213
+
214
+ class TestA2ASkill:
215
+ def test_roundtrip(self):
216
+ skill = A2ASkill(id="s1", name="Search", description="Search the web")
217
+ d = skill.to_dict()
218
+ assert d == {"id": "s1", "name": "Search", "description": "Search the web"}
219
+
220
+ restored = A2ASkill.from_dict(d)
221
+ assert restored.id == "s1"
222
+ assert restored.name == "Search"
223
+ assert restored.description == "Search the web"
224
+
225
+
226
+ # === A2AAgentCard ===
227
+
228
+
229
+ class TestA2AAgentCard:
230
+ def test_basic_roundtrip(self):
231
+ card = A2AAgentCard(
232
+ name="test-agent",
233
+ description="A test agent",
234
+ url="http://localhost:5000",
235
+ )
236
+ d = card.to_dict()
237
+ assert d["name"] == "test-agent"
238
+ assert d["url"] == "http://localhost:5000"
239
+ assert d["version"] == "1.0.0"
240
+ assert d["capabilities"]["streaming"] is True
241
+ assert d["defaultInputModes"] == ["text"]
242
+ assert d["defaultOutputModes"] == ["text"]
243
+
244
+ restored = A2AAgentCard.from_dict(d)
245
+ assert restored.name == "test-agent"
246
+ assert restored.url == "http://localhost:5000"
247
+
248
+ def test_with_skills(self):
249
+ card = A2AAgentCard(
250
+ name="skilled-agent",
251
+ description="Agent with skills",
252
+ url="http://localhost:5000",
253
+ skills=[A2ASkill(id="s1", name="Code", description="Write code")],
254
+ )
255
+ d = card.to_dict()
256
+ assert len(d["skills"]) == 1
257
+ assert d["skills"][0]["name"] == "Code"
258
+
259
+ restored = A2AAgentCard.from_dict(d)
260
+ assert len(restored.skills) == 1
261
+ assert restored.skills[0].name == "Code"
262
+
263
+ def test_from_dict_with_defaults(self):
264
+ card = A2AAgentCard.from_dict({
265
+ "name": "minimal",
266
+ "description": "Minimal card",
267
+ "url": "http://localhost:3000",
268
+ })
269
+ assert card.version == "1.0.0"
270
+ assert card.capabilities == {"streaming": True, "pushNotifications": False}
271
+ assert card.skills == []
272
+
273
+ def test_json_serialization(self):
274
+ card = A2AAgentCard(
275
+ name="json-test",
276
+ description="JSON test",
277
+ url="http://localhost:5000",
278
+ )
279
+ json_str = json.dumps(card.to_dict())
280
+ parsed = json.loads(json_str)
281
+ restored = A2AAgentCard.from_dict(parsed)
282
+ assert restored.name == "json-test"
283
+
284
+
285
+ # === A2AConfig ===
286
+
287
+
288
+ class TestA2AConfig:
289
+ def test_defaults(self):
290
+ config = A2AConfig()
291
+ assert config.server_port == 5000
292
+ assert config.server_host == "0.0.0.0"
293
+ assert config.target_url is None
294
+ assert config.registry == []
295
+ assert config.auth_scheme is None
296
+ assert config.auth_token is None
297
+
298
+ def test_custom_config(self):
299
+ config = A2AConfig(
300
+ server_port=8080,
301
+ target_url="http://remote-agent:5000",
302
+ registry=["http://agent1:5000", "http://agent2:5000"],
303
+ auth_scheme="bearer",
304
+ auth_token="test-token",
305
+ )
306
+ assert config.server_port == 8080
307
+ assert config.target_url == "http://remote-agent:5000"
308
+ assert len(config.registry) == 2
309
+
310
+
311
+ # === JSON-RPC helpers ===
312
+
313
+
314
+ class TestJsonRpc:
315
+ def test_make_request(self):
316
+ req = make_jsonrpc_request("message/send", {"text": "hi"}, id="req-1")
317
+ assert req["jsonrpc"] == "2.0"
318
+ assert req["method"] == "message/send"
319
+ assert req["params"] == {"text": "hi"}
320
+ assert req["id"] == "req-1"
321
+
322
+ def test_make_request_auto_id(self):
323
+ req = make_jsonrpc_request("message/send", {})
324
+ assert "id" in req
325
+ assert len(req["id"]) > 0
326
+
327
+ def test_make_response(self):
328
+ resp = make_jsonrpc_response({"status": "ok"}, "req-1")
329
+ assert resp["jsonrpc"] == "2.0"
330
+ assert resp["result"] == {"status": "ok"}
331
+ assert resp["id"] == "req-1"
332
+
333
+ def test_make_error(self):
334
+ err = make_jsonrpc_error(-32600, "Invalid request", "req-1")
335
+ assert err["jsonrpc"] == "2.0"
336
+ assert err["error"]["code"] == -32600
337
+ assert err["error"]["message"] == "Invalid request"
338
+ assert err["id"] == "req-1"
339
+
340
+ def test_make_error_with_null_id(self):
341
+ err = make_jsonrpc_error(-32700, "Parse error", None)
342
+ assert err["id"] is None
@@ -0,0 +1,67 @@
1
+ """Tests for top-level communicate auto-detection."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from types import ModuleType
7
+
8
+ import pytest
9
+
10
+
11
+ def _make_agent(module_name: str):
12
+ agent_cls = type("MockAgent", (), {})
13
+ agent_cls.__module__ = module_name
14
+ return agent_cls()
15
+
16
+
17
+ def _install_adapter_stub(monkeypatch, module_name: str, sentinel: object):
18
+ adapter_module = ModuleType(module_name)
19
+ calls: list[tuple[object, object]] = []
20
+
21
+ def fake_on_relay(agent, relay):
22
+ calls.append((agent, relay))
23
+ return sentinel
24
+
25
+ adapter_module.on_relay = fake_on_relay
26
+ monkeypatch.setitem(sys.modules, module_name, adapter_module)
27
+ return calls
28
+
29
+
30
+ def test_on_relay_is_exported_from_top_level_package():
31
+ from agent_relay import on_relay as top_level_on_relay
32
+ from agent_relay.communicate.core import on_relay as communicate_on_relay
33
+
34
+ assert top_level_on_relay is communicate_on_relay
35
+
36
+
37
+ @pytest.mark.parametrize(
38
+ ("agent_module", "adapter_module"),
39
+ [
40
+ ("agents.runtime", "agent_relay.communicate.adapters.openai_agents"),
41
+ ("google.adk.agents", "agent_relay.communicate.adapters.google_adk"),
42
+ ("agno.agent", "agent_relay.communicate.adapters.agno"),
43
+ ("swarms.agent", "agent_relay.communicate.adapters.swarms"),
44
+ ("crewai.agent", "agent_relay.communicate.adapters.crewai"),
45
+ ],
46
+ )
47
+ def test_on_relay_auto_detects_adapter(monkeypatch, agent_module: str, adapter_module: str):
48
+ from agent_relay import on_relay
49
+
50
+ relay = object()
51
+ sentinel = object()
52
+ calls = _install_adapter_stub(monkeypatch, adapter_module, sentinel)
53
+ agent = _make_agent(agent_module)
54
+
55
+ result = on_relay(agent, relay=relay)
56
+
57
+ assert result is sentinel
58
+ assert calls == [(agent, relay)]
59
+
60
+
61
+ def test_on_relay_rejects_unsupported_agent_types():
62
+ from agent_relay import on_relay
63
+
64
+ agent = _make_agent("custom.framework")
65
+
66
+ with pytest.raises(TypeError, match="Supported frameworks"):
67
+ on_relay(agent, relay=object())