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,294 @@
1
+ """A2A (Agent-to-Agent) protocol data model types."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import uuid
6
+ from dataclasses import asdict, dataclass, field
7
+ from datetime import datetime, timezone
8
+ from typing import Any
9
+
10
+
11
+ @dataclass
12
+ class A2APart:
13
+ """A single part of an A2A message (text, file, or structured data)."""
14
+
15
+ text: str | None = None
16
+ file: dict[str, Any] | None = None # FileContent — phase 2
17
+ data: dict[str, Any] | None = None # Structured data — phase 2
18
+
19
+ def to_dict(self) -> dict[str, Any]:
20
+ d: dict[str, Any] = {}
21
+ if self.text is not None:
22
+ d["text"] = self.text
23
+ if self.file is not None:
24
+ d["file"] = self.file
25
+ if self.data is not None:
26
+ d["data"] = self.data
27
+ return d
28
+
29
+ @classmethod
30
+ def from_dict(cls, d: dict[str, Any]) -> A2APart:
31
+ return cls(
32
+ text=d.get("text"),
33
+ file=d.get("file"),
34
+ data=d.get("data"),
35
+ )
36
+
37
+
38
+ @dataclass
39
+ class A2AMessage:
40
+ """An A2A protocol message."""
41
+
42
+ role: str # "user" | "agent"
43
+ parts: list[A2APart]
44
+ messageId: str | None = None
45
+ contextId: str | None = None
46
+ taskId: str | None = None
47
+
48
+ def __post_init__(self) -> None:
49
+ if self.messageId is None:
50
+ self.messageId = str(uuid.uuid4())
51
+
52
+ def to_dict(self) -> dict[str, Any]:
53
+ d: dict[str, Any] = {
54
+ "role": self.role,
55
+ "parts": [p.to_dict() for p in self.parts],
56
+ }
57
+ if self.messageId is not None:
58
+ d["messageId"] = self.messageId
59
+ if self.contextId is not None:
60
+ d["contextId"] = self.contextId
61
+ if self.taskId is not None:
62
+ d["taskId"] = self.taskId
63
+ return d
64
+
65
+ @classmethod
66
+ def from_dict(cls, d: dict[str, Any]) -> A2AMessage:
67
+ parts = [A2APart.from_dict(p) for p in d.get("parts", [])]
68
+ return cls(
69
+ role=d["role"],
70
+ parts=parts,
71
+ messageId=d.get("messageId"),
72
+ contextId=d.get("contextId"),
73
+ taskId=d.get("taskId"),
74
+ )
75
+
76
+ def get_text(self) -> str:
77
+ """Extract concatenated text from all text parts."""
78
+ return " ".join(p.text for p in self.parts if p.text)
79
+
80
+
81
+ @dataclass
82
+ class A2ATaskStatus:
83
+ """Status of an A2A task."""
84
+
85
+ state: str # "submitted" | "working" | "completed" | "failed" | "canceled"
86
+ message: A2AMessage | None = None
87
+ timestamp: str | None = None
88
+
89
+ def __post_init__(self) -> None:
90
+ if self.timestamp is None:
91
+ self.timestamp = datetime.now(timezone.utc).isoformat()
92
+
93
+ def to_dict(self) -> dict[str, Any]:
94
+ d: dict[str, Any] = {"state": self.state}
95
+ if self.message is not None:
96
+ d["message"] = self.message.to_dict()
97
+ if self.timestamp is not None:
98
+ d["timestamp"] = self.timestamp
99
+ return d
100
+
101
+ @classmethod
102
+ def from_dict(cls, d: dict[str, Any]) -> A2ATaskStatus:
103
+ msg = None
104
+ if "message" in d and d["message"] is not None:
105
+ msg = A2AMessage.from_dict(d["message"])
106
+ return cls(
107
+ state=d["state"],
108
+ message=msg,
109
+ timestamp=d.get("timestamp"),
110
+ )
111
+
112
+
113
+ VALID_TASK_STATES = {"submitted", "working", "completed", "failed", "canceled"}
114
+
115
+
116
+ @dataclass
117
+ class A2ATask:
118
+ """An A2A protocol task."""
119
+
120
+ id: str
121
+ contextId: str | None = None
122
+ status: A2ATaskStatus = field(
123
+ default_factory=lambda: A2ATaskStatus(state="submitted")
124
+ )
125
+ messages: list[A2AMessage] = field(default_factory=list)
126
+ artifacts: list[dict[str, Any]] = field(default_factory=list)
127
+
128
+ def to_dict(self) -> dict[str, Any]:
129
+ return {
130
+ "id": self.id,
131
+ "contextId": self.contextId,
132
+ "status": self.status.to_dict(),
133
+ "messages": [m.to_dict() for m in self.messages],
134
+ "artifacts": self.artifacts,
135
+ }
136
+
137
+ @classmethod
138
+ def from_dict(cls, d: dict[str, Any]) -> A2ATask:
139
+ status = A2ATaskStatus.from_dict(d["status"]) if "status" in d else A2ATaskStatus(state="submitted")
140
+ messages = [A2AMessage.from_dict(m) for m in d.get("messages", [])]
141
+ return cls(
142
+ id=d["id"],
143
+ contextId=d.get("contextId"),
144
+ status=status,
145
+ messages=messages,
146
+ artifacts=d.get("artifacts", []),
147
+ )
148
+
149
+
150
+ @dataclass
151
+ class A2ASkill:
152
+ """A skill advertised by an A2A agent."""
153
+
154
+ id: str
155
+ name: str
156
+ description: str
157
+
158
+ def to_dict(self) -> dict[str, Any]:
159
+ return {"id": self.id, "name": self.name, "description": self.description}
160
+
161
+ @classmethod
162
+ def from_dict(cls, d: dict[str, Any]) -> A2ASkill:
163
+ return cls(id=d["id"], name=d["name"], description=d["description"])
164
+
165
+
166
+ @dataclass
167
+ class A2AAgentCard:
168
+ """An A2A Agent Card describing an agent's capabilities and endpoint."""
169
+
170
+ name: str
171
+ description: str
172
+ url: str
173
+ version: str = "1.0.0"
174
+ capabilities: dict[str, Any] = field(
175
+ default_factory=lambda: {"streaming": True, "pushNotifications": False}
176
+ )
177
+ skills: list[A2ASkill] = field(default_factory=list)
178
+ defaultInputModes: list[str] = field(default_factory=lambda: ["text"])
179
+ defaultOutputModes: list[str] = field(default_factory=lambda: ["text"])
180
+
181
+ def to_dict(self) -> dict[str, Any]:
182
+ return {
183
+ "name": self.name,
184
+ "description": self.description,
185
+ "url": self.url,
186
+ "version": self.version,
187
+ "capabilities": self.capabilities,
188
+ "skills": [s.to_dict() for s in self.skills],
189
+ "defaultInputModes": self.defaultInputModes,
190
+ "defaultOutputModes": self.defaultOutputModes,
191
+ }
192
+
193
+ @classmethod
194
+ def from_dict(cls, d: dict[str, Any]) -> A2AAgentCard:
195
+ skills = [A2ASkill.from_dict(s) for s in d.get("skills", [])]
196
+ return cls(
197
+ name=d["name"],
198
+ description=d["description"],
199
+ url=d["url"],
200
+ version=d.get("version", "1.0.0"),
201
+ capabilities=d.get("capabilities", {"streaming": True, "pushNotifications": False}),
202
+ skills=skills,
203
+ defaultInputModes=d.get("defaultInputModes", ["text"]),
204
+ defaultOutputModes=d.get("defaultOutputModes", ["text"]),
205
+ )
206
+
207
+
208
+ @dataclass
209
+ class A2AConfig:
210
+ """Configuration for A2A transport."""
211
+
212
+ # Server mode
213
+ server_port: int = 5000
214
+ server_host: str = "0.0.0.0"
215
+
216
+ # Client mode
217
+ target_url: str | None = None
218
+
219
+ # Agent Card registry (known A2A agent URLs)
220
+ registry: list[str] = field(default_factory=list)
221
+
222
+ # Auth
223
+ auth_scheme: str | None = None # "bearer", "api_key", etc.
224
+ auth_token: str | None = None
225
+
226
+ # Agent metadata
227
+ agent_description: str | None = None
228
+ skills: list[A2ASkill] = field(default_factory=list)
229
+
230
+
231
+ def make_jsonrpc_request(method: str, params: dict[str, Any], id: str | int | None = None) -> dict[str, Any]:
232
+ """Build a JSON-RPC 2.0 request envelope."""
233
+ req: dict[str, Any] = {
234
+ "jsonrpc": "2.0",
235
+ "method": method,
236
+ "params": params,
237
+ }
238
+ if id is not None:
239
+ req["id"] = id
240
+ else:
241
+ req["id"] = str(uuid.uuid4())
242
+ return req
243
+
244
+
245
+ def make_jsonrpc_response(result: Any, id: str | int) -> dict[str, Any]:
246
+ """Build a JSON-RPC 2.0 success response."""
247
+ return {
248
+ "jsonrpc": "2.0",
249
+ "result": result,
250
+ "id": id,
251
+ }
252
+
253
+
254
+ def make_jsonrpc_error(code: int, message: str, id: str | int | None) -> dict[str, Any]:
255
+ """Build a JSON-RPC 2.0 error response."""
256
+ return {
257
+ "jsonrpc": "2.0",
258
+ "error": {"code": code, "message": message},
259
+ "id": id,
260
+ }
261
+
262
+
263
+ # Standard JSON-RPC error codes
264
+ JSONRPC_PARSE_ERROR = -32700
265
+ JSONRPC_INVALID_REQUEST = -32600
266
+ JSONRPC_METHOD_NOT_FOUND = -32601
267
+ JSONRPC_INVALID_PARAMS = -32602
268
+ JSONRPC_INTERNAL_ERROR = -32603
269
+
270
+ # A2A-specific error codes
271
+ A2A_TASK_NOT_FOUND = -32001
272
+ A2A_TASK_NOT_CANCELABLE = -32002
273
+
274
+
275
+ __all__ = [
276
+ "A2AAgentCard",
277
+ "A2AConfig",
278
+ "A2AMessage",
279
+ "A2APart",
280
+ "A2ASkill",
281
+ "A2ATask",
282
+ "A2ATaskStatus",
283
+ "A2A_TASK_NOT_CANCELABLE",
284
+ "A2A_TASK_NOT_FOUND",
285
+ "JSONRPC_INTERNAL_ERROR",
286
+ "JSONRPC_INVALID_PARAMS",
287
+ "JSONRPC_INVALID_REQUEST",
288
+ "JSONRPC_METHOD_NOT_FOUND",
289
+ "JSONRPC_PARSE_ERROR",
290
+ "VALID_TASK_STATES",
291
+ "make_jsonrpc_error",
292
+ "make_jsonrpc_request",
293
+ "make_jsonrpc_response",
294
+ ]
@@ -0,0 +1,10 @@
1
+ """Framework-specific adapters for on_relay()."""
2
+
3
+ _has_pi = False
4
+ try:
5
+ from .pi import on_relay as on_pi_relay
6
+ _has_pi = True
7
+ except ImportError:
8
+ pass
9
+
10
+ __all__ = [*(["on_pi_relay"] if _has_pi else [])]
@@ -0,0 +1,74 @@
1
+ """Agno adapter for on_relay()."""
2
+
3
+ from __future__ import annotations
4
+ import inspect
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from ..core import Relay
9
+
10
+
11
+ def _format_instructions_with_inbox(messages: list[Any], base_instructions: str) -> str:
12
+ content = "\n\nNew messages from other agents:\n"
13
+ for message in messages:
14
+ content += f" {message.sender}: {message.text}\n"
15
+ return f"{content}\n{base_instructions}" if base_instructions else content
16
+
17
+
18
+ def on_relay(agent: Any, relay: "Relay | None" = None) -> Any:
19
+ """Wrap Agno Agent to connect it to the relay."""
20
+ if relay is None:
21
+ from ..core import Relay
22
+ relay = Relay(getattr(agent, "name", "Agent"))
23
+
24
+ # 1. Add tools
25
+ async def relay_send(to: str, text: str) -> str:
26
+ """Send a private message to another agent."""
27
+ await relay.send(to, text)
28
+ return "Message sent"
29
+
30
+ async def relay_inbox() -> str:
31
+ """Check for new messages in the inbox."""
32
+ messages = await relay.inbox()
33
+ if not messages: return "No new messages"
34
+ return "\n".join([f"From {m.sender}: {m.text}" for m in messages])
35
+
36
+ async def relay_post(channel: str, text: str) -> str:
37
+ """Post a message to a shared channel."""
38
+ await relay.post(channel, text)
39
+ return "Message posted"
40
+
41
+ async def relay_agents() -> str:
42
+ """List all agents currently on the relay."""
43
+ agents = await relay.agents()
44
+ return ", ".join(agents)
45
+
46
+ agent.tools.extend([relay_send, relay_inbox, relay_post, relay_agents])
47
+
48
+ # 2. Wrap instructions with a local buffer so we don't starve relay_inbox tool
49
+ orig_instructions = agent.instructions
50
+ pending_messages: list[Any] = []
51
+
52
+ relay.on_message(lambda msg: pending_messages.append(msg))
53
+
54
+ async def instructions_wrapper(*args: Any, **kwargs: Any) -> str:
55
+ if callable(orig_instructions):
56
+ if inspect.iscoroutinefunction(orig_instructions):
57
+ base = await orig_instructions(*args, **kwargs)
58
+ else:
59
+ base = orig_instructions(*args, **kwargs)
60
+ if inspect.isawaitable(base):
61
+ base = await base
62
+ else:
63
+ base = orig_instructions
64
+
65
+ base = base or ""
66
+ if not pending_messages:
67
+ return base
68
+
69
+ messages = list(pending_messages)
70
+ pending_messages.clear()
71
+ return _format_instructions_with_inbox(messages, base)
72
+
73
+ agent.instructions = instructions_wrapper
74
+ return agent
@@ -0,0 +1,78 @@
1
+ """Claude Agent SDK adapter for on_relay()."""
2
+
3
+ from __future__ import annotations
4
+ import inspect
5
+ from types import SimpleNamespace
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ if TYPE_CHECKING:
9
+ from ..core import Relay
10
+
11
+
12
+ def on_relay(name: str, options: Any, relay: "Relay | None" = None) -> Any:
13
+ """Wrap Claude Agent SDK query options to connect them to the relay."""
14
+ try:
15
+ from claude_agent_sdk.types import HookResult
16
+ except ImportError as exc:
17
+ raise ImportError(
18
+ "on_relay() for Claude Agent SDK requires the 'claude-agent-sdk' package. "
19
+ "Install it with: pip install claude-agent-sdk"
20
+ ) from exc
21
+
22
+ agent_name = name or getattr(options, "name", "Agent")
23
+ if relay is None:
24
+ from ..core import Relay
25
+ relay = Relay(agent_name)
26
+
27
+ hooks = getattr(options, "hooks", None)
28
+ if hooks is None:
29
+ hooks = SimpleNamespace(post_tool_use=None, stop=None)
30
+ options.hooks = hooks
31
+
32
+ # 1. Inject Relaycast MCP server
33
+ mcp_config = {"name": "relaycast", "command": "agent-relay", "args": ["mcp"]}
34
+ if hasattr(options, "mcp_servers"):
35
+ options.mcp_servers.append(mcp_config)
36
+ else:
37
+ options.mcp_servers = [mcp_config]
38
+
39
+ # 2. Helper to format inbox messages
40
+ async def _drain_to_system_message() -> str | None:
41
+ messages = await relay.inbox()
42
+ if not messages: return None
43
+ content = "\n\nNew messages from other agents:\n"
44
+ for m in messages:
45
+ content += f" Relay message from {m.sender}: {m.text}\n"
46
+ return content
47
+
48
+ # 3. Hook wrappers
49
+ orig_post = getattr(hooks, "post_tool_use", None)
50
+ orig_stop = getattr(hooks, "stop", None)
51
+
52
+ async def post_tool_use_hook(*args, **kwargs):
53
+ res = None
54
+ if orig_post:
55
+ res = orig_post(*args, **kwargs)
56
+ if inspect.isawaitable(res):
57
+ res = await res
58
+ msg = await _drain_to_system_message()
59
+ if not msg:
60
+ return res
61
+ combined = (res.system_message + msg) if (res and res.system_message) else msg
62
+ return HookResult(system_message=combined)
63
+
64
+ async def stop_hook(*args, **kwargs):
65
+ res = None
66
+ if orig_stop:
67
+ res = orig_stop(*args, **kwargs)
68
+ if inspect.isawaitable(res):
69
+ res = await res
70
+ msg = await _drain_to_system_message()
71
+ if not msg:
72
+ return res
73
+ combined = (res.system_message + msg) if (res and res.system_message) else msg
74
+ return HookResult(system_message=combined, should_continue=True)
75
+
76
+ options.hooks.post_tool_use = post_tool_use_hook
77
+ options.hooks.stop = stop_hook
78
+ return options
@@ -0,0 +1,143 @@
1
+ """CrewAI adapter for on_relay()."""
2
+
3
+ from __future__ import annotations
4
+ import asyncio
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from ..core import Relay
9
+
10
+
11
+ def _message_key(message: Any) -> tuple[str | None, str, str]:
12
+ return (getattr(message, "message_id", None), message.sender, message.text)
13
+
14
+
15
+ def _format_backstory(messages: list[Any], base_backstory: str) -> str:
16
+ if not messages:
17
+ return base_backstory
18
+
19
+ content = "\n\nNew messages from other agents:\n"
20
+ for message in messages:
21
+ content += f" {message.sender}: {message.text}\n"
22
+ return f"{content}\n{base_backstory}" if base_backstory else content
23
+
24
+
25
+ class _RelayBackstory:
26
+ def __init__(self, relay: "Relay", base_backstory: str, buffer: list[Any]) -> None:
27
+ self._relay = relay
28
+ self._base_backstory = base_backstory
29
+ self._buffer = buffer
30
+
31
+ def _drain_buffer(self) -> list[Any]:
32
+ messages = list(self._buffer)
33
+ self._buffer.clear()
34
+ return messages
35
+
36
+ def _dedupe(self, messages: list[Any]) -> list[Any]:
37
+ seen: set[tuple[str | None, str, str]] = set()
38
+ unique: list[Any] = []
39
+ for message in messages:
40
+ key = _message_key(message)
41
+ if key in seen:
42
+ continue
43
+ seen.add(key)
44
+ unique.append(message)
45
+ return unique
46
+
47
+ def _resolve_sync(self) -> str:
48
+ messages = self._drain_buffer()
49
+ try:
50
+ loop = asyncio.get_running_loop()
51
+ except RuntimeError:
52
+ loop = None
53
+
54
+ if loop is None:
55
+ messages.extend(self._relay.inbox_sync())
56
+ else:
57
+ # Running inside an event loop — use a thread to avoid blocking
58
+ import concurrent.futures
59
+ with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
60
+ polled = pool.submit(asyncio.run, self._relay.inbox()).result()
61
+ messages.extend(polled)
62
+ return _format_backstory(self._dedupe(messages), self._base_backstory)
63
+
64
+ async def _resolve_async(self) -> str:
65
+ messages = self._drain_buffer()
66
+ messages.extend(await self._relay.inbox())
67
+ return _format_backstory(self._dedupe(messages), self._base_backstory)
68
+
69
+ def __call__(self) -> str:
70
+ return self._resolve_sync()
71
+
72
+ def __await__(self) -> Any:
73
+ return self._resolve_async().__await__()
74
+
75
+ def __contains__(self, item: str) -> bool:
76
+ return item in self._resolve_sync()
77
+
78
+ def __eq__(self, other: object) -> bool:
79
+ if isinstance(other, str):
80
+ return self._resolve_sync() == other
81
+ return NotImplemented
82
+
83
+ def __getattr__(self, name: str) -> Any:
84
+ return getattr(self._resolve_sync(), name)
85
+
86
+ def __repr__(self) -> str:
87
+ return repr(self._resolve_sync())
88
+
89
+ def __str__(self) -> str:
90
+ return self._resolve_sync()
91
+
92
+
93
+ def on_relay(agent: Any, relay: "Relay | None" = None) -> Any:
94
+ """Wrap CrewAI Agent to connect it to the relay."""
95
+ if relay is None:
96
+ from ..core import Relay
97
+ relay = Relay(getattr(agent, "name", "Agent"))
98
+ try:
99
+ from crewai.tools import tool
100
+ except ImportError:
101
+ raise ImportError(
102
+ "on_relay() for CrewAI requires the 'crewai' package. "
103
+ "Install it with: pip install crewai"
104
+ )
105
+
106
+ @tool
107
+ async def relay_send(to: str, text: str) -> str:
108
+ """Send a private message to another agent."""
109
+ await relay.send(to, text)
110
+ return "Message sent"
111
+
112
+ @tool
113
+ async def relay_inbox() -> str:
114
+ """Check for new messages in the inbox."""
115
+ messages = await relay.inbox()
116
+ if not messages:
117
+ return "No new messages"
118
+ return "\n".join([f"From {m.sender}: {m.text}" for m in messages])
119
+
120
+ @tool
121
+ async def relay_post(channel: str, text: str) -> str:
122
+ """Post a message to a shared channel."""
123
+ await relay.post(channel, text)
124
+ return "Message posted"
125
+
126
+ @tool
127
+ async def relay_agents() -> str:
128
+ """List all agents currently on the relay."""
129
+ agents = await relay.agents()
130
+ return ", ".join(agents)
131
+
132
+ agent.tools.extend([relay_send, relay_inbox, relay_post, relay_agents])
133
+
134
+ backstory_buffer: list[Any] = []
135
+
136
+ def _buffer_message(message: Any) -> None:
137
+ backstory_buffer.append(message)
138
+
139
+ unsubscribe = relay.on_message(_buffer_message)
140
+ agent.backstory = _RelayBackstory(relay, agent.backstory or "", backstory_buffer)
141
+ agent._relay_unsubscribe = unsubscribe # type: ignore[attr-defined]
142
+
143
+ return agent
@@ -0,0 +1,69 @@
1
+ """Google ADK adapter for on_relay()."""
2
+
3
+ from __future__ import annotations
4
+ import inspect
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ if TYPE_CHECKING:
8
+ from ..core import Relay
9
+
10
+ def on_relay(agent: Any, relay: "Relay | None" = None) -> Any:
11
+ """Wrap Google ADK Agent to connect it to the relay."""
12
+ if relay is None:
13
+ from ..core import Relay
14
+ relay = Relay(getattr(agent, "name", "Agent"))
15
+
16
+ # 1. Add tools for sending
17
+ async def relay_send(to: str, text: str) -> str:
18
+ """Send a private message to another agent."""
19
+ await relay.send(to, text)
20
+ return "Message sent"
21
+
22
+ async def relay_inbox() -> str:
23
+ """Check for new messages in the inbox."""
24
+ messages = await relay.inbox()
25
+ if not messages: return "No new messages"
26
+ return "\n".join([f"From {m.sender}: {m.text}" for m in messages])
27
+
28
+ async def relay_post(channel: str, text: str) -> str:
29
+ """Post a message to a shared channel."""
30
+ await relay.post(channel, text)
31
+ return "Message posted"
32
+
33
+ async def relay_agents() -> str:
34
+ """List all agents currently on the relay."""
35
+ agents = await relay.agents()
36
+ return ", ".join(agents)
37
+
38
+ agent.tools.extend([relay_send, relay_inbox, relay_post, relay_agents])
39
+
40
+ # 2. Inject before_model_callback for receiving
41
+ orig_callback = getattr(agent, "before_model_callback", None)
42
+
43
+ async def relay_callback(llm_request: Any) -> Any:
44
+ orig_result = None
45
+ if orig_callback:
46
+ orig_result = orig_callback(llm_request)
47
+ if inspect.isawaitable(orig_result):
48
+ orig_result = await orig_result
49
+
50
+ # Always inject relay messages, even if the original callback returned a result
51
+ messages = await relay.inbox()
52
+ if messages:
53
+ from google.genai.types import Content, Part
54
+
55
+ if getattr(llm_request, "contents", None) is None:
56
+ llm_request.contents = []
57
+
58
+ for message in messages:
59
+ llm_request.contents.append(
60
+ Content(
61
+ role="user",
62
+ parts=[Part(text=f"[Relay] {message.sender}: {message.text}")],
63
+ )
64
+ )
65
+
66
+ return orig_result
67
+
68
+ agent.before_model_callback = relay_callback
69
+ return agent