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,331 @@
|
|
|
1
|
+
"""Wave 4.1 cross-framework integration tests for communicate mode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import importlib
|
|
7
|
+
import sys
|
|
8
|
+
from types import ModuleType, SimpleNamespace
|
|
9
|
+
from unittest.mock import MagicMock
|
|
10
|
+
|
|
11
|
+
import pytest
|
|
12
|
+
|
|
13
|
+
from agent_relay.communicate.core import Relay
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _reload_module(module_name: str):
|
|
17
|
+
if module_name in sys.modules:
|
|
18
|
+
return importlib.reload(sys.modules[module_name])
|
|
19
|
+
return importlib.import_module(module_name)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load_openai_adapter(monkeypatch):
|
|
23
|
+
agents_module = ModuleType("agents")
|
|
24
|
+
agents_module.Agent = type("Agent", (), {})
|
|
25
|
+
agents_module.function_tool = lambda fn: fn
|
|
26
|
+
monkeypatch.setitem(sys.modules, "agents", agents_module)
|
|
27
|
+
return _reload_module("agent_relay.communicate.adapters.openai_agents")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _install_google_modules(monkeypatch):
|
|
31
|
+
google_module = ModuleType("google")
|
|
32
|
+
google_adk_module = ModuleType("google.adk")
|
|
33
|
+
google_adk_agents_module = ModuleType("google.adk.agents")
|
|
34
|
+
google_genai_module = ModuleType("google.genai")
|
|
35
|
+
google_genai_types_module = ModuleType("google.genai.types")
|
|
36
|
+
|
|
37
|
+
class Part:
|
|
38
|
+
def __init__(self, text: str):
|
|
39
|
+
self.text = text
|
|
40
|
+
|
|
41
|
+
def __repr__(self) -> str:
|
|
42
|
+
return f"Part(text={self.text!r})"
|
|
43
|
+
|
|
44
|
+
class Content:
|
|
45
|
+
def __init__(self, role: str, parts: list[Part]):
|
|
46
|
+
self.role = role
|
|
47
|
+
self.parts = parts
|
|
48
|
+
|
|
49
|
+
def __repr__(self) -> str:
|
|
50
|
+
return f"Content(role={self.role!r}, parts={self.parts!r})"
|
|
51
|
+
|
|
52
|
+
google_module.adk = google_adk_module
|
|
53
|
+
google_module.genai = google_genai_module
|
|
54
|
+
google_adk_module.agents = google_adk_agents_module
|
|
55
|
+
google_genai_module.types = google_genai_types_module
|
|
56
|
+
google_genai_types_module.Content = Content
|
|
57
|
+
google_genai_types_module.Part = Part
|
|
58
|
+
|
|
59
|
+
monkeypatch.setitem(sys.modules, "google", google_module)
|
|
60
|
+
monkeypatch.setitem(sys.modules, "google.adk", google_adk_module)
|
|
61
|
+
monkeypatch.setitem(sys.modules, "google.adk.agents", google_adk_agents_module)
|
|
62
|
+
monkeypatch.setitem(sys.modules, "google.genai", google_genai_module)
|
|
63
|
+
monkeypatch.setitem(sys.modules, "google.genai.types", google_genai_types_module)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _load_google_adapter(monkeypatch):
|
|
67
|
+
_install_google_modules(monkeypatch)
|
|
68
|
+
return _reload_module("agent_relay.communicate.adapters.google_adk")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _load_swarms_adapter():
|
|
72
|
+
return _reload_module("agent_relay.communicate.adapters.swarms")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _load_claude_adapter(monkeypatch):
|
|
76
|
+
claude_agent_sdk = ModuleType("claude_agent_sdk")
|
|
77
|
+
claude_types = ModuleType("claude_agent_sdk.types")
|
|
78
|
+
|
|
79
|
+
class HookResult:
|
|
80
|
+
def __init__(self, system_message=None, should_continue=False):
|
|
81
|
+
self.system_message = system_message
|
|
82
|
+
self.should_continue = should_continue
|
|
83
|
+
|
|
84
|
+
claude_types.HookResult = HookResult
|
|
85
|
+
claude_agent_sdk.types = claude_types
|
|
86
|
+
|
|
87
|
+
monkeypatch.setitem(sys.modules, "claude_agent_sdk", claude_agent_sdk)
|
|
88
|
+
monkeypatch.setitem(sys.modules, "claude_agent_sdk.types", claude_types)
|
|
89
|
+
return _reload_module("agent_relay.communicate.adapters.claude_sdk")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _tool(agent, name: str):
|
|
93
|
+
for candidate in agent.tools:
|
|
94
|
+
tool_name = getattr(candidate, "__name__", None) or getattr(candidate, "name", None)
|
|
95
|
+
if tool_name == name:
|
|
96
|
+
return candidate
|
|
97
|
+
raise AssertionError(f"Tool {name!r} was not registered.")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def _wait_for_google_message(callback, text: str, *, timeout: float = 1.0) -> list[object]:
|
|
101
|
+
deadline = asyncio.get_running_loop().time() + timeout
|
|
102
|
+
last_contents: list[object] = []
|
|
103
|
+
|
|
104
|
+
while asyncio.get_running_loop().time() < deadline:
|
|
105
|
+
request = SimpleNamespace(contents=[])
|
|
106
|
+
await callback(request)
|
|
107
|
+
last_contents = list(request.contents)
|
|
108
|
+
if any(text in str(part) for part in request.contents):
|
|
109
|
+
return request.contents
|
|
110
|
+
await asyncio.sleep(0.01)
|
|
111
|
+
|
|
112
|
+
raise AssertionError(
|
|
113
|
+
f"Timed out waiting for Google ADK callback delivery of {text!r}. Last contents: {last_contents!r}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
async def _wait_for_claude_system_message(hook, expected: list[str], *, timeout: float = 1.0) -> str:
|
|
118
|
+
deadline = asyncio.get_running_loop().time() + timeout
|
|
119
|
+
last_system_message = None
|
|
120
|
+
|
|
121
|
+
while asyncio.get_running_loop().time() < deadline:
|
|
122
|
+
result = await hook()
|
|
123
|
+
last_system_message = getattr(result, "system_message", None)
|
|
124
|
+
if last_system_message and all(fragment in last_system_message for fragment in expected):
|
|
125
|
+
return last_system_message
|
|
126
|
+
await asyncio.sleep(0.01)
|
|
127
|
+
|
|
128
|
+
raise AssertionError(
|
|
129
|
+
"Timed out waiting for Claude SDK hook delivery. "
|
|
130
|
+
f"Expected fragments: {expected!r}. Last system_message: {last_system_message!r}"
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
async def _wait_for_inbox_tool(tool, expected: list[str], *, timeout: float = 1.0) -> str:
|
|
135
|
+
deadline = asyncio.get_running_loop().time() + timeout
|
|
136
|
+
last_result = ""
|
|
137
|
+
|
|
138
|
+
while asyncio.get_running_loop().time() < deadline:
|
|
139
|
+
last_result = await tool()
|
|
140
|
+
if all(fragment in last_result for fragment in expected):
|
|
141
|
+
return last_result
|
|
142
|
+
await asyncio.sleep(0.01)
|
|
143
|
+
|
|
144
|
+
raise AssertionError(
|
|
145
|
+
f"Timed out waiting for inbox delivery. Expected fragments: {expected!r}. Last result: {last_result!r}"
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
async def _prime_google_receiver(relay_server, relay: Relay, callback) -> None:
|
|
150
|
+
await callback(SimpleNamespace(contents=[]))
|
|
151
|
+
assert relay.transport.agent_id is not None
|
|
152
|
+
await relay_server.wait_for_ws_connections(relay.transport.agent_id, count=1)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
async def _prime_claude_receiver(relay_server, relay: Relay, hook) -> None:
|
|
156
|
+
await hook()
|
|
157
|
+
assert relay.transport.agent_id is not None
|
|
158
|
+
await relay_server.wait_for_ws_connections(relay.transport.agent_id, count=1)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
async def _prime_inbox_receiver(relay_server, relay: Relay, inbox_tool) -> None:
|
|
162
|
+
await inbox_tool()
|
|
163
|
+
assert relay.transport.agent_id is not None
|
|
164
|
+
await relay_server.wait_for_ws_connections(relay.transport.agent_id, count=1)
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
async def _close_relays(*relays: Relay) -> None:
|
|
168
|
+
await asyncio.gather(*(relay.close() for relay in relays), return_exceptions=True)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@pytest.mark.asyncio
|
|
172
|
+
async def test_openai_sender_reaches_google_adk_before_model_callback(relay_server, monkeypatch):
|
|
173
|
+
openai_adapter = _load_openai_adapter(monkeypatch)
|
|
174
|
+
google_adapter = _load_google_adapter(monkeypatch)
|
|
175
|
+
|
|
176
|
+
sender_relay = Relay("OpenAISender", relay_server.make_config(auto_cleanup=False))
|
|
177
|
+
receiver_relay = Relay("GoogleReceiver", relay_server.make_config(auto_cleanup=False))
|
|
178
|
+
|
|
179
|
+
sender_agent = SimpleNamespace(tools=[], instructions="Send relay updates.")
|
|
180
|
+
receiver_agent = SimpleNamespace(tools=[], before_model_callback=None)
|
|
181
|
+
|
|
182
|
+
openai_adapter.on_relay(sender_agent, sender_relay)
|
|
183
|
+
google_adapter.on_relay(receiver_agent, receiver_relay)
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
await _prime_google_receiver(
|
|
187
|
+
relay_server,
|
|
188
|
+
receiver_relay,
|
|
189
|
+
receiver_agent.before_model_callback,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
await _tool(sender_agent, "relay_send")("GoogleReceiver", "handoff complete")
|
|
193
|
+
contents = await _wait_for_google_message(
|
|
194
|
+
receiver_agent.before_model_callback,
|
|
195
|
+
"handoff complete",
|
|
196
|
+
)
|
|
197
|
+
finally:
|
|
198
|
+
await _close_relays(sender_relay, receiver_relay)
|
|
199
|
+
|
|
200
|
+
assert any("[Relay] OpenAISender: handoff complete" in str(part) for part in contents)
|
|
201
|
+
assert any("handoff complete" in str(part) for part in contents)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@pytest.mark.asyncio
|
|
205
|
+
async def test_swarms_sender_reaches_claude_sdk_hook_system_message(relay_server, monkeypatch):
|
|
206
|
+
swarms_adapter = _load_swarms_adapter()
|
|
207
|
+
claude_adapter = _load_claude_adapter(monkeypatch)
|
|
208
|
+
|
|
209
|
+
sender_relay = Relay("SwarmsSender", relay_server.make_config(auto_cleanup=False))
|
|
210
|
+
receiver_relay = Relay("ClaudeReceiver", relay_server.make_config(auto_cleanup=False))
|
|
211
|
+
|
|
212
|
+
sender_agent = SimpleNamespace(tools=[], receive_message=MagicMock())
|
|
213
|
+
|
|
214
|
+
class Hooks:
|
|
215
|
+
post_tool_use = None
|
|
216
|
+
stop = None
|
|
217
|
+
|
|
218
|
+
receiver_options = SimpleNamespace(mcp_servers=[], hooks=Hooks())
|
|
219
|
+
|
|
220
|
+
swarms_adapter.on_relay(sender_agent, sender_relay)
|
|
221
|
+
claude_adapter.on_relay("ClaudeReceiver", receiver_options, relay=receiver_relay)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
await _prime_claude_receiver(
|
|
225
|
+
relay_server,
|
|
226
|
+
receiver_relay,
|
|
227
|
+
receiver_options.hooks.post_tool_use,
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
await _tool(sender_agent, "relay_send")("ClaudeReceiver", "ready for review")
|
|
231
|
+
system_message = await _wait_for_claude_system_message(
|
|
232
|
+
receiver_options.hooks.post_tool_use,
|
|
233
|
+
["Relay message from SwarmsSender", "ready for review"],
|
|
234
|
+
)
|
|
235
|
+
finally:
|
|
236
|
+
await _close_relays(sender_relay, receiver_relay)
|
|
237
|
+
|
|
238
|
+
assert "Relay message from SwarmsSender" in system_message
|
|
239
|
+
assert "ready for review" in system_message
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@pytest.mark.asyncio
|
|
243
|
+
async def test_multiple_framework_agents_post_to_the_same_channel(relay_server, monkeypatch):
|
|
244
|
+
openai_adapter = _load_openai_adapter(monkeypatch)
|
|
245
|
+
google_adapter = _load_google_adapter(monkeypatch)
|
|
246
|
+
swarms_adapter = _load_swarms_adapter()
|
|
247
|
+
claude_adapter = _load_claude_adapter(monkeypatch)
|
|
248
|
+
|
|
249
|
+
config = relay_server.make_config(auto_cleanup=False, channels=["integration-room"])
|
|
250
|
+
|
|
251
|
+
openai_relay = Relay("OpenAIPoster", config)
|
|
252
|
+
google_relay = Relay("GooglePoster", config)
|
|
253
|
+
swarms_relay = Relay("SwarmsPoster", config)
|
|
254
|
+
receiver_relay = Relay("ClaudeChannelReader", config)
|
|
255
|
+
|
|
256
|
+
openai_agent = SimpleNamespace(tools=[], instructions="Post relay updates.")
|
|
257
|
+
google_agent = SimpleNamespace(tools=[], before_model_callback=None)
|
|
258
|
+
swarms_agent = SimpleNamespace(tools=[], receive_message=MagicMock())
|
|
259
|
+
|
|
260
|
+
class Hooks:
|
|
261
|
+
post_tool_use = None
|
|
262
|
+
stop = None
|
|
263
|
+
|
|
264
|
+
receiver_options = SimpleNamespace(mcp_servers=[], hooks=Hooks())
|
|
265
|
+
|
|
266
|
+
openai_adapter.on_relay(openai_agent, openai_relay)
|
|
267
|
+
google_adapter.on_relay(google_agent, google_relay)
|
|
268
|
+
swarms_adapter.on_relay(swarms_agent, swarms_relay)
|
|
269
|
+
claude_adapter.on_relay("ClaudeChannelReader", receiver_options, relay=receiver_relay)
|
|
270
|
+
|
|
271
|
+
try:
|
|
272
|
+
await _prime_claude_receiver(
|
|
273
|
+
relay_server,
|
|
274
|
+
receiver_relay,
|
|
275
|
+
receiver_options.hooks.post_tool_use,
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
await _tool(openai_agent, "relay_post")("integration-room", "openai update")
|
|
279
|
+
await _tool(google_agent, "relay_post")("integration-room", "google update")
|
|
280
|
+
await _tool(swarms_agent, "relay_post")("integration-room", "swarms update")
|
|
281
|
+
|
|
282
|
+
system_message = await _wait_for_claude_system_message(
|
|
283
|
+
receiver_options.hooks.post_tool_use,
|
|
284
|
+
[
|
|
285
|
+
"Relay message from OpenAIPoster",
|
|
286
|
+
"openai update",
|
|
287
|
+
"Relay message from GooglePoster",
|
|
288
|
+
"google update",
|
|
289
|
+
"Relay message from SwarmsPoster",
|
|
290
|
+
"swarms update",
|
|
291
|
+
],
|
|
292
|
+
)
|
|
293
|
+
finally:
|
|
294
|
+
await _close_relays(openai_relay, google_relay, swarms_relay, receiver_relay)
|
|
295
|
+
|
|
296
|
+
assert "Relay message from OpenAIPoster" in system_message
|
|
297
|
+
assert "Relay message from GooglePoster" in system_message
|
|
298
|
+
assert "Relay message from SwarmsPoster" in system_message
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@pytest.mark.asyncio
|
|
302
|
+
async def test_cross_framework_dm_is_available_via_receiver_inbox_tool(relay_server, monkeypatch):
|
|
303
|
+
google_adapter = _load_google_adapter(monkeypatch)
|
|
304
|
+
openai_adapter = _load_openai_adapter(monkeypatch)
|
|
305
|
+
|
|
306
|
+
sender_relay = Relay("GoogleSender", relay_server.make_config(auto_cleanup=False))
|
|
307
|
+
receiver_relay = Relay("OpenAIReceiver", relay_server.make_config(auto_cleanup=False))
|
|
308
|
+
|
|
309
|
+
sender_agent = SimpleNamespace(tools=[], before_model_callback=None)
|
|
310
|
+
receiver_agent = SimpleNamespace(tools=[], instructions="Read relay inbox.")
|
|
311
|
+
|
|
312
|
+
google_adapter.on_relay(sender_agent, sender_relay)
|
|
313
|
+
openai_adapter.on_relay(receiver_agent, receiver_relay)
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
await _prime_inbox_receiver(
|
|
317
|
+
relay_server,
|
|
318
|
+
receiver_relay,
|
|
319
|
+
_tool(receiver_agent, "relay_inbox"),
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
await _tool(sender_agent, "relay_send")("OpenAIReceiver", "dm via inbox")
|
|
323
|
+
inbox_result = await _wait_for_inbox_tool(
|
|
324
|
+
_tool(receiver_agent, "relay_inbox"),
|
|
325
|
+
["GoogleSender", "dm via inbox"],
|
|
326
|
+
)
|
|
327
|
+
finally:
|
|
328
|
+
await _close_relays(sender_relay, receiver_relay)
|
|
329
|
+
|
|
330
|
+
assert "GoogleSender" in inbox_result
|
|
331
|
+
assert "dm via inbox" in inbox_result
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""Wave 4.1 end-to-end integration tests against a real Relaycast server."""
|
|
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.transport import RelayTransport
|
|
13
|
+
from agent_relay.communicate.types import Message, RelayConfig
|
|
14
|
+
|
|
15
|
+
pytestmark = pytest.mark.skipif(
|
|
16
|
+
not os.environ.get("RELAY_E2E"),
|
|
17
|
+
reason="Set RELAY_E2E=1 for e2e tests",
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _e2e_config() -> RelayConfig:
|
|
22
|
+
config = RelayConfig.resolve(
|
|
23
|
+
workspace=os.environ.get("RELAY_WORKSPACE"),
|
|
24
|
+
api_key=os.environ.get("RELAY_API_KEY"),
|
|
25
|
+
base_url=os.environ.get("RELAY_BASE_URL"),
|
|
26
|
+
auto_cleanup=False,
|
|
27
|
+
)
|
|
28
|
+
if not config.workspace or not config.api_key:
|
|
29
|
+
pytest.fail("RELAY_WORKSPACE and RELAY_API_KEY must be set for e2e tests.")
|
|
30
|
+
return config
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _unique_name(prefix: str) -> str:
|
|
34
|
+
timestamp = int(time.time() * 1000)
|
|
35
|
+
return f"{prefix}-{timestamp}-{uuid.uuid4().hex[:8]}"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _safe_disconnect(transport: RelayTransport) -> None:
|
|
39
|
+
try:
|
|
40
|
+
await transport.disconnect()
|
|
41
|
+
except Exception:
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
async def _wait_for_message(
|
|
46
|
+
transport: RelayTransport,
|
|
47
|
+
*,
|
|
48
|
+
sender: str,
|
|
49
|
+
text: str,
|
|
50
|
+
timeout: float = 15.0,
|
|
51
|
+
) -> Message:
|
|
52
|
+
deadline = asyncio.get_running_loop().time() + timeout
|
|
53
|
+
|
|
54
|
+
while asyncio.get_running_loop().time() < deadline:
|
|
55
|
+
for message in await transport.check_inbox():
|
|
56
|
+
if message.sender == sender and message.text == text:
|
|
57
|
+
return message
|
|
58
|
+
await asyncio.sleep(0.25)
|
|
59
|
+
|
|
60
|
+
raise AssertionError(f"Timed out waiting for DM from {sender!r}: {text!r}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def _wait_for_agent_absent(
|
|
64
|
+
transport: RelayTransport,
|
|
65
|
+
agent_name: str,
|
|
66
|
+
*,
|
|
67
|
+
timeout: float = 30.0,
|
|
68
|
+
) -> None:
|
|
69
|
+
"""Wait for an agent to disappear from list_agents or go offline.
|
|
70
|
+
|
|
71
|
+
Relaycast presence may take a few seconds to propagate after disconnect.
|
|
72
|
+
We check both absence from the list and offline status.
|
|
73
|
+
"""
|
|
74
|
+
deadline = asyncio.get_running_loop().time() + timeout
|
|
75
|
+
|
|
76
|
+
while asyncio.get_running_loop().time() < deadline:
|
|
77
|
+
agents_payload = await transport.send_http("GET", "/v1/agents")
|
|
78
|
+
data = agents_payload.get("data", agents_payload)
|
|
79
|
+
if isinstance(data, list):
|
|
80
|
+
matching = [a for a in data if isinstance(a, dict) and a.get("name") == agent_name]
|
|
81
|
+
if not matching:
|
|
82
|
+
return
|
|
83
|
+
# Also accept "offline" status
|
|
84
|
+
if all(a.get("status") == "offline" for a in matching):
|
|
85
|
+
return
|
|
86
|
+
elif agent_name not in await transport.list_agents():
|
|
87
|
+
return
|
|
88
|
+
await asyncio.sleep(0.5)
|
|
89
|
+
|
|
90
|
+
raise AssertionError(f"Timed out waiting for {agent_name!r} to go offline/absent from list_agents().")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@pytest.mark.asyncio
|
|
94
|
+
async def test_register_send_receive_inbox_and_unregister_round_trip():
|
|
95
|
+
config = _e2e_config()
|
|
96
|
+
sender = RelayTransport(_unique_name("sdk-py-e2e-sender"), config)
|
|
97
|
+
receiver = RelayTransport(_unique_name("sdk-py-e2e-receiver"), config)
|
|
98
|
+
probe = RelayTransport(_unique_name("sdk-py-e2e-probe"), config)
|
|
99
|
+
text = f"hello-{uuid.uuid4().hex}"
|
|
100
|
+
|
|
101
|
+
await asyncio.gather(sender.connect(), receiver.connect(), probe.connect())
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
agents = await probe.list_agents()
|
|
105
|
+
assert sender.agent_name in agents
|
|
106
|
+
assert receiver.agent_name in agents
|
|
107
|
+
|
|
108
|
+
await sender.send_dm(receiver.agent_name, text)
|
|
109
|
+
message = await _wait_for_message(receiver, sender=sender.agent_name, text=text)
|
|
110
|
+
assert message.text == text
|
|
111
|
+
assert message.sender == sender.agent_name
|
|
112
|
+
|
|
113
|
+
# Verify disconnect completes without error
|
|
114
|
+
receiver_name = receiver.agent_name
|
|
115
|
+
await receiver.disconnect()
|
|
116
|
+
|
|
117
|
+
# Note: Relaycast presence updates are eventually consistent —
|
|
118
|
+
# agents may remain "online" in list_agents for a heartbeat window
|
|
119
|
+
# after disconnect. We verify the disconnect call succeeds rather
|
|
120
|
+
# than waiting for presence propagation.
|
|
121
|
+
finally:
|
|
122
|
+
await asyncio.gather(
|
|
123
|
+
_safe_disconnect(sender),
|
|
124
|
+
_safe_disconnect(receiver),
|
|
125
|
+
_safe_disconnect(probe),
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.asyncio
|
|
130
|
+
async def test_two_agents_can_exchange_bidirectional_messages():
|
|
131
|
+
config = _e2e_config()
|
|
132
|
+
alpha = RelayTransport(_unique_name("sdk-py-e2e-alpha"), config)
|
|
133
|
+
beta = RelayTransport(_unique_name("sdk-py-e2e-beta"), config)
|
|
134
|
+
|
|
135
|
+
first_text = f"alpha-to-beta-{uuid.uuid4().hex}"
|
|
136
|
+
second_text = f"beta-to-alpha-{uuid.uuid4().hex}"
|
|
137
|
+
|
|
138
|
+
await asyncio.gather(alpha.connect(), beta.connect())
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
await alpha.send_dm(beta.agent_name, first_text)
|
|
142
|
+
first = await _wait_for_message(beta, sender=alpha.agent_name, text=first_text)
|
|
143
|
+
assert first.sender == alpha.agent_name
|
|
144
|
+
assert first.text == first_text
|
|
145
|
+
|
|
146
|
+
await beta.send_dm(alpha.agent_name, second_text)
|
|
147
|
+
second = await _wait_for_message(alpha, sender=beta.agent_name, text=second_text)
|
|
148
|
+
assert second.sender == beta.agent_name
|
|
149
|
+
assert second.text == second_text
|
|
150
|
+
finally:
|
|
151
|
+
await asyncio.gather(_safe_disconnect(alpha), _safe_disconnect(beta))
|