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.
- package/bin/agent-relay-broker-darwin-arm64 +0 -0
- package/bin/agent-relay-broker-darwin-x64 +0 -0
- package/bin/agent-relay-broker-linux-arm64 +0 -0
- package/bin/agent-relay-broker-linux-x64 +0 -0
- package/dist/index.cjs +1358 -941
- package/dist/src/cli/commands/agent-management.d.ts +2 -2
- package/dist/src/cli/commands/agent-management.d.ts.map +1 -1
- package/dist/src/cli/commands/agent-management.js +41 -240
- package/dist/src/cli/commands/agent-management.js.map +1 -1
- package/dist/src/cli/commands/messaging.d.ts +1 -1
- package/dist/src/cli/commands/messaging.d.ts.map +1 -1
- package/dist/src/cli/commands/messaging.js +14 -5
- package/dist/src/cli/commands/messaging.js.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts +4 -1
- package/dist/src/cli/lib/agent-management-listing.d.ts.map +1 -1
- package/dist/src/cli/lib/agent-management-listing.js +27 -2
- package/dist/src/cli/lib/agent-management-listing.js.map +1 -1
- package/package.json +11 -10
- package/packages/acp-bridge/package.json +2 -2
- package/packages/config/package.json +1 -1
- package/packages/hooks/package.json +4 -4
- package/packages/memory/package.json +2 -2
- package/packages/openclaw/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/sdk/ADAPTER_REVIEW.md +109 -0
- package/packages/sdk/dist/client.d.ts +66 -0
- package/packages/sdk/dist/client.d.ts.map +1 -1
- package/packages/sdk/dist/client.js +230 -0
- package/packages/sdk/dist/client.js.map +1 -1
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts +25 -0
- package/packages/sdk/dist/communicate/a2a-bridge.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js +89 -0
- package/packages/sdk/dist/communicate/a2a-bridge.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts +31 -0
- package/packages/sdk/dist/communicate/a2a-server.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-server.js +220 -0
- package/packages/sdk/dist/communicate/a2a-server.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts +48 -0
- package/packages/sdk/dist/communicate/a2a-transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-transport.js +302 -0
- package/packages/sdk/dist/communicate/a2a-transport.js.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts +107 -0
- package/packages/sdk/dist/communicate/a2a-types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/a2a-types.js +209 -0
- package/packages/sdk/dist/communicate/a2a-types.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts +28 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js +47 -0
- package/packages/sdk/dist/communicate/adapters/claude-sdk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts +42 -0
- package/packages/sdk/dist/communicate/adapters/crewai.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js +95 -0
- package/packages/sdk/dist/communicate/adapters/crewai.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts +53 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js +77 -0
- package/packages/sdk/dist/communicate/adapters/google-adk.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts +7 -0
- package/packages/sdk/dist/communicate/adapters/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/index.js +7 -0
- package/packages/sdk/dist/communicate/adapters/index.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts +40 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js +77 -0
- package/packages/sdk/dist/communicate/adapters/langgraph.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts +25 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js +70 -0
- package/packages/sdk/dist/communicate/adapters/openai-agents.js.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts +45 -0
- package/packages/sdk/dist/communicate/adapters/pi.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/adapters/pi.js +59 -0
- package/packages/sdk/dist/communicate/adapters/pi.js.map +1 -0
- package/packages/sdk/dist/communicate/core.d.ts +58 -0
- package/packages/sdk/dist/communicate/core.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/core.js +128 -0
- package/packages/sdk/dist/communicate/core.js.map +1 -0
- package/packages/sdk/dist/communicate/index.d.ts +4 -0
- package/packages/sdk/dist/communicate/index.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/index.js +4 -0
- package/packages/sdk/dist/communicate/index.js.map +1 -0
- package/packages/sdk/dist/communicate/transport.d.ts +36 -0
- package/packages/sdk/dist/communicate/transport.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/transport.js +371 -0
- package/packages/sdk/dist/communicate/transport.js.map +1 -0
- package/packages/sdk/dist/communicate/types.d.ts +58 -0
- package/packages/sdk/dist/communicate/types.d.ts.map +1 -0
- package/packages/sdk/dist/communicate/types.js +66 -0
- package/packages/sdk/dist/communicate/types.js.map +1 -0
- package/packages/sdk/dist/workflows/builder.d.ts +35 -5
- package/packages/sdk/dist/workflows/builder.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/builder.js +81 -7
- package/packages/sdk/dist/workflows/builder.js.map +1 -1
- package/packages/sdk/dist/workflows/cli.js +14 -1
- package/packages/sdk/dist/workflows/cli.js.map +1 -1
- package/packages/sdk/dist/workflows/runner.d.ts +10 -2
- package/packages/sdk/dist/workflows/runner.d.ts.map +1 -1
- package/packages/sdk/dist/workflows/runner.js +95 -1
- package/packages/sdk/dist/workflows/runner.js.map +1 -1
- package/packages/sdk/dist/workflows/types.d.ts +11 -0
- package/packages/sdk/dist/workflows/types.d.ts.map +1 -1
- package/packages/sdk/examples/communicate/claude_sdk_example.ts +5 -0
- package/packages/sdk/examples/communicate/pi_example.ts +8 -0
- package/packages/sdk/package.json +48 -2
- package/packages/sdk/src/__tests__/builder-deterministic.test.ts +132 -0
- package/packages/sdk/src/__tests__/communicate/a2a-bridge.test.ts +211 -0
- package/packages/sdk/src/__tests__/communicate/a2a-server.test.ts +359 -0
- package/packages/sdk/src/__tests__/communicate/a2a-transport.test.ts +537 -0
- package/packages/sdk/src/__tests__/communicate/a2a-types.test.ts +297 -0
- package/packages/sdk/src/__tests__/communicate/adapters/claude-sdk.test.ts +163 -0
- package/packages/sdk/src/__tests__/communicate/adapters/crewai.test.ts +219 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-crewai.test.ts +101 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-google-adk.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-langgraph.test.ts +181 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-openai-agents.test.ts +137 -0
- package/packages/sdk/src/__tests__/communicate/adapters/e2e-pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/adapters/google-adk.test.ts +200 -0
- package/packages/sdk/src/__tests__/communicate/adapters/langgraph.test.ts +162 -0
- package/packages/sdk/src/__tests__/communicate/adapters/openai-agents.test.ts +166 -0
- package/packages/sdk/src/__tests__/communicate/adapters/pi.test.ts +140 -0
- package/packages/sdk/src/__tests__/communicate/core.test.ts +574 -0
- package/packages/sdk/src/__tests__/communicate/integration/cross-framework.test.ts +353 -0
- package/packages/sdk/src/__tests__/communicate/transport.test.ts +613 -0
- package/packages/sdk/src/__tests__/start-from.test.ts +346 -0
- package/packages/sdk/src/client.ts +301 -0
- package/packages/sdk/src/communicate/a2a-bridge.ts +111 -0
- package/packages/sdk/src/communicate/a2a-server.ts +277 -0
- package/packages/sdk/src/communicate/a2a-transport.ts +395 -0
- package/packages/sdk/src/communicate/a2a-types.ts +338 -0
- package/packages/sdk/src/communicate/adapters/claude-sdk.ts +85 -0
- package/packages/sdk/src/communicate/adapters/crewai.ts +141 -0
- package/packages/sdk/src/communicate/adapters/google-adk.ts +139 -0
- package/packages/sdk/src/communicate/adapters/index.ts +6 -0
- package/packages/sdk/src/communicate/adapters/langgraph.ts +112 -0
- package/packages/sdk/src/communicate/adapters/openai-agents.ts +113 -0
- package/packages/sdk/src/communicate/adapters/pi.ts +105 -0
- package/packages/sdk/src/communicate/core.ts +157 -0
- package/packages/sdk/src/communicate/index.ts +3 -0
- package/packages/sdk/src/communicate/transport.ts +489 -0
- package/packages/sdk/src/communicate/types.ts +106 -0
- package/packages/sdk/src/examples/workflows/fix-dashboard-user-registration.yaml +182 -0
- package/packages/sdk/src/workflows/builder.ts +97 -9
- package/packages/sdk/src/workflows/cli.ts +16 -1
- package/packages/sdk/src/workflows/runner.ts +110 -1
- package/packages/sdk/src/workflows/types.ts +14 -0
- package/packages/sdk/tsconfig.build.json +1 -7
- package/packages/sdk/tsconfig.json +1 -7
- package/packages/sdk-py/README.md +67 -25
- package/packages/sdk-py/examples/communicate/agno_example.py +8 -0
- package/packages/sdk-py/examples/communicate/claude_sdk_example.py +6 -0
- package/packages/sdk-py/examples/communicate/crewai_example.py +7 -0
- package/packages/sdk-py/examples/communicate/google_adk_example.py +7 -0
- package/packages/sdk-py/examples/communicate/openai_agents_example.py +8 -0
- package/packages/sdk-py/examples/communicate/swarms_example.py +7 -0
- package/packages/sdk-py/pyproject.toml +12 -1
- package/packages/sdk-py/src/agent_relay/__init__.py +8 -0
- package/packages/sdk-py/src/agent_relay/builder.py +65 -26
- package/packages/sdk-py/src/agent_relay/communicate/__init__.py +6 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_bridge.py +138 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_server.py +242 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_transport.py +366 -0
- package/packages/sdk-py/src/agent_relay/communicate/a2a_types.py +294 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/__init__.py +10 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/agno.py +74 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/claude_sdk.py +78 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/crewai.py +143 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/google_adk.py +69 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/openai_agents.py +86 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/pi.py +175 -0
- package/packages/sdk-py/src/agent_relay/communicate/adapters/swarms.py +44 -0
- package/packages/sdk-py/src/agent_relay/communicate/core.py +293 -0
- package/packages/sdk-py/src/agent_relay/communicate/transport.py +502 -0
- package/packages/sdk-py/src/agent_relay/communicate/types.py +89 -0
- package/packages/sdk-py/src/agent_relay/types.py +2 -1
- package/packages/sdk-py/tests/communicate/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/__init__.py +0 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_agno.py +154 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_claude_sdk.py +428 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_crewai.py +234 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_google_adk.py +182 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_langgraph.py +262 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_openai_agents.py +88 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_pi.py +156 -0
- package/packages/sdk-py/tests/communicate/adapters/e2e_test_swarms.py +239 -0
- package/packages/sdk-py/tests/communicate/adapters/test_agno.py +140 -0
- package/packages/sdk-py/tests/communicate/adapters/test_claude_sdk.py +147 -0
- package/packages/sdk-py/tests/communicate/adapters/test_crewai.py +136 -0
- package/packages/sdk-py/tests/communicate/adapters/test_google_adk.py +125 -0
- package/packages/sdk-py/tests/communicate/adapters/test_openai_agents.py +99 -0
- package/packages/sdk-py/tests/communicate/adapters/test_pi.py +270 -0
- package/packages/sdk-py/tests/communicate/adapters/test_swarms.py +113 -0
- package/packages/sdk-py/tests/communicate/conftest.py +555 -0
- package/packages/sdk-py/tests/communicate/integration/__init__.py +1 -0
- package/packages/sdk-py/tests/communicate/integration/test_cross_framework.py +331 -0
- package/packages/sdk-py/tests/communicate/integration/test_end_to_end.py +151 -0
- package/packages/sdk-py/tests/communicate/test_a2a_bridge.py +363 -0
- package/packages/sdk-py/tests/communicate/test_a2a_server.py +346 -0
- package/packages/sdk-py/tests/communicate/test_a2a_transport.py +561 -0
- package/packages/sdk-py/tests/communicate/test_a2a_types.py +342 -0
- package/packages/sdk-py/tests/communicate/test_auto_detect.py +67 -0
- package/packages/sdk-py/tests/communicate/test_core.py +331 -0
- package/packages/sdk-py/tests/communicate/test_transport.py +373 -0
- package/packages/sdk-py/tests/communicate/test_types.py +285 -0
- package/packages/sdk-py/tests/test_builder_deterministic.py +118 -0
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/package.json +2 -2
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts +0 -14
- package/packages/sdk/dist/__tests__/completion-pipeline.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js +0 -1476
- package/packages/sdk/dist/__tests__/completion-pipeline.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/contract-fixtures.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js +0 -152
- package/packages/sdk/dist/__tests__/contract-fixtures.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts +0 -16
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js +0 -640
- package/packages/sdk/dist/__tests__/e2e-owner-review.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/facade.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/facade.test.js +0 -305
- package/packages/sdk/dist/__tests__/facade.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/integration.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/integration.test.js +0 -205
- package/packages/sdk/dist/__tests__/integration.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/pty.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/pty.test.js +0 -20
- package/packages/sdk/dist/__tests__/pty.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/quickstart.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/quickstart.test.js +0 -176
- package/packages/sdk/dist/__tests__/quickstart.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/spawn-from-env.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js +0 -222
- package/packages/sdk/dist/__tests__/spawn-from-env.test.js.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.d.ts +0 -2
- package/packages/sdk/dist/__tests__/unit.test.d.ts.map +0 -1
- package/packages/sdk/dist/__tests__/unit.test.js +0 -357
- package/packages/sdk/dist/__tests__/unit.test.js.map +0 -1
- package/packages/sdk-py/agent_relay/__init__.py +0 -21
- package/packages/sdk-py/agent_relay/models.py +0 -398
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
"""Tests for A2AServer — JSON-RPC dispatch, Agent Card serving, task lifecycle."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import uuid
|
|
7
|
+
|
|
8
|
+
import aiohttp
|
|
9
|
+
import pytest
|
|
10
|
+
import pytest_asyncio
|
|
11
|
+
|
|
12
|
+
from agent_relay.communicate.a2a_server import A2AServer
|
|
13
|
+
from agent_relay.communicate.a2a_types import (
|
|
14
|
+
A2AMessage,
|
|
15
|
+
A2APart,
|
|
16
|
+
A2ASkill,
|
|
17
|
+
A2ATask,
|
|
18
|
+
A2ATaskStatus,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest_asyncio.fixture
|
|
23
|
+
async def a2a_server():
|
|
24
|
+
"""Start an A2AServer on a random port for testing."""
|
|
25
|
+
server = A2AServer(agent_name="test-agent", port=0, host="127.0.0.1")
|
|
26
|
+
await server.start()
|
|
27
|
+
try:
|
|
28
|
+
yield server
|
|
29
|
+
finally:
|
|
30
|
+
await server.stop()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def server_url(a2a_server: A2AServer) -> str:
|
|
35
|
+
return a2a_server.url
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# --- Agent Card tests ---
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class TestAgentCard:
|
|
42
|
+
async def test_agent_card_served_at_well_known(self, server_url: str):
|
|
43
|
+
async with aiohttp.ClientSession() as session:
|
|
44
|
+
async with session.get(f"{server_url}/.well-known/agent.json") as resp:
|
|
45
|
+
assert resp.status == 200
|
|
46
|
+
data = await resp.json()
|
|
47
|
+
assert data["name"] == "test-agent"
|
|
48
|
+
assert "Agent Relay agent" in data["description"]
|
|
49
|
+
assert data["version"] == "1.0.0"
|
|
50
|
+
assert data["capabilities"]["streaming"] is True
|
|
51
|
+
assert data["defaultInputModes"] == ["text"]
|
|
52
|
+
assert data["defaultOutputModes"] == ["text"]
|
|
53
|
+
|
|
54
|
+
async def test_agent_card_includes_skills(self):
|
|
55
|
+
skills = [A2ASkill(id="s1", name="Skill One", description="Does thing one")]
|
|
56
|
+
server = A2AServer(agent_name="skilled-agent", port=0, host="127.0.0.1", skills=skills)
|
|
57
|
+
await server.start()
|
|
58
|
+
try:
|
|
59
|
+
async with aiohttp.ClientSession() as session:
|
|
60
|
+
async with session.get(f"{server.url}/.well-known/agent.json") as resp:
|
|
61
|
+
data = await resp.json()
|
|
62
|
+
assert len(data["skills"]) == 1
|
|
63
|
+
assert data["skills"][0]["id"] == "s1"
|
|
64
|
+
assert data["skills"][0]["name"] == "Skill One"
|
|
65
|
+
finally:
|
|
66
|
+
await server.stop()
|
|
67
|
+
|
|
68
|
+
async def test_get_agent_card_method(self, a2a_server: A2AServer):
|
|
69
|
+
card = a2a_server.get_agent_card()
|
|
70
|
+
assert card.name == "test-agent"
|
|
71
|
+
assert card.url == a2a_server.url
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# --- JSON-RPC dispatch tests ---
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class TestJsonRpcDispatch:
|
|
78
|
+
async def test_message_send_creates_task(self, server_url: str, a2a_server: A2AServer):
|
|
79
|
+
payload = {
|
|
80
|
+
"jsonrpc": "2.0",
|
|
81
|
+
"method": "message/send",
|
|
82
|
+
"params": {
|
|
83
|
+
"message": {
|
|
84
|
+
"role": "user",
|
|
85
|
+
"parts": [{"text": "Hello, agent!"}],
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
"id": "req-1",
|
|
89
|
+
}
|
|
90
|
+
async with aiohttp.ClientSession() as session:
|
|
91
|
+
async with session.post(server_url, json=payload) as resp:
|
|
92
|
+
assert resp.status == 200
|
|
93
|
+
data = await resp.json()
|
|
94
|
+
assert data["jsonrpc"] == "2.0"
|
|
95
|
+
assert data["id"] == "req-1"
|
|
96
|
+
result = data["result"]
|
|
97
|
+
assert "id" in result
|
|
98
|
+
assert result["status"]["state"] == "completed"
|
|
99
|
+
assert len(result["messages"]) >= 1
|
|
100
|
+
# Task should be stored
|
|
101
|
+
assert result["id"] in a2a_server.tasks
|
|
102
|
+
|
|
103
|
+
async def test_message_send_with_callback(self, server_url: str, a2a_server: A2AServer):
|
|
104
|
+
async def echo_handler(msg: A2AMessage) -> A2AMessage:
|
|
105
|
+
text = msg.parts[0].text if msg.parts else ""
|
|
106
|
+
return A2AMessage(
|
|
107
|
+
role="agent",
|
|
108
|
+
parts=[A2APart(text=f"Echo: {text}")],
|
|
109
|
+
messageId=str(uuid.uuid4()),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
a2a_server.on_message(echo_handler)
|
|
113
|
+
|
|
114
|
+
payload = {
|
|
115
|
+
"jsonrpc": "2.0",
|
|
116
|
+
"method": "message/send",
|
|
117
|
+
"params": {
|
|
118
|
+
"message": {
|
|
119
|
+
"role": "user",
|
|
120
|
+
"parts": [{"text": "Hello!"}],
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
"id": "req-2",
|
|
124
|
+
}
|
|
125
|
+
async with aiohttp.ClientSession() as session:
|
|
126
|
+
async with session.post(server_url, json=payload) as resp:
|
|
127
|
+
data = await resp.json()
|
|
128
|
+
result = data["result"]
|
|
129
|
+
assert result["status"]["state"] == "completed"
|
|
130
|
+
# Should have both the user message and the agent response
|
|
131
|
+
assert len(result["messages"]) == 2
|
|
132
|
+
agent_msg = result["messages"][1]
|
|
133
|
+
assert agent_msg["role"] == "agent"
|
|
134
|
+
assert "Echo: Hello!" in agent_msg["parts"][0]["text"]
|
|
135
|
+
|
|
136
|
+
async def test_message_send_sync_callback(self, server_url: str, a2a_server: A2AServer):
|
|
137
|
+
"""Test that synchronous (non-async) callbacks work."""
|
|
138
|
+
|
|
139
|
+
def sync_handler(msg: A2AMessage) -> A2AMessage:
|
|
140
|
+
return A2AMessage(
|
|
141
|
+
role="agent",
|
|
142
|
+
parts=[A2APart(text="sync response")],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
a2a_server.on_message(sync_handler)
|
|
146
|
+
|
|
147
|
+
payload = {
|
|
148
|
+
"jsonrpc": "2.0",
|
|
149
|
+
"method": "message/send",
|
|
150
|
+
"params": {"message": {"role": "user", "parts": [{"text": "test"}]}},
|
|
151
|
+
"id": "req-sync",
|
|
152
|
+
}
|
|
153
|
+
async with aiohttp.ClientSession() as session:
|
|
154
|
+
async with session.post(server_url, json=payload) as resp:
|
|
155
|
+
data = await resp.json()
|
|
156
|
+
assert data["result"]["status"]["state"] == "completed"
|
|
157
|
+
assert data["result"]["messages"][1]["parts"][0]["text"] == "sync response"
|
|
158
|
+
|
|
159
|
+
async def test_tasks_get(self, server_url: str, a2a_server: A2AServer):
|
|
160
|
+
# First create a task via message/send
|
|
161
|
+
send_payload = {
|
|
162
|
+
"jsonrpc": "2.0",
|
|
163
|
+
"method": "message/send",
|
|
164
|
+
"params": {"message": {"role": "user", "parts": [{"text": "create task"}]}},
|
|
165
|
+
"id": "req-create",
|
|
166
|
+
}
|
|
167
|
+
async with aiohttp.ClientSession() as session:
|
|
168
|
+
async with session.post(server_url, json=send_payload) as resp:
|
|
169
|
+
created = (await resp.json())["result"]
|
|
170
|
+
task_id = created["id"]
|
|
171
|
+
|
|
172
|
+
# Now get the task
|
|
173
|
+
get_payload = {
|
|
174
|
+
"jsonrpc": "2.0",
|
|
175
|
+
"method": "tasks/get",
|
|
176
|
+
"params": {"id": task_id},
|
|
177
|
+
"id": "req-get",
|
|
178
|
+
}
|
|
179
|
+
async with session.post(server_url, json=get_payload) as resp:
|
|
180
|
+
data = await resp.json()
|
|
181
|
+
assert data["id"] == "req-get"
|
|
182
|
+
assert data["result"]["id"] == task_id
|
|
183
|
+
assert data["result"]["status"]["state"] == "completed"
|
|
184
|
+
|
|
185
|
+
async def test_tasks_get_not_found(self, server_url: str):
|
|
186
|
+
payload = {
|
|
187
|
+
"jsonrpc": "2.0",
|
|
188
|
+
"method": "tasks/get",
|
|
189
|
+
"params": {"id": "nonexistent-task"},
|
|
190
|
+
"id": "req-404",
|
|
191
|
+
}
|
|
192
|
+
async with aiohttp.ClientSession() as session:
|
|
193
|
+
async with session.post(server_url, json=payload) as resp:
|
|
194
|
+
assert resp.status == 404
|
|
195
|
+
data = await resp.json()
|
|
196
|
+
assert data["error"]["code"] == -32602
|
|
197
|
+
|
|
198
|
+
async def test_tasks_cancel(self, server_url: str, a2a_server: A2AServer):
|
|
199
|
+
# Create a task
|
|
200
|
+
send_payload = {
|
|
201
|
+
"jsonrpc": "2.0",
|
|
202
|
+
"method": "message/send",
|
|
203
|
+
"params": {"message": {"role": "user", "parts": [{"text": "cancel me"}]}},
|
|
204
|
+
"id": "req-c1",
|
|
205
|
+
}
|
|
206
|
+
async with aiohttp.ClientSession() as session:
|
|
207
|
+
async with session.post(server_url, json=send_payload) as resp:
|
|
208
|
+
task_id = (await resp.json())["result"]["id"]
|
|
209
|
+
|
|
210
|
+
# Cancel it
|
|
211
|
+
cancel_payload = {
|
|
212
|
+
"jsonrpc": "2.0",
|
|
213
|
+
"method": "tasks/cancel",
|
|
214
|
+
"params": {"id": task_id},
|
|
215
|
+
"id": "req-c2",
|
|
216
|
+
}
|
|
217
|
+
async with session.post(server_url, json=cancel_payload) as resp:
|
|
218
|
+
data = await resp.json()
|
|
219
|
+
assert data["result"]["status"]["state"] == "canceled"
|
|
220
|
+
|
|
221
|
+
# Verify stored task state
|
|
222
|
+
assert a2a_server.tasks[task_id].status.state == "canceled"
|
|
223
|
+
|
|
224
|
+
async def test_unknown_method(self, server_url: str):
|
|
225
|
+
payload = {
|
|
226
|
+
"jsonrpc": "2.0",
|
|
227
|
+
"method": "unknown/method",
|
|
228
|
+
"params": {},
|
|
229
|
+
"id": "req-unknown",
|
|
230
|
+
}
|
|
231
|
+
async with aiohttp.ClientSession() as session:
|
|
232
|
+
async with session.post(server_url, json=payload) as resp:
|
|
233
|
+
assert resp.status == 400
|
|
234
|
+
data = await resp.json()
|
|
235
|
+
assert data["error"]["code"] == -32601
|
|
236
|
+
|
|
237
|
+
async def test_invalid_json(self, server_url: str):
|
|
238
|
+
async with aiohttp.ClientSession() as session:
|
|
239
|
+
async with session.post(
|
|
240
|
+
server_url,
|
|
241
|
+
data=b"not json",
|
|
242
|
+
headers={"Content-Type": "application/json"},
|
|
243
|
+
) as resp:
|
|
244
|
+
assert resp.status == 400
|
|
245
|
+
data = await resp.json()
|
|
246
|
+
assert data["error"]["code"] == -32700
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# --- Task lifecycle tests ---
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class TestTaskLifecycle:
|
|
253
|
+
async def test_task_submitted_to_completed(self, server_url: str, a2a_server: A2AServer):
|
|
254
|
+
"""Task goes through working -> completed when callback responds."""
|
|
255
|
+
|
|
256
|
+
async def delayed_handler(msg: A2AMessage) -> A2AMessage:
|
|
257
|
+
return A2AMessage(role="agent", parts=[A2APart(text="done")])
|
|
258
|
+
|
|
259
|
+
a2a_server.on_message(delayed_handler)
|
|
260
|
+
|
|
261
|
+
payload = {
|
|
262
|
+
"jsonrpc": "2.0",
|
|
263
|
+
"method": "message/send",
|
|
264
|
+
"params": {"message": {"role": "user", "parts": [{"text": "work"}]}},
|
|
265
|
+
"id": "req-lc",
|
|
266
|
+
}
|
|
267
|
+
async with aiohttp.ClientSession() as session:
|
|
268
|
+
async with session.post(server_url, json=payload) as resp:
|
|
269
|
+
data = await resp.json()
|
|
270
|
+
assert data["result"]["status"]["state"] == "completed"
|
|
271
|
+
|
|
272
|
+
async def test_task_completed_without_callback(self, server_url: str, a2a_server: A2AServer):
|
|
273
|
+
"""Task completes even with no callback registered."""
|
|
274
|
+
payload = {
|
|
275
|
+
"jsonrpc": "2.0",
|
|
276
|
+
"method": "message/send",
|
|
277
|
+
"params": {"message": {"role": "user", "parts": [{"text": "no handler"}]}},
|
|
278
|
+
"id": "req-no-cb",
|
|
279
|
+
}
|
|
280
|
+
async with aiohttp.ClientSession() as session:
|
|
281
|
+
async with session.post(server_url, json=payload) as resp:
|
|
282
|
+
data = await resp.json()
|
|
283
|
+
assert data["result"]["status"]["state"] == "completed"
|
|
284
|
+
assert len(data["result"]["messages"]) == 1
|
|
285
|
+
|
|
286
|
+
async def test_existing_task_updated_with_new_message(self, a2a_server: A2AServer):
|
|
287
|
+
"""Sending a message with an existing taskId appends to that task."""
|
|
288
|
+
task_id = str(uuid.uuid4())
|
|
289
|
+
|
|
290
|
+
# First message
|
|
291
|
+
result1 = await a2a_server.handle_message_send({
|
|
292
|
+
"message": {
|
|
293
|
+
"role": "user",
|
|
294
|
+
"parts": [{"text": "first"}],
|
|
295
|
+
"taskId": task_id,
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
assert result1["id"] == task_id
|
|
299
|
+
assert len(result1["messages"]) == 1
|
|
300
|
+
|
|
301
|
+
# Second message to same task
|
|
302
|
+
result2 = await a2a_server.handle_message_send({
|
|
303
|
+
"message": {
|
|
304
|
+
"role": "user",
|
|
305
|
+
"parts": [{"text": "second"}],
|
|
306
|
+
"taskId": task_id,
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
assert result2["id"] == task_id
|
|
310
|
+
assert len(result2["messages"]) == 2
|
|
311
|
+
|
|
312
|
+
async def test_message_preserves_context_id(self, a2a_server: A2AServer):
|
|
313
|
+
ctx_id = "ctx-123"
|
|
314
|
+
result = await a2a_server.handle_message_send({
|
|
315
|
+
"message": {
|
|
316
|
+
"role": "user",
|
|
317
|
+
"parts": [{"text": "with context"}],
|
|
318
|
+
"contextId": ctx_id,
|
|
319
|
+
}
|
|
320
|
+
})
|
|
321
|
+
assert result["contextId"] == ctx_id
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
# --- Server start/stop ---
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
class TestServerLifecycle:
|
|
328
|
+
async def test_start_and_stop(self):
|
|
329
|
+
server = A2AServer(agent_name="lifecycle-test", port=0, host="127.0.0.1")
|
|
330
|
+
await server.start()
|
|
331
|
+
assert server._actual_port is not None
|
|
332
|
+
assert server._actual_port > 0
|
|
333
|
+
|
|
334
|
+
# Verify server is running
|
|
335
|
+
async with aiohttp.ClientSession() as session:
|
|
336
|
+
async with session.get(f"{server.url}/.well-known/agent.json") as resp:
|
|
337
|
+
assert resp.status == 200
|
|
338
|
+
|
|
339
|
+
await server.stop()
|
|
340
|
+
assert server._runner is None
|
|
341
|
+
|
|
342
|
+
async def test_double_stop_is_safe(self):
|
|
343
|
+
server = A2AServer(agent_name="double-stop", port=0, host="127.0.0.1")
|
|
344
|
+
await server.start()
|
|
345
|
+
await server.stop()
|
|
346
|
+
await server.stop() # Should not raise
|