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,234 @@
|
|
|
1
|
+
"""Real end-to-end test of the CrewAI Python adapter against live Relaycast."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
import time
|
|
8
|
+
import uuid
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from agent_relay.communicate.core import Relay
|
|
13
|
+
from agent_relay.communicate.types import RelayConfig
|
|
14
|
+
|
|
15
|
+
pytestmark = pytest.mark.asyncio
|
|
16
|
+
|
|
17
|
+
RATE_LIMIT_PAUSE = 5 # seconds between tests to avoid 429
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.fixture(autouse=True)
|
|
21
|
+
async def _rate_limit_pause():
|
|
22
|
+
"""Pause between tests to respect the 60 req/min rate limit."""
|
|
23
|
+
yield
|
|
24
|
+
await asyncio.sleep(RATE_LIMIT_PAUSE)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _e2e_config() -> RelayConfig:
|
|
28
|
+
config = RelayConfig.resolve(
|
|
29
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
30
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
31
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
32
|
+
channels=[],
|
|
33
|
+
auto_cleanup=False,
|
|
34
|
+
)
|
|
35
|
+
if not config.workspace or not config.api_key:
|
|
36
|
+
pytest.fail("RELAY_WORKSPACE and RELAY_API_KEY must be set.")
|
|
37
|
+
return config
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _unique_name(prefix: str) -> str:
|
|
41
|
+
ts = int(time.time() * 1000)
|
|
42
|
+
return f"{prefix}-{ts}-{uuid.uuid4().hex[:6]}"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def test_crewai_on_relay_adds_tools():
|
|
46
|
+
"""on_relay() should attach four relay tools to a CrewAI Agent."""
|
|
47
|
+
from crewai import Agent as CrewAgent
|
|
48
|
+
from agent_relay.communicate.adapters.crewai import on_relay
|
|
49
|
+
|
|
50
|
+
config = _e2e_config()
|
|
51
|
+
agent_name = _unique_name("e2e-crewai-tools")
|
|
52
|
+
|
|
53
|
+
relay = Relay(agent_name, config)
|
|
54
|
+
try:
|
|
55
|
+
crew_agent = CrewAgent(
|
|
56
|
+
role="Test worker",
|
|
57
|
+
goal="Verify relay tools",
|
|
58
|
+
backstory="Test agent.",
|
|
59
|
+
llm="gpt-4o-mini",
|
|
60
|
+
)
|
|
61
|
+
original_tool_count = len(crew_agent.tools)
|
|
62
|
+
|
|
63
|
+
wrapped = on_relay(crew_agent, relay)
|
|
64
|
+
assert wrapped is crew_agent
|
|
65
|
+
|
|
66
|
+
tool_names = {t.name for t in crew_agent.tools}
|
|
67
|
+
expected = {"relay_send", "relay_inbox", "relay_post", "relay_agents"}
|
|
68
|
+
assert expected.issubset(tool_names), f"Missing tools: {expected - tool_names}"
|
|
69
|
+
assert len(crew_agent.tools) >= original_tool_count + 4
|
|
70
|
+
finally:
|
|
71
|
+
await relay.close()
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
async def test_crewai_relay_agents_lists_live_agents():
|
|
75
|
+
"""relay_agents tool should list agents from the live workspace."""
|
|
76
|
+
from crewai import Agent as CrewAgent
|
|
77
|
+
from agent_relay.communicate.adapters.crewai import on_relay
|
|
78
|
+
|
|
79
|
+
config = _e2e_config()
|
|
80
|
+
agent_name = _unique_name("e2e-crewai-list")
|
|
81
|
+
|
|
82
|
+
relay = Relay(agent_name, config)
|
|
83
|
+
try:
|
|
84
|
+
crew_agent = CrewAgent(
|
|
85
|
+
role="Lister",
|
|
86
|
+
goal="List agents",
|
|
87
|
+
backstory="Lists agents.",
|
|
88
|
+
llm="gpt-4o-mini",
|
|
89
|
+
)
|
|
90
|
+
on_relay(crew_agent, relay)
|
|
91
|
+
|
|
92
|
+
tools_by_name = {t.name: t for t in crew_agent.tools}
|
|
93
|
+
agents_tool = tools_by_name["relay_agents"]
|
|
94
|
+
|
|
95
|
+
result = await agents_tool.func()
|
|
96
|
+
assert isinstance(result, str)
|
|
97
|
+
assert agent_name in result, f"Expected {agent_name} in agents list, got: {result}"
|
|
98
|
+
finally:
|
|
99
|
+
await relay.close()
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
async def test_crewai_relay_send_dm():
|
|
103
|
+
"""relay_send tool should successfully send a DM via live API."""
|
|
104
|
+
from crewai import Agent as CrewAgent
|
|
105
|
+
from agent_relay.communicate.adapters.crewai import on_relay
|
|
106
|
+
|
|
107
|
+
config = _e2e_config()
|
|
108
|
+
sender_name = _unique_name("e2e-crewai-sender")
|
|
109
|
+
receiver_name = _unique_name("e2e-crewai-recv")
|
|
110
|
+
|
|
111
|
+
sender_relay = Relay(sender_name, config)
|
|
112
|
+
receiver_relay = Relay(receiver_name, config)
|
|
113
|
+
try:
|
|
114
|
+
sender_agent = CrewAgent(
|
|
115
|
+
role="Sender",
|
|
116
|
+
goal="Send messages",
|
|
117
|
+
backstory="Sends.",
|
|
118
|
+
llm="gpt-4o-mini",
|
|
119
|
+
)
|
|
120
|
+
on_relay(sender_agent, relay=sender_relay)
|
|
121
|
+
|
|
122
|
+
# Connect receiver so it exists
|
|
123
|
+
await receiver_relay._ensure_connected()
|
|
124
|
+
|
|
125
|
+
tools_by_name = {t.name: t for t in sender_agent.tools}
|
|
126
|
+
send_tool = tools_by_name["relay_send"]
|
|
127
|
+
|
|
128
|
+
result = await send_tool.func(receiver_name, f"hello-{uuid.uuid4().hex[:8]}")
|
|
129
|
+
assert result == "Message sent"
|
|
130
|
+
finally:
|
|
131
|
+
await asyncio.gather(sender_relay.close(), receiver_relay.close())
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def test_crewai_relay_post_channel():
|
|
135
|
+
"""relay_post tool should successfully post to a channel via live API."""
|
|
136
|
+
from crewai import Agent as CrewAgent
|
|
137
|
+
from agent_relay.communicate.adapters.crewai import on_relay
|
|
138
|
+
|
|
139
|
+
config = _e2e_config()
|
|
140
|
+
agent_name = _unique_name("e2e-crewai-post")
|
|
141
|
+
|
|
142
|
+
relay = Relay(agent_name, config)
|
|
143
|
+
try:
|
|
144
|
+
crew_agent = CrewAgent(
|
|
145
|
+
role="Poster",
|
|
146
|
+
goal="Post to channel",
|
|
147
|
+
backstory="Posts.",
|
|
148
|
+
llm="gpt-4o-mini",
|
|
149
|
+
)
|
|
150
|
+
on_relay(crew_agent, relay=relay)
|
|
151
|
+
|
|
152
|
+
# Join general before posting
|
|
153
|
+
await relay.join("general")
|
|
154
|
+
|
|
155
|
+
tools_by_name = {t.name: t for t in crew_agent.tools}
|
|
156
|
+
post_tool = tools_by_name["relay_post"]
|
|
157
|
+
|
|
158
|
+
msg_text = f"e2e-crewai-post-{uuid.uuid4().hex[:8]}"
|
|
159
|
+
result = await post_tool.func("general", msg_text)
|
|
160
|
+
assert result == "Message posted"
|
|
161
|
+
finally:
|
|
162
|
+
await relay.close()
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def test_crewai_backstory_wrapping():
|
|
166
|
+
"""on_relay() should wrap backstory so it includes relay messages."""
|
|
167
|
+
from crewai import Agent as CrewAgent
|
|
168
|
+
from agent_relay.communicate.adapters.crewai import on_relay
|
|
169
|
+
|
|
170
|
+
config = _e2e_config()
|
|
171
|
+
agent_name = _unique_name("e2e-crewai-backstory")
|
|
172
|
+
|
|
173
|
+
relay = Relay(agent_name, config)
|
|
174
|
+
try:
|
|
175
|
+
original_backstory = "Expert researcher."
|
|
176
|
+
crew_agent = CrewAgent(
|
|
177
|
+
role="Researcher",
|
|
178
|
+
goal="Research things",
|
|
179
|
+
backstory=original_backstory,
|
|
180
|
+
llm="gpt-4o-mini",
|
|
181
|
+
)
|
|
182
|
+
on_relay(crew_agent, relay=relay)
|
|
183
|
+
|
|
184
|
+
backstory_str = str(crew_agent.backstory)
|
|
185
|
+
assert "Expert researcher." in backstory_str
|
|
186
|
+
finally:
|
|
187
|
+
await relay.close()
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
async def test_crewai_full_round_trip():
|
|
191
|
+
"""Full round-trip: register, list agents, send DM, post to channel, close."""
|
|
192
|
+
from crewai import Agent as CrewAgent
|
|
193
|
+
from agent_relay.communicate.adapters.crewai import on_relay
|
|
194
|
+
|
|
195
|
+
config = _e2e_config()
|
|
196
|
+
agent_name = _unique_name("e2e-crewai-full")
|
|
197
|
+
peer_name = _unique_name("e2e-crewai-peer")
|
|
198
|
+
|
|
199
|
+
relay = Relay(agent_name, config)
|
|
200
|
+
peer_relay = Relay(peer_name, config)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
crew_agent = CrewAgent(
|
|
204
|
+
role="Full test",
|
|
205
|
+
goal="Run full e2e",
|
|
206
|
+
backstory="Full round trip.",
|
|
207
|
+
llm="gpt-4o-mini",
|
|
208
|
+
)
|
|
209
|
+
on_relay(crew_agent, relay=relay)
|
|
210
|
+
await peer_relay._ensure_connected()
|
|
211
|
+
|
|
212
|
+
tools = {t.name: t for t in crew_agent.tools}
|
|
213
|
+
|
|
214
|
+
# 1. list_agents
|
|
215
|
+
agents_result = await tools["relay_agents"].func()
|
|
216
|
+
assert agent_name in agents_result
|
|
217
|
+
assert peer_name in agents_result
|
|
218
|
+
|
|
219
|
+
# 2. send DM
|
|
220
|
+
dm_text = f"e2e-roundtrip-{uuid.uuid4().hex[:8]}"
|
|
221
|
+
send_result = await tools["relay_send"].func(peer_name, dm_text)
|
|
222
|
+
assert send_result == "Message sent"
|
|
223
|
+
|
|
224
|
+
# 3. post to channel
|
|
225
|
+
await relay.join("general")
|
|
226
|
+
post_text = f"e2e-channel-{uuid.uuid4().hex[:8]}"
|
|
227
|
+
post_result = await tools["relay_post"].func("general", post_text)
|
|
228
|
+
assert post_result == "Message posted"
|
|
229
|
+
|
|
230
|
+
# 4. inbox (may or may not have messages, just verify it runs)
|
|
231
|
+
inbox_result = await tools["relay_inbox"].func()
|
|
232
|
+
assert isinstance(inbox_result, str)
|
|
233
|
+
finally:
|
|
234
|
+
await asyncio.gather(relay.close(), peer_relay.close())
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""E2E test: Google ADK Python adapter against live Relaycast.
|
|
2
|
+
|
|
3
|
+
Single consolidated test to stay within the 60 req/min rate limit.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
from types import ModuleType
|
|
14
|
+
from unittest.mock import MagicMock
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
from agent_relay.communicate.core import Relay
|
|
19
|
+
from agent_relay.communicate.types import RelayConfig
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _install_google_modules(monkeypatch):
|
|
23
|
+
"""Inject fake google.adk / google.genai modules so the adapter can import."""
|
|
24
|
+
google_module = ModuleType("google")
|
|
25
|
+
google_adk_module = ModuleType("google.adk")
|
|
26
|
+
google_adk_agents_module = ModuleType("google.adk.agents")
|
|
27
|
+
google_genai_module = ModuleType("google.genai")
|
|
28
|
+
google_genai_types_module = ModuleType("google.genai.types")
|
|
29
|
+
|
|
30
|
+
class Part:
|
|
31
|
+
def __init__(self, text: str):
|
|
32
|
+
self.text = text
|
|
33
|
+
|
|
34
|
+
class Content:
|
|
35
|
+
def __init__(self, role: str, parts: list):
|
|
36
|
+
self.role = role
|
|
37
|
+
self.parts = parts
|
|
38
|
+
|
|
39
|
+
google_module.adk = google_adk_module
|
|
40
|
+
google_module.genai = google_genai_module
|
|
41
|
+
google_adk_module.agents = google_adk_agents_module
|
|
42
|
+
google_genai_module.types = google_genai_types_module
|
|
43
|
+
google_genai_types_module.Content = Content
|
|
44
|
+
google_genai_types_module.Part = Part
|
|
45
|
+
|
|
46
|
+
monkeypatch.setitem(sys.modules, "google", google_module)
|
|
47
|
+
monkeypatch.setitem(sys.modules, "google.adk", google_adk_module)
|
|
48
|
+
monkeypatch.setitem(sys.modules, "google.adk.agents", google_adk_agents_module)
|
|
49
|
+
monkeypatch.setitem(sys.modules, "google.genai", google_genai_module)
|
|
50
|
+
monkeypatch.setitem(sys.modules, "google.genai.types", google_genai_types_module)
|
|
51
|
+
|
|
52
|
+
return google_genai_types_module
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _live_config() -> RelayConfig:
|
|
56
|
+
return RelayConfig.resolve(
|
|
57
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
58
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
59
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
60
|
+
channels=[],
|
|
61
|
+
auto_cleanup=False,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _unique_name(prefix: str = "e2e-adk-py") -> str:
|
|
66
|
+
return f"{prefix}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _make_mock_agent(name: str):
|
|
70
|
+
agent = MagicMock()
|
|
71
|
+
agent.name = name
|
|
72
|
+
agent.tools = []
|
|
73
|
+
agent.before_model_callback = None
|
|
74
|
+
type(agent).__module__ = "google.adk.agents"
|
|
75
|
+
return agent
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _tool_by_name(agent, name: str):
|
|
79
|
+
return next(t for t in agent.tools if t.__name__ == name)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@pytest.mark.asyncio
|
|
83
|
+
async def test_google_adk_e2e_full_round_trip(monkeypatch):
|
|
84
|
+
"""Full round-trip: real Relay, mock ADK Agent, live Relaycast API.
|
|
85
|
+
|
|
86
|
+
Exercises on_relay tool injection, list_agents, post, send/inbox,
|
|
87
|
+
before_model_callback, and cleanup -- all in one test to stay
|
|
88
|
+
within rate limits.
|
|
89
|
+
"""
|
|
90
|
+
types_mod = _install_google_modules(monkeypatch)
|
|
91
|
+
from agent_relay.communicate.adapters.google_adk import on_relay
|
|
92
|
+
|
|
93
|
+
sender_name = _unique_name("adk-sender")
|
|
94
|
+
receiver_name = _unique_name("adk-recv")
|
|
95
|
+
config = _live_config()
|
|
96
|
+
|
|
97
|
+
sender_relay = Relay(sender_name, config)
|
|
98
|
+
receiver_relay = Relay(receiver_name, config)
|
|
99
|
+
sender_agent = _make_mock_agent(sender_name)
|
|
100
|
+
receiver_agent = _make_mock_agent(receiver_name)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# -- Step 1: on_relay injects 4 tools + before_model_callback --
|
|
104
|
+
wrapped_s = on_relay(sender_agent, sender_relay)
|
|
105
|
+
wrapped_r = on_relay(receiver_agent, receiver_relay)
|
|
106
|
+
|
|
107
|
+
assert wrapped_s is sender_agent
|
|
108
|
+
assert wrapped_r is receiver_agent
|
|
109
|
+
|
|
110
|
+
expected_tools = {"relay_send", "relay_inbox", "relay_post", "relay_agents"}
|
|
111
|
+
assert set(t.__name__ for t in sender_agent.tools) == expected_tools
|
|
112
|
+
assert set(t.__name__ for t in receiver_agent.tools) == expected_tools
|
|
113
|
+
assert receiver_agent.before_model_callback is not None
|
|
114
|
+
|
|
115
|
+
# -- Step 2: list_agents via tool closure (live API) --
|
|
116
|
+
# Ensure both agents are registered before any cross-agent calls
|
|
117
|
+
agents_result = await _tool_by_name(sender_agent, "relay_agents")()
|
|
118
|
+
assert isinstance(agents_result, str)
|
|
119
|
+
assert sender_name in agents_result
|
|
120
|
+
|
|
121
|
+
receiver_agents = await _tool_by_name(receiver_agent, "relay_agents")()
|
|
122
|
+
assert receiver_name in receiver_agents
|
|
123
|
+
|
|
124
|
+
# -- Step 3: post to general channel via tool closure --
|
|
125
|
+
await sender_relay.join("general")
|
|
126
|
+
post_result = await _tool_by_name(sender_agent, "relay_post")(
|
|
127
|
+
"general", f"adk-e2e-{uuid.uuid4().hex[:8]}"
|
|
128
|
+
)
|
|
129
|
+
assert post_result == "Message posted"
|
|
130
|
+
|
|
131
|
+
# -- Step 4: send DM and verify inbox round-trip --
|
|
132
|
+
dm_text = f"adk-dm-{uuid.uuid4().hex[:8]}"
|
|
133
|
+
send_fn = _tool_by_name(sender_agent, "relay_send")
|
|
134
|
+
result = await send_fn(receiver_name, dm_text)
|
|
135
|
+
assert result == "Message sent"
|
|
136
|
+
|
|
137
|
+
inbox_fn = _tool_by_name(receiver_agent, "relay_inbox")
|
|
138
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
139
|
+
found = False
|
|
140
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
141
|
+
inbox_result = await inbox_fn()
|
|
142
|
+
if dm_text in inbox_result:
|
|
143
|
+
found = True
|
|
144
|
+
break
|
|
145
|
+
await asyncio.sleep(0.5)
|
|
146
|
+
assert found, f"DM containing '{dm_text}' not received within timeout"
|
|
147
|
+
|
|
148
|
+
# -- Step 5: before_model_callback injects relay messages --
|
|
149
|
+
cb_text = f"adk-cb-{uuid.uuid4().hex[:8]}"
|
|
150
|
+
await sender_relay.send(receiver_name, cb_text)
|
|
151
|
+
|
|
152
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
153
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
154
|
+
msgs = await receiver_relay.peek()
|
|
155
|
+
if any(cb_text in m.text for m in msgs):
|
|
156
|
+
break
|
|
157
|
+
await asyncio.sleep(0.5)
|
|
158
|
+
|
|
159
|
+
llm_request = MagicMock()
|
|
160
|
+
llm_request.contents = []
|
|
161
|
+
cb_result = await receiver_agent.before_model_callback(llm_request)
|
|
162
|
+
assert cb_result is None
|
|
163
|
+
|
|
164
|
+
injected = [p.text for c in llm_request.contents for p in c.parts]
|
|
165
|
+
assert any(cb_text in t for t in injected), (
|
|
166
|
+
f"Expected '{cb_text}' in callback-injected contents, got: {injected}"
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
# -- Step 6: cleanup -- close sender, verify internal state reset --
|
|
170
|
+
await sender_relay.close()
|
|
171
|
+
assert not sender_relay._connected
|
|
172
|
+
assert not sender_relay._ws_connected
|
|
173
|
+
|
|
174
|
+
finally:
|
|
175
|
+
try:
|
|
176
|
+
await sender_relay.close()
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
try:
|
|
180
|
+
await receiver_relay.close()
|
|
181
|
+
except Exception:
|
|
182
|
+
pass
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
"""E2E test: LangGraph-style tool wrapping with core Relay against live Relaycast.
|
|
2
|
+
|
|
3
|
+
There is no dedicated LangGraph Python adapter — this test demonstrates using
|
|
4
|
+
the core Relay class with LangGraph-compatible tool functions (plain async
|
|
5
|
+
callables that can be wrapped with @tool or used directly in a ToolNode).
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import asyncio
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
from agent_relay.communicate.core import Relay
|
|
18
|
+
from agent_relay.communicate.types import RelayConfig
|
|
19
|
+
|
|
20
|
+
pytestmark = pytest.mark.asyncio
|
|
21
|
+
|
|
22
|
+
RATE_LIMIT_PAUSE = 5 # seconds between tests to avoid 429
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(autouse=True)
|
|
26
|
+
async def _rate_limit_pause():
|
|
27
|
+
"""Pause between tests to respect the 60 req/min rate limit."""
|
|
28
|
+
yield
|
|
29
|
+
await asyncio.sleep(RATE_LIMIT_PAUSE)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _e2e_config() -> RelayConfig:
|
|
33
|
+
config = RelayConfig.resolve(
|
|
34
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
35
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
36
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
37
|
+
channels=[],
|
|
38
|
+
auto_cleanup=False,
|
|
39
|
+
)
|
|
40
|
+
if not config.workspace or not config.api_key:
|
|
41
|
+
pytest.fail("RELAY_WORKSPACE and RELAY_API_KEY must be set.")
|
|
42
|
+
return config
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _unique_name(prefix: str = "e2e-langgraph") -> str:
|
|
46
|
+
ts = int(time.time() * 1000)
|
|
47
|
+
return f"{prefix}-{ts}-{uuid.uuid4().hex[:6]}"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
# LangGraph-style tool factories
|
|
52
|
+
# These mirror what a real LangGraph ToolNode would use.
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
def make_relay_tools(relay: Relay) -> dict[str, object]:
|
|
56
|
+
"""Create LangGraph-compatible async tool functions backed by a Relay instance."""
|
|
57
|
+
|
|
58
|
+
async def relay_send(to: str, message: str) -> str:
|
|
59
|
+
"""Send a DM to another agent."""
|
|
60
|
+
await relay.send(to, message)
|
|
61
|
+
return "Message sent"
|
|
62
|
+
|
|
63
|
+
async def relay_inbox() -> str:
|
|
64
|
+
"""Check the inbox for new messages."""
|
|
65
|
+
messages = await relay.inbox()
|
|
66
|
+
if not messages:
|
|
67
|
+
return "No new messages"
|
|
68
|
+
return "\n".join(f"[{m.sender}] {m.text}" for m in messages)
|
|
69
|
+
|
|
70
|
+
async def relay_agents() -> str:
|
|
71
|
+
"""List all connected agents."""
|
|
72
|
+
agents = await relay.agents()
|
|
73
|
+
return ", ".join(agents) if agents else "No agents online"
|
|
74
|
+
|
|
75
|
+
async def relay_post(channel: str, message: str) -> str:
|
|
76
|
+
"""Post a message to a channel."""
|
|
77
|
+
await relay.post(channel, message)
|
|
78
|
+
return "Message posted"
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"relay_send": relay_send,
|
|
82
|
+
"relay_inbox": relay_inbox,
|
|
83
|
+
"relay_agents": relay_agents,
|
|
84
|
+
"relay_post": relay_post,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ---------------------------------------------------------------------------
|
|
89
|
+
# Tests
|
|
90
|
+
# ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
async def test_langgraph_tool_creation():
|
|
93
|
+
"""make_relay_tools() should return four async callables."""
|
|
94
|
+
config = _e2e_config()
|
|
95
|
+
agent_name = _unique_name("lg-tools")
|
|
96
|
+
|
|
97
|
+
relay = Relay(agent_name, config)
|
|
98
|
+
try:
|
|
99
|
+
tools = make_relay_tools(relay)
|
|
100
|
+
expected = {"relay_send", "relay_inbox", "relay_agents", "relay_post"}
|
|
101
|
+
assert set(tools.keys()) == expected
|
|
102
|
+
for fn in tools.values():
|
|
103
|
+
assert asyncio.iscoroutinefunction(fn)
|
|
104
|
+
finally:
|
|
105
|
+
await relay.close()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
async def test_langgraph_relay_agents_live():
|
|
109
|
+
"""relay_agents tool should list agents from the live workspace."""
|
|
110
|
+
config = _e2e_config()
|
|
111
|
+
agent_name = _unique_name("lg-list")
|
|
112
|
+
|
|
113
|
+
relay = Relay(agent_name, config)
|
|
114
|
+
try:
|
|
115
|
+
tools = make_relay_tools(relay)
|
|
116
|
+
result = await tools["relay_agents"]()
|
|
117
|
+
assert isinstance(result, str)
|
|
118
|
+
assert agent_name in result, f"Expected {agent_name} in agents list, got: {result}"
|
|
119
|
+
finally:
|
|
120
|
+
await relay.close()
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
async def test_langgraph_relay_send_dm():
|
|
124
|
+
"""relay_send tool should successfully send a DM via live API."""
|
|
125
|
+
config = _e2e_config()
|
|
126
|
+
sender_name = _unique_name("lg-sender")
|
|
127
|
+
receiver_name = _unique_name("lg-recv")
|
|
128
|
+
|
|
129
|
+
sender_relay = Relay(sender_name, config)
|
|
130
|
+
receiver_relay = Relay(receiver_name, config)
|
|
131
|
+
try:
|
|
132
|
+
await receiver_relay._ensure_connected()
|
|
133
|
+
|
|
134
|
+
tools = make_relay_tools(sender_relay)
|
|
135
|
+
result = await tools["relay_send"](receiver_name, f"hello-{uuid.uuid4().hex[:8]}")
|
|
136
|
+
assert result == "Message sent"
|
|
137
|
+
finally:
|
|
138
|
+
await asyncio.gather(sender_relay.close(), receiver_relay.close())
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def test_langgraph_relay_post_channel():
|
|
142
|
+
"""relay_post tool should successfully post to a channel via live API."""
|
|
143
|
+
config = _e2e_config()
|
|
144
|
+
agent_name = _unique_name("lg-post")
|
|
145
|
+
|
|
146
|
+
relay = Relay(agent_name, config)
|
|
147
|
+
try:
|
|
148
|
+
await relay.join("general")
|
|
149
|
+
|
|
150
|
+
tools = make_relay_tools(relay)
|
|
151
|
+
msg_text = f"e2e-lg-post-{uuid.uuid4().hex[:8]}"
|
|
152
|
+
result = await tools["relay_post"]("general", msg_text)
|
|
153
|
+
assert result == "Message posted"
|
|
154
|
+
finally:
|
|
155
|
+
await relay.close()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
async def test_langgraph_inbox_round_trip():
|
|
159
|
+
"""Send a DM and verify it appears in the receiver's inbox."""
|
|
160
|
+
config = _e2e_config()
|
|
161
|
+
sender_name = _unique_name("lg-inbox-s")
|
|
162
|
+
receiver_name = _unique_name("lg-inbox-r")
|
|
163
|
+
|
|
164
|
+
sender_relay = Relay(sender_name, config)
|
|
165
|
+
receiver_relay = Relay(receiver_name, config)
|
|
166
|
+
try:
|
|
167
|
+
sender_tools = make_relay_tools(sender_relay)
|
|
168
|
+
receiver_tools = make_relay_tools(receiver_relay)
|
|
169
|
+
|
|
170
|
+
dm_text = f"lg-dm-{uuid.uuid4().hex[:8]}"
|
|
171
|
+
await sender_tools["relay_send"](receiver_name, dm_text)
|
|
172
|
+
|
|
173
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
174
|
+
found = False
|
|
175
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
176
|
+
inbox_result = await receiver_tools["relay_inbox"]()
|
|
177
|
+
if dm_text in inbox_result:
|
|
178
|
+
found = True
|
|
179
|
+
break
|
|
180
|
+
await asyncio.sleep(0.5)
|
|
181
|
+
assert found, f"DM containing '{dm_text}' not received within timeout"
|
|
182
|
+
finally:
|
|
183
|
+
await asyncio.gather(sender_relay.close(), receiver_relay.close())
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
async def test_langgraph_full_round_trip():
|
|
187
|
+
"""Full round-trip: register, list agents, send DM, post to channel, inbox, close."""
|
|
188
|
+
config = _e2e_config()
|
|
189
|
+
agent_name = _unique_name("lg-full")
|
|
190
|
+
peer_name = _unique_name("lg-peer")
|
|
191
|
+
|
|
192
|
+
relay = Relay(agent_name, config)
|
|
193
|
+
peer_relay = Relay(peer_name, config)
|
|
194
|
+
|
|
195
|
+
try:
|
|
196
|
+
tools = make_relay_tools(relay)
|
|
197
|
+
peer_tools = make_relay_tools(peer_relay)
|
|
198
|
+
|
|
199
|
+
# Ensure peer is connected
|
|
200
|
+
await peer_relay._ensure_connected()
|
|
201
|
+
|
|
202
|
+
# 1. list_agents — both should appear
|
|
203
|
+
agents_result = await tools["relay_agents"]()
|
|
204
|
+
assert agent_name in agents_result
|
|
205
|
+
assert peer_name in agents_result
|
|
206
|
+
|
|
207
|
+
# 2. send DM
|
|
208
|
+
dm_text = f"lg-roundtrip-{uuid.uuid4().hex[:8]}"
|
|
209
|
+
send_result = await tools["relay_send"](peer_name, dm_text)
|
|
210
|
+
assert send_result == "Message sent"
|
|
211
|
+
|
|
212
|
+
# 3. post to channel
|
|
213
|
+
await relay.join("general")
|
|
214
|
+
post_text = f"lg-channel-{uuid.uuid4().hex[:8]}"
|
|
215
|
+
post_result = await tools["relay_post"]("general", post_text)
|
|
216
|
+
assert post_result == "Message posted"
|
|
217
|
+
|
|
218
|
+
# 4. inbox on peer — verify DM arrived
|
|
219
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
220
|
+
found = False
|
|
221
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
222
|
+
inbox_result = await peer_tools["relay_inbox"]()
|
|
223
|
+
if dm_text in inbox_result:
|
|
224
|
+
found = True
|
|
225
|
+
break
|
|
226
|
+
await asyncio.sleep(0.5)
|
|
227
|
+
assert found, f"DM containing '{dm_text}' not received within timeout"
|
|
228
|
+
|
|
229
|
+
finally:
|
|
230
|
+
await asyncio.gather(relay.close(), peer_relay.close())
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@pytest.mark.xfail(reason="Server TTL behavior — agent may linger after disconnect")
|
|
234
|
+
async def test_langgraph_disconnect_removes_agent():
|
|
235
|
+
"""After close(), the agent should no longer appear in the agents list."""
|
|
236
|
+
config = _e2e_config()
|
|
237
|
+
agent_name = _unique_name("lg-disc")
|
|
238
|
+
observer_name = _unique_name("lg-obs")
|
|
239
|
+
|
|
240
|
+
relay = Relay(agent_name, config)
|
|
241
|
+
observer_relay = Relay(observer_name, config)
|
|
242
|
+
try:
|
|
243
|
+
await relay._ensure_connected()
|
|
244
|
+
observer_tools = make_relay_tools(observer_relay)
|
|
245
|
+
|
|
246
|
+
# Verify agent is listed
|
|
247
|
+
agents_before = await observer_tools["relay_agents"]()
|
|
248
|
+
assert agent_name in agents_before
|
|
249
|
+
|
|
250
|
+
# Disconnect
|
|
251
|
+
await relay.close()
|
|
252
|
+
await asyncio.sleep(2)
|
|
253
|
+
|
|
254
|
+
# Agent should be gone (xfail: server TTL may keep it)
|
|
255
|
+
agents_after = await observer_tools["relay_agents"]()
|
|
256
|
+
assert agent_name not in agents_after
|
|
257
|
+
finally:
|
|
258
|
+
try:
|
|
259
|
+
await relay.close()
|
|
260
|
+
except Exception:
|
|
261
|
+
pass
|
|
262
|
+
await observer_relay.close()
|