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,537 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import * as http from 'node:http';
3
+
4
+ import { A2ATransport, A2AError } from '../../communicate/a2a-transport.js';
5
+ import type { A2AConfig } from '../../communicate/a2a-types.js';
6
+
7
+ /** Start a minimal A2A mock server and return its URL + cleanup fn. */
8
+ function startMockA2AServer(
9
+ agentCard: Record<string, unknown>,
10
+ onJsonRpc?: (body: Record<string, unknown>) => Record<string, unknown>,
11
+ ): Promise<{ url: string; close: () => Promise<void> }> {
12
+ return new Promise((resolve) => {
13
+ const server = http.createServer((req, res) => {
14
+ if (req.method === 'GET' && req.url === '/.well-known/agent.json') {
15
+ res.writeHead(200, { 'Content-Type': 'application/json' });
16
+ res.end(JSON.stringify(agentCard));
17
+ return;
18
+ }
19
+
20
+ if (req.method === 'POST' && req.url === '/') {
21
+ const chunks: Buffer[] = [];
22
+ req.on('data', (c: Buffer) => chunks.push(c));
23
+ req.on('end', () => {
24
+ const body = JSON.parse(Buffer.concat(chunks).toString()) as Record<string, unknown>;
25
+ const response = onJsonRpc?.(body) ?? {
26
+ jsonrpc: '2.0',
27
+ result: {
28
+ id: 'task-1',
29
+ status: { state: 'completed' },
30
+ messages: [
31
+ { role: 'agent', parts: [{ text: 'mock reply' }] },
32
+ ],
33
+ artifacts: [],
34
+ },
35
+ id: body.id,
36
+ };
37
+ res.writeHead(200, { 'Content-Type': 'application/json' });
38
+ res.end(JSON.stringify(response));
39
+ });
40
+ return;
41
+ }
42
+
43
+ res.writeHead(404);
44
+ res.end();
45
+ });
46
+
47
+ server.listen(0, '127.0.0.1', () => {
48
+ const addr = server.address() as { port: number };
49
+ resolve({
50
+ url: `http://127.0.0.1:${addr.port}`,
51
+ close: () => new Promise<void>((r, j) => server.close((e) => (e ? j(e) : r()))),
52
+ });
53
+ });
54
+ });
55
+ }
56
+
57
+ describe('A2ATransport', () => {
58
+ let transport: A2ATransport;
59
+
60
+ afterEach(async () => {
61
+ try {
62
+ await transport?.unregister();
63
+ } catch {
64
+ // ignore
65
+ }
66
+ });
67
+
68
+ describe('register / unregister', () => {
69
+ it('starts HTTP server and returns agent info', async () => {
70
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
71
+ transport = new A2ATransport(config);
72
+
73
+ const info = await transport.register('test-agent');
74
+ expect(info.name).toBe('test-agent');
75
+ expect(info.type).toBe('a2a');
76
+ expect(info.url).toContain('http://');
77
+ expect(transport.agentCard).toBeDefined();
78
+ expect(transport.agentCard!.name).toBe('test-agent');
79
+ });
80
+
81
+ it('serves agent card at /.well-known/agent.json', async () => {
82
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
83
+ transport = new A2ATransport(config);
84
+ const info = await transport.register('card-agent');
85
+
86
+ const response = await fetch(`${info.url}/.well-known/agent.json`);
87
+ expect(response.ok).toBe(true);
88
+ const card = (await response.json()) as Record<string, unknown>;
89
+ expect(card.name).toBe('card-agent');
90
+ expect(card.version).toBe('1.0.0');
91
+ });
92
+
93
+ it('unregisters and stops server', async () => {
94
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
95
+ transport = new A2ATransport(config);
96
+ const info = await transport.register('stop-agent');
97
+
98
+ await transport.unregister();
99
+
100
+ await expect(fetch(`${info.url}/.well-known/agent.json`)).rejects.toThrow();
101
+ });
102
+ });
103
+
104
+ describe('JSON-RPC dispatch', () => {
105
+ it('handles message/send and invokes callbacks', async () => {
106
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
107
+ transport = new A2ATransport(config);
108
+ const info = await transport.register('dispatch-agent');
109
+
110
+ const received: string[] = [];
111
+ transport.onMessage((msg) => {
112
+ received.push(msg.text);
113
+ });
114
+
115
+ const rpcRequest = {
116
+ jsonrpc: '2.0',
117
+ method: 'message/send',
118
+ params: {
119
+ message: {
120
+ role: 'user',
121
+ parts: [{ text: 'hello from test' }],
122
+ messageId: 'msg-1',
123
+ },
124
+ },
125
+ id: 'rpc-1',
126
+ };
127
+
128
+ const response = await fetch(info.url, {
129
+ method: 'POST',
130
+ headers: { 'Content-Type': 'application/json' },
131
+ body: JSON.stringify(rpcRequest),
132
+ });
133
+
134
+ const body = (await response.json()) as Record<string, unknown>;
135
+ expect(body.jsonrpc).toBe('2.0');
136
+ expect(body.id).toBe('rpc-1');
137
+
138
+ const result = body.result as Record<string, unknown>;
139
+ expect(result.id).toBeDefined();
140
+ expect((result.status as Record<string, unknown>).state).toBe('completed');
141
+
142
+ expect(received).toEqual(['hello from test']);
143
+ });
144
+
145
+ it('handles tasks/get', async () => {
146
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
147
+ transport = new A2ATransport(config);
148
+ const info = await transport.register('tasks-agent');
149
+
150
+ // First create a task via message/send
151
+ const sendReq = {
152
+ jsonrpc: '2.0',
153
+ method: 'message/send',
154
+ params: {
155
+ message: {
156
+ role: 'user',
157
+ parts: [{ text: 'create task' }],
158
+ taskId: 'my-task',
159
+ },
160
+ },
161
+ id: 'rpc-send',
162
+ };
163
+
164
+ await fetch(info.url, {
165
+ method: 'POST',
166
+ headers: { 'Content-Type': 'application/json' },
167
+ body: JSON.stringify(sendReq),
168
+ });
169
+
170
+ // Now get the task
171
+ const getReq = {
172
+ jsonrpc: '2.0',
173
+ method: 'tasks/get',
174
+ params: { id: 'my-task' },
175
+ id: 'rpc-get',
176
+ };
177
+
178
+ const response = await fetch(info.url, {
179
+ method: 'POST',
180
+ headers: { 'Content-Type': 'application/json' },
181
+ body: JSON.stringify(getReq),
182
+ });
183
+
184
+ const body = (await response.json()) as Record<string, unknown>;
185
+ const result = body.result as Record<string, unknown>;
186
+ expect(result.id).toBe('my-task');
187
+ expect((result.status as Record<string, unknown>).state).toBe('completed');
188
+ });
189
+
190
+ it('handles tasks/cancel', async () => {
191
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
192
+ transport = new A2ATransport(config);
193
+ const info = await transport.register('cancel-agent');
194
+
195
+ // Create a task and manually set it to working
196
+ transport.tasks.set('cancel-me', {
197
+ id: 'cancel-me',
198
+ contextId: 'ctx-1',
199
+ status: { state: 'working', timestamp: new Date().toISOString() },
200
+ messages: [],
201
+ artifacts: [],
202
+ });
203
+
204
+ const cancelReq = {
205
+ jsonrpc: '2.0',
206
+ method: 'tasks/cancel',
207
+ params: { id: 'cancel-me' },
208
+ id: 'rpc-cancel',
209
+ };
210
+
211
+ const response = await fetch(info.url, {
212
+ method: 'POST',
213
+ headers: { 'Content-Type': 'application/json' },
214
+ body: JSON.stringify(cancelReq),
215
+ });
216
+
217
+ const body = (await response.json()) as Record<string, unknown>;
218
+ const result = body.result as Record<string, unknown>;
219
+ expect((result.status as Record<string, unknown>).state).toBe('canceled');
220
+ });
221
+
222
+ it('returns method not found for unknown methods', async () => {
223
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
224
+ transport = new A2ATransport(config);
225
+ const info = await transport.register('unknown-method-agent');
226
+
227
+ const req = {
228
+ jsonrpc: '2.0',
229
+ method: 'unknown/method',
230
+ params: {},
231
+ id: 'rpc-unk',
232
+ };
233
+
234
+ const response = await fetch(info.url, {
235
+ method: 'POST',
236
+ headers: { 'Content-Type': 'application/json' },
237
+ body: JSON.stringify(req),
238
+ });
239
+
240
+ const body = (await response.json()) as Record<string, unknown>;
241
+ expect(body.error).toBeDefined();
242
+ expect((body.error as Record<string, unknown>).code).toBe(-32601);
243
+ });
244
+
245
+ it('returns parse error for invalid JSON', async () => {
246
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
247
+ transport = new A2ATransport(config);
248
+ const info = await transport.register('parse-err-agent');
249
+
250
+ const response = await fetch(info.url, {
251
+ method: 'POST',
252
+ headers: { 'Content-Type': 'application/json' },
253
+ body: 'not json{{{',
254
+ });
255
+
256
+ const body = (await response.json()) as Record<string, unknown>;
257
+ expect(body.error).toBeDefined();
258
+ expect((body.error as Record<string, unknown>).code).toBe(-32700);
259
+ });
260
+ });
261
+
262
+ describe('sendDm (client side)', () => {
263
+ it('sends message/send to external A2A agent', async () => {
264
+ const mockCard = {
265
+ name: 'mock-agent',
266
+ description: 'Mock A2A agent',
267
+ url: '', // will be set after server starts
268
+ version: '1.0.0',
269
+ capabilities: { streaming: false },
270
+ skills: [],
271
+ defaultInputModes: ['text'],
272
+ defaultOutputModes: ['text'],
273
+ };
274
+
275
+ let receivedMethod: string | undefined;
276
+ const mock = await startMockA2AServer(mockCard, (body) => {
277
+ receivedMethod = body.method as string;
278
+ return {
279
+ jsonrpc: '2.0',
280
+ result: {
281
+ id: 'task-99',
282
+ status: { state: 'completed' },
283
+ messages: [
284
+ { role: 'user', parts: [{ text: 'hello' }] },
285
+ { role: 'agent', parts: [{ text: 'reply from mock' }] },
286
+ ],
287
+ artifacts: [],
288
+ },
289
+ id: body.id,
290
+ };
291
+ });
292
+
293
+ // Update mock card URL
294
+ mockCard.url = mock.url;
295
+
296
+ try {
297
+ const config: A2AConfig = { serverHost: '127.0.0.1', serverPort: 0 };
298
+ transport = new A2ATransport(config);
299
+
300
+ const result = await transport.sendDm(mock.url, 'hello external agent');
301
+ expect(receivedMethod).toBe('message/send');
302
+ expect(result.sender).toBe('mock-agent');
303
+ expect(result.text).toBe('reply from mock');
304
+ expect(result.task_id).toBe('task-99');
305
+ expect(result.status).toBe('completed');
306
+ } finally {
307
+ await mock.close();
308
+ }
309
+ });
310
+
311
+ it('throws A2AError on error response', async () => {
312
+ const mockCard = {
313
+ name: 'err-agent',
314
+ description: 'Error agent',
315
+ url: '',
316
+ version: '1.0.0',
317
+ capabilities: {},
318
+ skills: [],
319
+ defaultInputModes: ['text'],
320
+ defaultOutputModes: ['text'],
321
+ };
322
+
323
+ const mock = await startMockA2AServer(mockCard, (body) => ({
324
+ jsonrpc: '2.0',
325
+ error: { code: -32602, message: 'Bad params' },
326
+ id: body.id,
327
+ }));
328
+
329
+ mockCard.url = mock.url;
330
+
331
+ try {
332
+ const config: A2AConfig = {};
333
+ transport = new A2ATransport(config);
334
+
335
+ await expect(transport.sendDm(mock.url, 'bad')).rejects.toThrow(A2AError);
336
+ } finally {
337
+ await mock.close();
338
+ }
339
+ });
340
+
341
+ it('caches discovered agent cards', async () => {
342
+ let discoveryCount = 0;
343
+ const mockCard = {
344
+ name: 'cache-agent',
345
+ description: 'Cache test',
346
+ url: '',
347
+ version: '1.0.0',
348
+ capabilities: {},
349
+ skills: [],
350
+ defaultInputModes: ['text'],
351
+ defaultOutputModes: ['text'],
352
+ };
353
+
354
+ const server = await new Promise<{ url: string; close: () => Promise<void> }>((resolve) => {
355
+ const srv = http.createServer((req, res) => {
356
+ if (req.url === '/.well-known/agent.json') {
357
+ discoveryCount++;
358
+ res.writeHead(200, { 'Content-Type': 'application/json' });
359
+ res.end(JSON.stringify({ ...mockCard, url: '' }));
360
+ return;
361
+ }
362
+ if (req.method === 'POST') {
363
+ const chunks: Buffer[] = [];
364
+ req.on('data', (c: Buffer) => chunks.push(c));
365
+ req.on('end', () => {
366
+ const body = JSON.parse(Buffer.concat(chunks).toString());
367
+ res.writeHead(200, { 'Content-Type': 'application/json' });
368
+ res.end(JSON.stringify({
369
+ jsonrpc: '2.0',
370
+ result: { id: 't', status: { state: 'completed' }, messages: [], artifacts: [] },
371
+ id: body.id,
372
+ }));
373
+ });
374
+ return;
375
+ }
376
+ res.writeHead(404);
377
+ res.end();
378
+ });
379
+
380
+ srv.listen(0, '127.0.0.1', () => {
381
+ const addr = srv.address() as { port: number };
382
+ const url = `http://127.0.0.1:${addr.port}`;
383
+ mockCard.url = url;
384
+ resolve({
385
+ url,
386
+ close: () => new Promise<void>((r, j) => srv.close((e) => (e ? j(e) : r()))),
387
+ });
388
+ });
389
+ });
390
+
391
+ try {
392
+ const config: A2AConfig = {};
393
+ transport = new A2ATransport(config);
394
+
395
+ await transport.sendDm(server.url, 'first');
396
+ await transport.sendDm(server.url, 'second');
397
+
398
+ expect(discoveryCount).toBe(1); // Only discovered once
399
+ } finally {
400
+ await server.close();
401
+ }
402
+ });
403
+ });
404
+
405
+ describe('listAgents', () => {
406
+ it('lists agents from registry', async () => {
407
+ const mockCard = {
408
+ name: 'reg-agent',
409
+ description: 'Registry agent',
410
+ url: 'http://localhost:9999',
411
+ version: '1.0.0',
412
+ capabilities: {},
413
+ skills: [{ id: 's1', name: 'Skill', description: 'A skill' }],
414
+ defaultInputModes: ['text'],
415
+ defaultOutputModes: ['text'],
416
+ };
417
+
418
+ const mock = await startMockA2AServer(mockCard);
419
+
420
+ try {
421
+ const config: A2AConfig = { registry: [mock.url] };
422
+ transport = new A2ATransport(config);
423
+
424
+ const agents = await transport.listAgents();
425
+ expect(agents).toHaveLength(1);
426
+ expect(agents[0].name).toBe('reg-agent');
427
+ expect(agents[0].description).toBe('Registry agent');
428
+ } finally {
429
+ await mock.close();
430
+ }
431
+ });
432
+
433
+ it('skips unreachable agents', async () => {
434
+ const config: A2AConfig = { registry: ['http://127.0.0.1:19999'] };
435
+ transport = new A2ATransport(config);
436
+
437
+ const agents = await transport.listAgents();
438
+ expect(agents).toEqual([]);
439
+ });
440
+ });
441
+
442
+ describe('connectWs', () => {
443
+ it('is a no-op', async () => {
444
+ transport = new A2ATransport({});
445
+ await expect(transport.connectWs()).resolves.toBeUndefined();
446
+ });
447
+ });
448
+
449
+ describe('message conversion', () => {
450
+ it('converts relay message to A2A', () => {
451
+ const msg = A2ATransport._relayMsgToA2A('hello', 'sender-1');
452
+ expect(msg.role).toBe('user');
453
+ expect(msg.parts).toHaveLength(1);
454
+ expect(msg.parts[0].text).toBe('hello');
455
+ });
456
+
457
+ it('converts A2A message to relay', () => {
458
+ const a2aMsg = {
459
+ role: 'agent' as const,
460
+ parts: [{ text: 'response' }],
461
+ messageId: 'mid-1',
462
+ contextId: 'ctx-1',
463
+ };
464
+ const relayMsg = A2ATransport._a2aToRelayMsg(a2aMsg, 'sender');
465
+ expect(relayMsg.sender).toBe('sender');
466
+ expect(relayMsg.text).toBe('response');
467
+ expect(relayMsg.threadId).toBe('ctx-1');
468
+ expect(relayMsg.messageId).toBe('mid-1');
469
+ });
470
+ });
471
+
472
+ describe('auth headers', () => {
473
+ it('includes bearer token', async () => {
474
+ let receivedAuth: string | undefined;
475
+ const mockCard = {
476
+ name: 'auth-agent',
477
+ description: 'Auth test',
478
+ url: '',
479
+ version: '1.0.0',
480
+ capabilities: {},
481
+ skills: [],
482
+ defaultInputModes: ['text'],
483
+ defaultOutputModes: ['text'],
484
+ };
485
+
486
+ const server = await new Promise<{ url: string; close: () => Promise<void> }>((resolve) => {
487
+ const srv = http.createServer((req, res) => {
488
+ if (req.url === '/.well-known/agent.json') {
489
+ res.writeHead(200, { 'Content-Type': 'application/json' });
490
+ res.end(JSON.stringify(mockCard));
491
+ return;
492
+ }
493
+ if (req.method === 'POST') {
494
+ receivedAuth = req.headers.authorization;
495
+ const chunks: Buffer[] = [];
496
+ req.on('data', (c: Buffer) => chunks.push(c));
497
+ req.on('end', () => {
498
+ const body = JSON.parse(Buffer.concat(chunks).toString());
499
+ res.writeHead(200, { 'Content-Type': 'application/json' });
500
+ res.end(JSON.stringify({
501
+ jsonrpc: '2.0',
502
+ result: { id: 't', status: { state: 'completed' }, messages: [], artifacts: [] },
503
+ id: body.id,
504
+ }));
505
+ });
506
+ return;
507
+ }
508
+ res.writeHead(404);
509
+ res.end();
510
+ });
511
+
512
+ srv.listen(0, '127.0.0.1', () => {
513
+ const addr = srv.address() as { port: number };
514
+ const url = `http://127.0.0.1:${addr.port}`;
515
+ mockCard.url = url;
516
+ resolve({
517
+ url,
518
+ close: () => new Promise<void>((r, j) => srv.close((e) => (e ? j(e) : r()))),
519
+ });
520
+ });
521
+ });
522
+
523
+ try {
524
+ const config: A2AConfig = {
525
+ authScheme: 'bearer',
526
+ authToken: 'test-token-123',
527
+ };
528
+ transport = new A2ATransport(config);
529
+
530
+ await transport.sendDm(server.url, 'auth test');
531
+ expect(receivedAuth).toBe('Bearer test-token-123');
532
+ } finally {
533
+ await server.close();
534
+ }
535
+ });
536
+ });
537
+ });