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,88 @@
|
|
|
1
|
+
"""E2E test: OpenAI Agents Python adapter against live Relaycast."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import sys
|
|
7
|
+
import uuid
|
|
8
|
+
import time
|
|
9
|
+
from types import ModuleType
|
|
10
|
+
from unittest.mock import MagicMock
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from agent_relay.communicate.core import Relay
|
|
15
|
+
from agent_relay.communicate.types import RelayConfig
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _install_agents_module(monkeypatch):
|
|
19
|
+
"""Inject a fake 'agents' module so the adapter can import function_tool."""
|
|
20
|
+
agents_mod = ModuleType("agents")
|
|
21
|
+
agents_mod.Agent = type("Agent", (), {})
|
|
22
|
+
agents_mod.function_tool = MagicMock(side_effect=lambda func: func)
|
|
23
|
+
monkeypatch.setitem(sys.modules, "agents", agents_mod)
|
|
24
|
+
return agents_mod
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _make_mock_agent(name: str):
|
|
28
|
+
agent = MagicMock()
|
|
29
|
+
agent.name = name
|
|
30
|
+
agent.tools = []
|
|
31
|
+
agent.instructions = "You are a helpful agent."
|
|
32
|
+
type(agent).__module__ = "agents"
|
|
33
|
+
return agent
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _unique_name(prefix: str) -> str:
|
|
37
|
+
return f"{prefix}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:6]}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.mark.asyncio
|
|
41
|
+
async def test_openai_agents_e2e(monkeypatch):
|
|
42
|
+
"""Full round-trip: real Relay, mock Agent, live Relaycast API."""
|
|
43
|
+
agents_mod = _install_agents_module(monkeypatch)
|
|
44
|
+
|
|
45
|
+
from agent_relay.communicate.adapters.openai_agents import on_relay
|
|
46
|
+
|
|
47
|
+
agent_name = _unique_name("oai-py-e2e")
|
|
48
|
+
config = RelayConfig.resolve(channels=["general"], auto_cleanup=False)
|
|
49
|
+
|
|
50
|
+
relay = Relay(agent_name, config)
|
|
51
|
+
mock_agent = _make_mock_agent(agent_name)
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Step 1: wrap agent — tools should be injected
|
|
55
|
+
wrapped = on_relay(mock_agent, relay)
|
|
56
|
+
assert wrapped is mock_agent
|
|
57
|
+
assert agents_mod.function_tool.call_count == 4
|
|
58
|
+
tool_names = [c.args[0].__name__ for c in agents_mod.function_tool.call_args_list]
|
|
59
|
+
assert set(tool_names) == {"relay_send", "relay_inbox", "relay_post", "relay_agents"}
|
|
60
|
+
assert len(mock_agent.tools) == 4
|
|
61
|
+
|
|
62
|
+
# Step 2: list_agents against real API
|
|
63
|
+
agents_list = await relay.agents()
|
|
64
|
+
assert isinstance(agents_list, list)
|
|
65
|
+
assert agent_name in agents_list, f"{agent_name} not in {agents_list}"
|
|
66
|
+
|
|
67
|
+
# Step 3: post a message to the general channel
|
|
68
|
+
test_text = f"e2e-openai-py-{uuid.uuid4().hex[:8]}"
|
|
69
|
+
await relay.post("general", test_text)
|
|
70
|
+
|
|
71
|
+
# Step 4: invoke the relay_agents tool closure
|
|
72
|
+
relay_agents_fn = mock_agent.tools[3]
|
|
73
|
+
result = await relay_agents_fn()
|
|
74
|
+
assert isinstance(result, str)
|
|
75
|
+
assert agent_name in result
|
|
76
|
+
|
|
77
|
+
# Step 5: invoke the relay_post tool closure
|
|
78
|
+
relay_post_fn = mock_agent.tools[2]
|
|
79
|
+
post_result = await relay_post_fn("general", f"tool-post-{uuid.uuid4().hex[:6]}")
|
|
80
|
+
assert post_result == "Message posted"
|
|
81
|
+
|
|
82
|
+
# Step 6: instructions wrapper returns base instructions when no pending msgs
|
|
83
|
+
assert callable(mock_agent.instructions)
|
|
84
|
+
instr = await mock_agent.instructions()
|
|
85
|
+
assert "You are a helpful agent." in instr
|
|
86
|
+
|
|
87
|
+
finally:
|
|
88
|
+
await relay.close()
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""End-to-end tests for the Pi RPC Python adapter against live Relaycast.
|
|
2
|
+
|
|
3
|
+
Reuses a small number of Relay connections across tests to stay within
|
|
4
|
+
the 60 req/min rate limit of the free plan.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import asyncio
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import time
|
|
13
|
+
import uuid
|
|
14
|
+
from unittest.mock import MagicMock, patch
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
from agent_relay.communicate import Relay
|
|
19
|
+
from agent_relay.communicate.adapters.pi import RELAY_TOOL_PREAMBLE, PiRpcSession, on_relay
|
|
20
|
+
from agent_relay.communicate.types import Message, RelayConfig
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _live_config() -> RelayConfig:
|
|
24
|
+
return RelayConfig.resolve(
|
|
25
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
26
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
27
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
28
|
+
channels=[],
|
|
29
|
+
auto_cleanup=False,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _unique_name(prefix: str = "e2e-pi-py") -> str:
|
|
34
|
+
return f"{prefix}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _make_mock_proc():
|
|
38
|
+
proc = MagicMock()
|
|
39
|
+
proc.stdin = MagicMock()
|
|
40
|
+
proc.stdout = iter([])
|
|
41
|
+
proc.stderr = MagicMock()
|
|
42
|
+
proc.poll = MagicMock(return_value=None)
|
|
43
|
+
proc.terminate = MagicMock()
|
|
44
|
+
proc.wait = MagicMock()
|
|
45
|
+
return proc
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.mark.asyncio
|
|
49
|
+
async def test_pi_adapter_e2e():
|
|
50
|
+
"""Comprehensive e2e test for the Pi adapter against live Relaycast.
|
|
51
|
+
|
|
52
|
+
Uses only 2 Relay connections to minimize API calls and stay within rate limits.
|
|
53
|
+
Tests cover:
|
|
54
|
+
1. PiRpcSession creation via on_relay() with a real Relay
|
|
55
|
+
2. RELAY_TOOL_PREAMBLE contents
|
|
56
|
+
3. Relay registration against live API (unique agent name with uuid suffix)
|
|
57
|
+
4. relay.agents() - live agent list
|
|
58
|
+
5. relay.send() + relay.inbox() - DM round-trip
|
|
59
|
+
6. relay.post() - channel posting
|
|
60
|
+
7. PiRpcSession relay message callback routing
|
|
61
|
+
8. Cleanup / disconnect
|
|
62
|
+
"""
|
|
63
|
+
config = _live_config()
|
|
64
|
+
sender_name = _unique_name("e2e-pi-sender")
|
|
65
|
+
receiver_name = _unique_name("e2e-pi-recv")
|
|
66
|
+
|
|
67
|
+
sender = Relay(sender_name, config)
|
|
68
|
+
receiver = Relay(receiver_name, config)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
# --- 1. Registration: both agents connect and appear in agent list ---
|
|
72
|
+
agents = await sender.agents()
|
|
73
|
+
assert sender_name in agents, f"{sender_name} not in agents after connect"
|
|
74
|
+
|
|
75
|
+
await asyncio.sleep(1)
|
|
76
|
+
agents2 = await receiver.agents()
|
|
77
|
+
assert receiver_name in agents2, f"{receiver_name} not in agents after connect"
|
|
78
|
+
assert sender_name in agents2, f"{sender_name} not visible to receiver"
|
|
79
|
+
|
|
80
|
+
# --- 2. RELAY_TOOL_PREAMBLE has expected relay tool descriptions ---
|
|
81
|
+
assert "relay_send" in RELAY_TOOL_PREAMBLE
|
|
82
|
+
assert "relay_inbox" in RELAY_TOOL_PREAMBLE
|
|
83
|
+
assert "relay_agents" in RELAY_TOOL_PREAMBLE
|
|
84
|
+
assert "relay_post" in RELAY_TOOL_PREAMBLE
|
|
85
|
+
|
|
86
|
+
# --- 3. on_relay() creates a PiRpcSession backed by a real Relay ---
|
|
87
|
+
with patch("subprocess.Popen") as mock_popen:
|
|
88
|
+
proc = _make_mock_proc()
|
|
89
|
+
mock_popen.return_value = proc
|
|
90
|
+
|
|
91
|
+
session = on_relay(sender_name, relay=sender)
|
|
92
|
+
|
|
93
|
+
assert isinstance(session, PiRpcSession)
|
|
94
|
+
assert session._relay is sender
|
|
95
|
+
assert session._relay.agent_name == sender_name
|
|
96
|
+
|
|
97
|
+
# --- 4. Relay message callback routes to Pi subprocess ---
|
|
98
|
+
msg = Message(sender="test-lead", text="status update request")
|
|
99
|
+
callback = sender._callbacks[0]
|
|
100
|
+
callback(msg)
|
|
101
|
+
|
|
102
|
+
written = proc.stdin.write.call_args[0][0]
|
|
103
|
+
parsed = json.loads(written.strip())
|
|
104
|
+
assert "test-lead" in parsed["message"]
|
|
105
|
+
assert "status update request" in parsed["message"]
|
|
106
|
+
assert parsed["streamingBehavior"] == "followUp"
|
|
107
|
+
|
|
108
|
+
# Clean up session (subprocess mock only)
|
|
109
|
+
session.close()
|
|
110
|
+
proc.terminate.assert_called_once()
|
|
111
|
+
assert session._closed
|
|
112
|
+
|
|
113
|
+
await asyncio.sleep(1)
|
|
114
|
+
|
|
115
|
+
# --- 5. DM round-trip: send() + inbox() ---
|
|
116
|
+
text = f"pi-e2e-{uuid.uuid4().hex[:8]}"
|
|
117
|
+
await sender.send(receiver_name, text)
|
|
118
|
+
|
|
119
|
+
deadline = asyncio.get_event_loop().time() + 15.0
|
|
120
|
+
found = False
|
|
121
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
122
|
+
messages = await receiver.inbox()
|
|
123
|
+
for m in messages:
|
|
124
|
+
if m.sender == sender_name and m.text == text:
|
|
125
|
+
found = True
|
|
126
|
+
break
|
|
127
|
+
if found:
|
|
128
|
+
break
|
|
129
|
+
await asyncio.sleep(1.0)
|
|
130
|
+
|
|
131
|
+
assert found, f"DM from {sender_name} not received within timeout"
|
|
132
|
+
|
|
133
|
+
await asyncio.sleep(1)
|
|
134
|
+
|
|
135
|
+
# --- 6. Post to channel ---
|
|
136
|
+
await sender.join("general")
|
|
137
|
+
await sender.post("general", f"pi-e2e-test-{uuid.uuid4().hex[:8]}")
|
|
138
|
+
|
|
139
|
+
# --- 7. Cleanup: close both relays ---
|
|
140
|
+
# Relaycast presence is eventually consistent -- agents may remain
|
|
141
|
+
# "online" in list_agents for a heartbeat window after disconnect.
|
|
142
|
+
# We verify that close() completes without error (same approach as
|
|
143
|
+
# the existing transport e2e tests).
|
|
144
|
+
await sender.close()
|
|
145
|
+
assert not sender._connected, "sender._connected should be False after close"
|
|
146
|
+
await receiver.close()
|
|
147
|
+
assert not receiver._connected, "receiver._connected should be False after close"
|
|
148
|
+
|
|
149
|
+
except Exception:
|
|
150
|
+
# Best-effort cleanup on failure
|
|
151
|
+
for r in (sender, receiver):
|
|
152
|
+
try:
|
|
153
|
+
await r.close()
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
raise
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
"""E2E test: Swarms Python adapter against live Relaycast."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import time
|
|
7
|
+
import uuid
|
|
8
|
+
from unittest.mock import MagicMock
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
from agent_relay.communicate.core import Relay
|
|
13
|
+
from agent_relay.communicate.types import Message, RelayConfig, RelayConnectionError
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _live_config() -> RelayConfig:
|
|
17
|
+
return RelayConfig.resolve(channels=[], auto_cleanup=False)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _unique_name(prefix: str = "e2e-swarms-py") -> str:
|
|
21
|
+
return f"{prefix}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _make_mock_agent(name: str):
|
|
25
|
+
agent = MagicMock()
|
|
26
|
+
agent.name = name
|
|
27
|
+
agent.tools = []
|
|
28
|
+
agent.receive_message = MagicMock()
|
|
29
|
+
type(agent).__module__ = "swarms"
|
|
30
|
+
return agent
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
async def _retry_on_429(coro_fn, max_retries=5, base_delay=15.0):
|
|
34
|
+
"""Call an async function, retrying on 429 rate limit errors."""
|
|
35
|
+
for attempt in range(max_retries):
|
|
36
|
+
try:
|
|
37
|
+
return await coro_fn()
|
|
38
|
+
except RelayConnectionError as e:
|
|
39
|
+
if e.status_code == 429 and attempt < max_retries - 1:
|
|
40
|
+
delay = base_delay * (attempt + 1)
|
|
41
|
+
await asyncio.sleep(delay)
|
|
42
|
+
continue
|
|
43
|
+
raise
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
async def _make_connected_relay(name: str, config: RelayConfig) -> Relay:
|
|
47
|
+
"""Create a Relay and connect with 429 retry."""
|
|
48
|
+
relay = Relay(name, config)
|
|
49
|
+
await _retry_on_429(relay.agents)
|
|
50
|
+
return relay
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class TestSwarmsToolInjection:
|
|
54
|
+
"""Verify on_relay() injects tools (no API calls needed)."""
|
|
55
|
+
|
|
56
|
+
def test_on_relay_injects_four_tools(self):
|
|
57
|
+
from agent_relay.communicate.adapters.swarms import on_relay
|
|
58
|
+
|
|
59
|
+
mock_agent = _make_mock_agent("local-agent")
|
|
60
|
+
mock_relay = MagicMock()
|
|
61
|
+
mock_relay.on_message = MagicMock(return_value=lambda: None)
|
|
62
|
+
|
|
63
|
+
wrapped = on_relay(mock_agent, mock_relay)
|
|
64
|
+
assert wrapped is mock_agent
|
|
65
|
+
tool_names = [t.__name__ for t in mock_agent.tools]
|
|
66
|
+
assert set(tool_names) == {"relay_send", "relay_inbox", "relay_post", "relay_agents"}
|
|
67
|
+
|
|
68
|
+
def test_on_relay_registers_callback(self):
|
|
69
|
+
from agent_relay.communicate.adapters.swarms import on_relay
|
|
70
|
+
|
|
71
|
+
mock_agent = _make_mock_agent("cb-agent")
|
|
72
|
+
mock_relay = MagicMock()
|
|
73
|
+
mock_relay.on_message = MagicMock(return_value=lambda: None)
|
|
74
|
+
|
|
75
|
+
on_relay(mock_agent, mock_relay)
|
|
76
|
+
assert mock_relay.on_message.called
|
|
77
|
+
|
|
78
|
+
callback = mock_relay.on_message.call_args[0][0]
|
|
79
|
+
msg = Message(sender="test-lead", text="status check")
|
|
80
|
+
callback(msg)
|
|
81
|
+
mock_agent.receive_message.assert_called_once_with("test-lead", "status check")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class TestSwarmsLiveAPI:
|
|
85
|
+
"""Live API tests — consolidated to minimize requests."""
|
|
86
|
+
|
|
87
|
+
@pytest.mark.asyncio
|
|
88
|
+
async def test_registration_and_agents_tool(self):
|
|
89
|
+
"""Agent registers and relay_agents tool returns its name."""
|
|
90
|
+
from agent_relay.communicate.adapters.swarms import on_relay
|
|
91
|
+
|
|
92
|
+
agent_name = _unique_name("swarms-reg")
|
|
93
|
+
config = _live_config()
|
|
94
|
+
relay = Relay(agent_name, config)
|
|
95
|
+
mock_agent = _make_mock_agent(agent_name)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
on_relay(mock_agent, relay)
|
|
99
|
+
agents = await _retry_on_429(relay.agents)
|
|
100
|
+
assert agent_name in agents
|
|
101
|
+
|
|
102
|
+
tools = {t.__name__: t for t in mock_agent.tools}
|
|
103
|
+
result = await tools["relay_agents"]()
|
|
104
|
+
assert isinstance(result, str)
|
|
105
|
+
assert agent_name in result
|
|
106
|
+
finally:
|
|
107
|
+
await relay.close()
|
|
108
|
+
|
|
109
|
+
@pytest.mark.asyncio
|
|
110
|
+
async def test_post_tool_and_channel(self):
|
|
111
|
+
"""The relay_post tool posts to a channel without errors."""
|
|
112
|
+
from agent_relay.communicate.adapters.swarms import on_relay
|
|
113
|
+
|
|
114
|
+
agent_name = _unique_name("swarms-post")
|
|
115
|
+
config = _live_config()
|
|
116
|
+
relay = Relay(agent_name, config)
|
|
117
|
+
mock_agent = _make_mock_agent(agent_name)
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
on_relay(mock_agent, relay)
|
|
121
|
+
await _retry_on_429(relay.agents)
|
|
122
|
+
await _retry_on_429(lambda: relay.join("general"))
|
|
123
|
+
|
|
124
|
+
tools = {t.__name__: t for t in mock_agent.tools}
|
|
125
|
+
result = await tools["relay_post"]("general", f"swarms-e2e-{uuid.uuid4().hex[:8]}")
|
|
126
|
+
assert result == "Message posted"
|
|
127
|
+
finally:
|
|
128
|
+
await relay.close()
|
|
129
|
+
|
|
130
|
+
@pytest.mark.asyncio
|
|
131
|
+
async def test_send_and_inbox_dm(self):
|
|
132
|
+
"""relay_send delivers a DM that relay_inbox retrieves."""
|
|
133
|
+
from agent_relay.communicate.adapters.swarms import on_relay
|
|
134
|
+
|
|
135
|
+
config = _live_config()
|
|
136
|
+
sender_name = _unique_name("swarms-s")
|
|
137
|
+
receiver_name = _unique_name("swarms-r")
|
|
138
|
+
sender_relay = Relay(sender_name, config)
|
|
139
|
+
receiver_relay = Relay(receiver_name, config)
|
|
140
|
+
sender_agent = _make_mock_agent(sender_name)
|
|
141
|
+
receiver_agent = _make_mock_agent(receiver_name)
|
|
142
|
+
text = f"swarms-dm-{uuid.uuid4().hex[:8]}"
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
on_relay(sender_agent, sender_relay)
|
|
146
|
+
on_relay(receiver_agent, receiver_relay)
|
|
147
|
+
|
|
148
|
+
await _retry_on_429(sender_relay.agents)
|
|
149
|
+
await _retry_on_429(receiver_relay.agents)
|
|
150
|
+
|
|
151
|
+
sender_tools = {t.__name__: t for t in sender_agent.tools}
|
|
152
|
+
result = await sender_tools["relay_send"](receiver_name, text)
|
|
153
|
+
assert result == "Message sent"
|
|
154
|
+
|
|
155
|
+
deadline = asyncio.get_event_loop().time() + 20.0
|
|
156
|
+
found = False
|
|
157
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
158
|
+
try:
|
|
159
|
+
messages = await receiver_relay.inbox()
|
|
160
|
+
except RelayConnectionError as e:
|
|
161
|
+
if e.status_code == 429:
|
|
162
|
+
await asyncio.sleep(15)
|
|
163
|
+
continue
|
|
164
|
+
raise
|
|
165
|
+
for msg in messages:
|
|
166
|
+
if msg.sender == sender_name and msg.text == text:
|
|
167
|
+
found = True
|
|
168
|
+
break
|
|
169
|
+
if found:
|
|
170
|
+
break
|
|
171
|
+
await asyncio.sleep(2.0)
|
|
172
|
+
|
|
173
|
+
assert found, f"DM from {sender_name} not received within timeout"
|
|
174
|
+
finally:
|
|
175
|
+
await asyncio.gather(sender_relay.close(), receiver_relay.close())
|
|
176
|
+
|
|
177
|
+
@pytest.mark.asyncio
|
|
178
|
+
async def test_callback_with_live_relay(self):
|
|
179
|
+
"""on_message callback routes messages to agent.receive_message with a live relay."""
|
|
180
|
+
from agent_relay.communicate.adapters.swarms import on_relay
|
|
181
|
+
|
|
182
|
+
agent_name = _unique_name("swarms-cb")
|
|
183
|
+
config = _live_config()
|
|
184
|
+
relay = Relay(agent_name, config)
|
|
185
|
+
mock_agent = _make_mock_agent(agent_name)
|
|
186
|
+
|
|
187
|
+
try:
|
|
188
|
+
on_relay(mock_agent, relay)
|
|
189
|
+
await _retry_on_429(relay.agents)
|
|
190
|
+
|
|
191
|
+
assert len(relay._callbacks) >= 1
|
|
192
|
+
callback = relay._callbacks[0]
|
|
193
|
+
msg = Message(sender="test-lead", text="status check")
|
|
194
|
+
callback(msg)
|
|
195
|
+
|
|
196
|
+
mock_agent.receive_message.assert_called_once_with("test-lead", "status check")
|
|
197
|
+
finally:
|
|
198
|
+
await relay.close()
|
|
199
|
+
|
|
200
|
+
@pytest.mark.asyncio
|
|
201
|
+
async def test_cleanup_removes_agent(self):
|
|
202
|
+
"""After relay.close(), agent disappears from the agent list."""
|
|
203
|
+
config = _live_config()
|
|
204
|
+
agent_name = _unique_name("swarms-cl")
|
|
205
|
+
relay = Relay(agent_name, config)
|
|
206
|
+
probe = Relay(_unique_name("swarms-pr"), config)
|
|
207
|
+
|
|
208
|
+
try:
|
|
209
|
+
agents = await _retry_on_429(relay.agents)
|
|
210
|
+
assert agent_name in agents
|
|
211
|
+
|
|
212
|
+
await relay.close()
|
|
213
|
+
|
|
214
|
+
# If close() silently failed to unregister (e.g. 429), retry manually
|
|
215
|
+
if relay.transport.agent_id is not None:
|
|
216
|
+
await asyncio.sleep(15)
|
|
217
|
+
try:
|
|
218
|
+
await relay.transport.unregister_agent()
|
|
219
|
+
except Exception:
|
|
220
|
+
pass
|
|
221
|
+
|
|
222
|
+
deadline = asyncio.get_event_loop().time() + 35.0
|
|
223
|
+
absent = False
|
|
224
|
+
while asyncio.get_event_loop().time() < deadline:
|
|
225
|
+
try:
|
|
226
|
+
current = await probe.agents()
|
|
227
|
+
except RelayConnectionError as e:
|
|
228
|
+
if e.status_code == 429:
|
|
229
|
+
await asyncio.sleep(15)
|
|
230
|
+
continue
|
|
231
|
+
raise
|
|
232
|
+
if agent_name not in current:
|
|
233
|
+
absent = True
|
|
234
|
+
break
|
|
235
|
+
await asyncio.sleep(3.0)
|
|
236
|
+
|
|
237
|
+
assert absent, f"Agent {agent_name} still present after close"
|
|
238
|
+
finally:
|
|
239
|
+
await probe.close()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Tests for the Agno Python adapter."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import sys
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
# Mock agno before it's imported in the adapter
|
|
12
|
+
agno_mock = MagicMock()
|
|
13
|
+
sys.modules["agno"] = agno_mock
|
|
14
|
+
sys.modules["agno.agent"] = agno_mock.agent
|
|
15
|
+
|
|
16
|
+
def _adapter_module():
|
|
17
|
+
import importlib
|
|
18
|
+
if "agent_relay.communicate.adapters.agno" in sys.modules:
|
|
19
|
+
importlib.reload(sys.modules["agent_relay.communicate.adapters.agno"])
|
|
20
|
+
return importlib.import_module("agent_relay.communicate.adapters.agno")
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def mock_relay():
|
|
24
|
+
relay = MagicMock()
|
|
25
|
+
relay.agent_name = "TestAgent"
|
|
26
|
+
relay.inbox = AsyncMock(return_value=[])
|
|
27
|
+
relay.peek = AsyncMock(return_value=[])
|
|
28
|
+
relay.send = AsyncMock()
|
|
29
|
+
relay.post = AsyncMock()
|
|
30
|
+
relay.agents = AsyncMock(return_value=[])
|
|
31
|
+
return relay
|
|
32
|
+
|
|
33
|
+
@pytest.fixture
|
|
34
|
+
def mock_agent():
|
|
35
|
+
agent = MagicMock()
|
|
36
|
+
agent.tools = []
|
|
37
|
+
agent.instructions = "Agno base instructions."
|
|
38
|
+
return agent
|
|
39
|
+
|
|
40
|
+
def test_on_relay_adds_tools(mock_relay, mock_agent):
|
|
41
|
+
adapter = _adapter_module()
|
|
42
|
+
|
|
43
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
44
|
+
|
|
45
|
+
tools = {t.__name__: t for t in mock_agent.tools}
|
|
46
|
+
assert "relay_send" in tools
|
|
47
|
+
assert "relay_inbox" in tools
|
|
48
|
+
assert "relay_post" in tools
|
|
49
|
+
assert "relay_agents" in tools
|
|
50
|
+
|
|
51
|
+
@pytest.mark.asyncio
|
|
52
|
+
async def test_tool_execution(mock_relay, mock_agent):
|
|
53
|
+
adapter = _adapter_module()
|
|
54
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
55
|
+
tools = {t.__name__: t for t in mock_agent.tools}
|
|
56
|
+
|
|
57
|
+
# Test relay_send
|
|
58
|
+
await tools["relay_send"]("Alice", "Hi")
|
|
59
|
+
mock_relay.send.assert_called_with("Alice", "Hi")
|
|
60
|
+
|
|
61
|
+
# Test relay_inbox
|
|
62
|
+
from agent_relay.communicate.types import Message
|
|
63
|
+
mock_relay.inbox.return_value = [Message(sender="Bob", text="Hey")]
|
|
64
|
+
inbox_res = await tools["relay_inbox"]()
|
|
65
|
+
assert "From Bob: Hey" in inbox_res
|
|
66
|
+
|
|
67
|
+
# Test relay_post
|
|
68
|
+
await tools["relay_post"]("general", "Update")
|
|
69
|
+
mock_relay.post.assert_called_with("general", "Update")
|
|
70
|
+
|
|
71
|
+
# Test relay_agents
|
|
72
|
+
mock_relay.agents.return_value = ["Alice", "Bob"]
|
|
73
|
+
agents_res = await tools["relay_agents"]()
|
|
74
|
+
assert "Alice, Bob" in agents_res
|
|
75
|
+
|
|
76
|
+
@pytest.mark.asyncio
|
|
77
|
+
async def test_instructions_wrapping(mock_relay, mock_agent):
|
|
78
|
+
adapter = _adapter_module()
|
|
79
|
+
from agent_relay.communicate.types import Message
|
|
80
|
+
|
|
81
|
+
mock_relay.peek.return_value = [
|
|
82
|
+
Message(sender="Other", text="Agno message", message_id="1")
|
|
83
|
+
]
|
|
84
|
+
|
|
85
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
86
|
+
|
|
87
|
+
assert callable(mock_agent.instructions)
|
|
88
|
+
|
|
89
|
+
result = await mock_agent.instructions()
|
|
90
|
+
assert "Agno base instructions." in result
|
|
91
|
+
assert "Other: Agno message" in result
|
|
92
|
+
|
|
93
|
+
@pytest.mark.asyncio
|
|
94
|
+
async def test_instructions_wrapping_callable(mock_relay, mock_agent):
|
|
95
|
+
"""Chaining: existing callable instructions are preserved."""
|
|
96
|
+
adapter = _adapter_module()
|
|
97
|
+
|
|
98
|
+
original_instructions = MagicMock(return_value="Dynamic context.")
|
|
99
|
+
mock_agent.instructions = original_instructions
|
|
100
|
+
|
|
101
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
102
|
+
|
|
103
|
+
result = await mock_agent.instructions()
|
|
104
|
+
assert "Dynamic context." in result
|
|
105
|
+
assert original_instructions.called
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@pytest.mark.asyncio
|
|
109
|
+
async def test_instructions_wrapping_async_callable(mock_relay, mock_agent):
|
|
110
|
+
adapter = _adapter_module()
|
|
111
|
+
from agent_relay.communicate.types import Message
|
|
112
|
+
|
|
113
|
+
async def original_instructions():
|
|
114
|
+
return "Async context."
|
|
115
|
+
|
|
116
|
+
mock_agent.instructions = original_instructions
|
|
117
|
+
mock_relay.peek.return_value = [Message(sender="Other", text="Agno message", message_id="1")]
|
|
118
|
+
|
|
119
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
120
|
+
|
|
121
|
+
result = await mock_agent.instructions()
|
|
122
|
+
assert result.startswith("\n\nNew messages from other agents:\n Other: Agno message\n")
|
|
123
|
+
assert result.endswith("\n\nAsync context.")
|
|
124
|
+
|
|
125
|
+
@pytest.mark.asyncio
|
|
126
|
+
async def test_instructions_no_messages(mock_relay, mock_agent):
|
|
127
|
+
"""When inbox is empty, only base instructions returned."""
|
|
128
|
+
adapter = _adapter_module()
|
|
129
|
+
mock_relay.peek = AsyncMock(return_value=[])
|
|
130
|
+
|
|
131
|
+
adapter.on_relay(mock_agent, mock_relay)
|
|
132
|
+
|
|
133
|
+
result = await mock_agent.instructions()
|
|
134
|
+
assert result == "Agno base instructions."
|
|
135
|
+
|
|
136
|
+
def test_on_relay_returns_agent(mock_relay, mock_agent):
|
|
137
|
+
"""on_relay() returns the modified agent."""
|
|
138
|
+
adapter = _adapter_module()
|
|
139
|
+
result = adapter.on_relay(mock_agent, mock_relay)
|
|
140
|
+
assert result is mock_agent
|