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,111 @@
1
+ /**
2
+ * Bridge that connects an external A2A agent into a Relay workspace.
3
+ *
4
+ * - Registers a proxy agent on the Relay workspace
5
+ * - When Relay messages arrive for the proxy, forwards them as A2A JSON-RPC
6
+ * message/send calls to the external agent
7
+ * - When A2A responses come back, forwards them as Relay DMs
8
+ */
9
+
10
+ import { randomUUID } from 'node:crypto';
11
+
12
+ import { Relay } from './core.js';
13
+ import {
14
+ type A2AAgentCard,
15
+ a2aAgentCardFromDict,
16
+ } from './a2a-types.js';
17
+ import type { Message, RelayConfig } from './types.js';
18
+
19
+ export class A2ABridge {
20
+ readonly relay: Relay;
21
+ readonly a2aAgentUrl: string;
22
+ readonly proxyName: string;
23
+
24
+ private _agentCard?: A2AAgentCard;
25
+ private _started = false;
26
+
27
+ constructor(
28
+ relayConfig: RelayConfig,
29
+ a2aAgentUrl: string,
30
+ proxyName: string,
31
+ ) {
32
+ this.relay = new Relay(proxyName, relayConfig);
33
+ this.a2aAgentUrl = a2aAgentUrl.replace(/\/+$/, '');
34
+ this.proxyName = proxyName;
35
+ }
36
+
37
+ async start(): Promise<void> {
38
+ this.relay.onMessage((msg) => this._handleRelayMessage(msg));
39
+ this._started = true;
40
+ }
41
+
42
+ async stop(): Promise<void> {
43
+ this._started = false;
44
+ await this.relay.close();
45
+ }
46
+
47
+ async discoverAgent(): Promise<A2AAgentCard> {
48
+ const url = `${this.a2aAgentUrl}/.well-known/agent.json`;
49
+ const response = await fetch(url);
50
+ const data = (await response.json()) as Record<string, unknown>;
51
+ this._agentCard = a2aAgentCardFromDict(data);
52
+ return this._agentCard;
53
+ }
54
+
55
+ async sendA2AMessage(text: string): Promise<string | null> {
56
+ const a2aMsg = {
57
+ role: 'user' as const,
58
+ parts: [{ text }],
59
+ messageId: randomUUID(),
60
+ };
61
+
62
+ const jsonrpcRequest = {
63
+ jsonrpc: '2.0' as const,
64
+ method: 'message/send',
65
+ params: { message: a2aMsg },
66
+ id: randomUUID(),
67
+ };
68
+
69
+ const targetUrl = this._agentCard?.url ?? this.a2aAgentUrl;
70
+
71
+ const response = await fetch(targetUrl, {
72
+ method: 'POST',
73
+ headers: { 'Content-Type': 'application/json' },
74
+ body: JSON.stringify(jsonrpcRequest),
75
+ });
76
+
77
+ const data = (await response.json()) as Record<string, unknown>;
78
+ const result = (data.result ?? {}) as Record<string, unknown>;
79
+
80
+ // Extract response text from task status message
81
+ const status = (result.status ?? {}) as Record<string, unknown>;
82
+ const statusMsg = (status.message ?? {}) as Record<string, unknown>;
83
+ const statusParts = (statusMsg.parts ?? []) as Record<string, unknown>[];
84
+ if (statusParts.length > 0) {
85
+ const t = statusParts[0].text as string | undefined;
86
+ if (t) return t;
87
+ }
88
+
89
+ // Try from messages list
90
+ const messages = (result.messages ?? []) as Record<string, unknown>[];
91
+ for (let i = messages.length - 1; i >= 0; i--) {
92
+ const msg = messages[i];
93
+ if (msg.role === 'agent') {
94
+ const msgParts = (msg.parts ?? []) as Record<string, unknown>[];
95
+ if (msgParts.length > 0) {
96
+ const t = msgParts[0].text as string | undefined;
97
+ if (t) return t;
98
+ }
99
+ }
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ private async _handleRelayMessage(msg: Message): Promise<void> {
106
+ const responseText = await this.sendA2AMessage(msg.text);
107
+ if (responseText) {
108
+ await this.relay.send(msg.sender, responseText);
109
+ }
110
+ }
111
+ }
@@ -0,0 +1,277 @@
1
+ /**
2
+ * A2A-compliant HTTP server that exposes a Relay agent as an A2A endpoint.
3
+ *
4
+ * Routes:
5
+ * GET /.well-known/agent.json -> Agent Card
6
+ * POST / -> JSON-RPC 2.0 dispatcher
7
+ */
8
+
9
+ import { randomUUID } from 'node:crypto';
10
+ import * as http from 'node:http';
11
+
12
+ import {
13
+ type A2AAgentCard,
14
+ type A2AMessage,
15
+ type A2APart,
16
+ type A2ASkill,
17
+ type A2ATask,
18
+ type A2ATaskStatus,
19
+ a2aAgentCardToDict,
20
+ a2aMessageToDict,
21
+ createA2AAgentCard,
22
+ createA2ATaskStatus,
23
+ } from './a2a-types.js';
24
+
25
+ export type A2AMessageHandler = (
26
+ msg: A2AMessage,
27
+ ) => A2AMessage | null | Promise<A2AMessage | null>;
28
+
29
+ export class A2AServer {
30
+ readonly agentName: string;
31
+ readonly port: number;
32
+ readonly host: string;
33
+ readonly skills: A2ASkill[];
34
+ readonly tasks: Map<string, A2ATask> = new Map();
35
+
36
+ private _onMessage?: A2AMessageHandler;
37
+ private _server?: http.Server;
38
+ private _actualPort?: number;
39
+
40
+ constructor(
41
+ agentName: string,
42
+ port: number = 5000,
43
+ host: string = '0.0.0.0',
44
+ skills: A2ASkill[] = [],
45
+ ) {
46
+ this.agentName = agentName;
47
+ this.port = port;
48
+ this.host = host;
49
+ this.skills = skills;
50
+ }
51
+
52
+ get url(): string {
53
+ const p = this._actualPort ?? this.port;
54
+ return `http://${this.host}:${p}`;
55
+ }
56
+
57
+ onMessage(callback: A2AMessageHandler): void {
58
+ this._onMessage = callback;
59
+ }
60
+
61
+ getAgentCard(): A2AAgentCard {
62
+ return createA2AAgentCard(
63
+ this.agentName,
64
+ `Agent Relay agent: ${this.agentName}`,
65
+ this.url,
66
+ [...this.skills],
67
+ );
68
+ }
69
+
70
+ async handleMessageSend(params: Record<string, unknown>): Promise<Record<string, unknown>> {
71
+ const messageData = (params.message ?? {}) as Record<string, unknown>;
72
+ const rawParts = (messageData.parts ?? []) as Record<string, unknown>[];
73
+ const parts: A2APart[] = rawParts.map((p) => ({ text: p.text as string | undefined }));
74
+
75
+ const incoming: A2AMessage = {
76
+ role: (messageData.role as 'user' | 'agent') ?? 'user',
77
+ parts,
78
+ messageId: (messageData.messageId as string) ?? randomUUID(),
79
+ contextId: messageData.contextId as string | undefined,
80
+ taskId: messageData.taskId as string | undefined,
81
+ };
82
+
83
+ // Create or find task
84
+ const taskId = incoming.taskId ?? randomUUID();
85
+ const contextId = incoming.contextId ?? randomUUID();
86
+
87
+ let task: A2ATask;
88
+ if (this.tasks.has(taskId)) {
89
+ task = this.tasks.get(taskId)!;
90
+ task.messages.push(incoming);
91
+ task.status = createA2ATaskStatus('working');
92
+ } else {
93
+ task = {
94
+ id: taskId,
95
+ contextId,
96
+ status: createA2ATaskStatus('working'),
97
+ messages: [incoming],
98
+ artifacts: [],
99
+ };
100
+ this.tasks.set(taskId, task);
101
+ }
102
+
103
+ // Invoke callback
104
+ let responseMsg: A2AMessage | null = null;
105
+ if (this._onMessage) {
106
+ const result = this._onMessage(incoming);
107
+ responseMsg = result instanceof Promise ? await result : result;
108
+ }
109
+
110
+ if (responseMsg) {
111
+ task.messages.push(responseMsg);
112
+ task.status = createA2ATaskStatus('completed', responseMsg);
113
+ } else {
114
+ task.status = createA2ATaskStatus('completed');
115
+ }
116
+
117
+ return taskToDict(task);
118
+ }
119
+
120
+ async handleTasksGet(taskId: string): Promise<Record<string, unknown>> {
121
+ const task = this.tasks.get(taskId);
122
+ if (!task) {
123
+ throw new Error(`Task not found: ${taskId}`);
124
+ }
125
+ return taskToDict(task);
126
+ }
127
+
128
+ async handleTasksCancel(taskId: string): Promise<Record<string, unknown>> {
129
+ const task = this.tasks.get(taskId);
130
+ if (!task) {
131
+ throw new Error(`Task not found: ${taskId}`);
132
+ }
133
+ task.status = createA2ATaskStatus('canceled');
134
+ return taskToDict(task);
135
+ }
136
+
137
+ async start(): Promise<void> {
138
+ return new Promise((resolve, reject) => {
139
+ const server = http.createServer((req, res) => {
140
+ void this._handleRequest(req, res);
141
+ });
142
+
143
+ server.on('error', reject);
144
+
145
+ server.listen(this.port, this.host, () => {
146
+ this._server = server;
147
+ const addr = server.address();
148
+ if (typeof addr === 'object' && addr) {
149
+ this._actualPort = addr.port;
150
+ }
151
+ resolve();
152
+ });
153
+ });
154
+ }
155
+
156
+ async stop(): Promise<void> {
157
+ if (this._server) {
158
+ await new Promise<void>((resolve, reject) => {
159
+ this._server!.close((err) => (err ? reject(err) : resolve()));
160
+ });
161
+ this._server = undefined;
162
+ this._actualPort = undefined;
163
+ }
164
+ }
165
+
166
+ // --- HTTP handlers ---
167
+
168
+ private async _handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
169
+ if (req.method === 'GET' && req.url === '/.well-known/agent.json') {
170
+ const card = this.getAgentCard();
171
+ res.writeHead(200, { 'Content-Type': 'application/json' });
172
+ res.end(JSON.stringify(a2aAgentCardToDict(card)));
173
+ return;
174
+ }
175
+
176
+ if (req.method === 'POST' && (req.url === '/' || req.url === '')) {
177
+ await this._handleJsonRpc(req, res);
178
+ return;
179
+ }
180
+
181
+ res.writeHead(404, { 'Content-Type': 'application/json' });
182
+ res.end(JSON.stringify({ error: 'Not found' }));
183
+ }
184
+
185
+ private async _handleJsonRpc(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
186
+ let body: Record<string, unknown>;
187
+ try {
188
+ const raw = await readBody(req);
189
+ body = JSON.parse(raw) as Record<string, unknown>;
190
+ } catch {
191
+ res.writeHead(400, { 'Content-Type': 'application/json' });
192
+ res.end(
193
+ JSON.stringify({
194
+ jsonrpc: '2.0',
195
+ error: { code: -32700, message: 'Parse error' },
196
+ id: null,
197
+ }),
198
+ );
199
+ return;
200
+ }
201
+
202
+ const method = (body.method ?? '') as string;
203
+ const params = (body.params ?? {}) as Record<string, unknown>;
204
+ const rpcId = body.id ?? null;
205
+
206
+ try {
207
+ let result: Record<string, unknown>;
208
+
209
+ if (method === 'message/send') {
210
+ result = await this.handleMessageSend(params);
211
+ } else if (method === 'tasks/get') {
212
+ const id = (params.id ?? params.taskId ?? '') as string;
213
+ result = await this.handleTasksGet(id);
214
+ } else if (method === 'tasks/cancel') {
215
+ const id = (params.id ?? params.taskId ?? '') as string;
216
+ result = await this.handleTasksCancel(id);
217
+ } else {
218
+ res.writeHead(400, { 'Content-Type': 'application/json' });
219
+ res.end(
220
+ JSON.stringify({
221
+ jsonrpc: '2.0',
222
+ error: { code: -32601, message: `Method not found: ${method}` },
223
+ id: rpcId,
224
+ }),
225
+ );
226
+ return;
227
+ }
228
+
229
+ res.writeHead(200, { 'Content-Type': 'application/json' });
230
+ res.end(JSON.stringify({ jsonrpc: '2.0', result, id: rpcId }));
231
+ } catch (err) {
232
+ res.writeHead(404, { 'Content-Type': 'application/json' });
233
+ res.end(
234
+ JSON.stringify({
235
+ jsonrpc: '2.0',
236
+ error: { code: -32602, message: String(err) },
237
+ id: rpcId,
238
+ }),
239
+ );
240
+ }
241
+ }
242
+ }
243
+
244
+ // --- Helpers ---
245
+
246
+ function readBody(req: http.IncomingMessage): Promise<string> {
247
+ return new Promise((resolve, reject) => {
248
+ const chunks: Buffer[] = [];
249
+ req.on('data', (chunk: Buffer) => chunks.push(chunk));
250
+ req.on('end', () => resolve(Buffer.concat(chunks).toString()));
251
+ req.on('error', reject);
252
+ });
253
+ }
254
+
255
+ function taskToDict(task: A2ATask): Record<string, unknown> {
256
+ const statusDict: Record<string, unknown> = { state: task.status.state };
257
+ if (task.status.message) {
258
+ statusDict.message = a2aMessageToDict(task.status.message);
259
+ }
260
+
261
+ const messages = task.messages.map((m) => {
262
+ const md: Record<string, unknown> = {
263
+ role: m.role,
264
+ parts: m.parts.map((p) => ({ text: p.text })),
265
+ };
266
+ if (m.messageId) md.messageId = m.messageId;
267
+ return md;
268
+ });
269
+
270
+ return {
271
+ id: task.id,
272
+ contextId: task.contextId,
273
+ status: statusDict,
274
+ messages,
275
+ artifacts: task.artifacts,
276
+ };
277
+ }