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,86 @@
|
|
|
1
|
+
"""OpenAI Agents adapter for on_relay()."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
from typing import TYPE_CHECKING, Any
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from ..core import Relay
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _format_instructions_with_inbox(messages: list[Any], base_instructions: str) -> str:
|
|
13
|
+
content = "\n\nNew messages from other agents:\n"
|
|
14
|
+
for message in messages:
|
|
15
|
+
content += f" {message.sender}: {message.text}\n"
|
|
16
|
+
return f"{content}\n{base_instructions}" if base_instructions else content
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def on_relay(agent: Any, relay: "Relay | None" = None) -> Any:
|
|
20
|
+
"""Wrap OpenAI Agent to connect it to the relay."""
|
|
21
|
+
if relay is None:
|
|
22
|
+
from ..core import Relay
|
|
23
|
+
relay = Relay(getattr(agent, "name", "Agent"))
|
|
24
|
+
try:
|
|
25
|
+
from agents import function_tool
|
|
26
|
+
except ImportError:
|
|
27
|
+
raise ImportError(
|
|
28
|
+
"on_relay() for OpenAI Agents requires the 'openai-agents' package. "
|
|
29
|
+
"Install it with: pip install openai-agents"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
async def relay_send(to: str, text: str) -> str:
|
|
33
|
+
"""Send a private message to another agent."""
|
|
34
|
+
await relay.send(to, text)
|
|
35
|
+
return "Message sent"
|
|
36
|
+
|
|
37
|
+
async def relay_inbox() -> str:
|
|
38
|
+
"""Check for new messages in the inbox."""
|
|
39
|
+
messages = await relay.inbox()
|
|
40
|
+
if not messages: return "No new messages"
|
|
41
|
+
return "\n".join([f"From {m.sender}: {m.text}" for m in messages])
|
|
42
|
+
|
|
43
|
+
async def relay_post(channel: str, text: str) -> str:
|
|
44
|
+
"""Post a message to a shared channel."""
|
|
45
|
+
await relay.post(channel, text)
|
|
46
|
+
return "Message posted"
|
|
47
|
+
|
|
48
|
+
async def relay_agents() -> str:
|
|
49
|
+
"""List all agents currently on the relay."""
|
|
50
|
+
agents = await relay.agents()
|
|
51
|
+
return ", ".join(agents)
|
|
52
|
+
|
|
53
|
+
agent.tools.extend([
|
|
54
|
+
function_tool(relay_send),
|
|
55
|
+
function_tool(relay_inbox),
|
|
56
|
+
function_tool(relay_post),
|
|
57
|
+
function_tool(relay_agents)
|
|
58
|
+
])
|
|
59
|
+
|
|
60
|
+
# 2. Wrap instructions with a local buffer so we don't starve relay_inbox tool
|
|
61
|
+
orig_instructions = agent.instructions
|
|
62
|
+
pending_messages: list[Any] = []
|
|
63
|
+
|
|
64
|
+
relay.on_message(lambda msg: pending_messages.append(msg))
|
|
65
|
+
|
|
66
|
+
async def instructions_wrapper(*args: Any, **kwargs: Any) -> str:
|
|
67
|
+
if callable(orig_instructions):
|
|
68
|
+
if inspect.iscoroutinefunction(orig_instructions):
|
|
69
|
+
base = await orig_instructions(*args, **kwargs)
|
|
70
|
+
else:
|
|
71
|
+
base = orig_instructions(*args, **kwargs)
|
|
72
|
+
if inspect.isawaitable(base):
|
|
73
|
+
base = await base
|
|
74
|
+
else:
|
|
75
|
+
base = orig_instructions
|
|
76
|
+
|
|
77
|
+
base = base or ""
|
|
78
|
+
if not pending_messages:
|
|
79
|
+
return base
|
|
80
|
+
|
|
81
|
+
messages = list(pending_messages)
|
|
82
|
+
pending_messages.clear()
|
|
83
|
+
return _format_instructions_with_inbox(messages, base)
|
|
84
|
+
|
|
85
|
+
agent.instructions = instructions_wrapper
|
|
86
|
+
return agent
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""Pi RPC adapter for on_relay().
|
|
2
|
+
|
|
3
|
+
Spawns Pi (a TypeScript coding agent) as a subprocess in RPC mode and bridges
|
|
4
|
+
relay communication over its stdin/stdout JSONL protocol.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import subprocess
|
|
11
|
+
import threading
|
|
12
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from ..core import Relay
|
|
16
|
+
|
|
17
|
+
RELAY_TOOL_PREAMBLE = (
|
|
18
|
+
"You have access to the following relay tools for multi-agent communication:\n"
|
|
19
|
+
"- relay_send(to, text): Send a direct message to another relay agent.\n"
|
|
20
|
+
"- relay_inbox(): Drain and inspect newly received relay messages.\n"
|
|
21
|
+
"- relay_post(channel, text): Post a message to a relay channel.\n"
|
|
22
|
+
"- relay_agents(): List currently online relay agents.\n"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class PiRpcSession:
|
|
27
|
+
"""Manages a Pi subprocess in RPC mode with relay integration."""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
proc: subprocess.Popen[str],
|
|
32
|
+
relay: "Relay",
|
|
33
|
+
) -> None:
|
|
34
|
+
self._proc = proc
|
|
35
|
+
self._relay = relay
|
|
36
|
+
self._is_streaming = False
|
|
37
|
+
self._unsubscribe: Callable[[], None] | None = None
|
|
38
|
+
self._reader_thread: threading.Thread | None = None
|
|
39
|
+
self._event_callbacks: list[Callable[[dict[str, Any]], None]] = []
|
|
40
|
+
self._closed = False
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def is_streaming(self) -> bool:
|
|
44
|
+
return self._is_streaming
|
|
45
|
+
|
|
46
|
+
def send_command(self, command: dict[str, Any]) -> None:
|
|
47
|
+
"""Send a JSONL command to Pi's stdin."""
|
|
48
|
+
if self._proc.stdin is None:
|
|
49
|
+
return
|
|
50
|
+
line = json.dumps(command) + "\n"
|
|
51
|
+
self._proc.stdin.write(line)
|
|
52
|
+
self._proc.stdin.flush()
|
|
53
|
+
|
|
54
|
+
def prompt(self, message: str, streaming_behavior: str | None = None) -> None:
|
|
55
|
+
cmd: dict[str, Any] = {"type": "prompt", "message": message}
|
|
56
|
+
if streaming_behavior:
|
|
57
|
+
cmd["streamingBehavior"] = streaming_behavior
|
|
58
|
+
self.send_command(cmd)
|
|
59
|
+
|
|
60
|
+
def steer(self, message: str) -> None:
|
|
61
|
+
self.send_command({"type": "prompt", "message": message, "streamingBehavior": "steer"})
|
|
62
|
+
|
|
63
|
+
def follow_up(self, message: str) -> None:
|
|
64
|
+
self.send_command({"type": "prompt", "message": message, "streamingBehavior": "followUp"})
|
|
65
|
+
|
|
66
|
+
def abort(self) -> None:
|
|
67
|
+
self.send_command({"type": "abort"})
|
|
68
|
+
|
|
69
|
+
def on_event(self, callback: Callable[[dict[str, Any]], None]) -> Callable[[], None]:
|
|
70
|
+
self._event_callbacks.append(callback)
|
|
71
|
+
|
|
72
|
+
def unsubscribe() -> None:
|
|
73
|
+
try:
|
|
74
|
+
self._event_callbacks.remove(callback)
|
|
75
|
+
except ValueError:
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
return unsubscribe
|
|
79
|
+
|
|
80
|
+
def close(self) -> None:
|
|
81
|
+
if self._closed:
|
|
82
|
+
return
|
|
83
|
+
self._closed = True
|
|
84
|
+
if self._unsubscribe:
|
|
85
|
+
self._unsubscribe()
|
|
86
|
+
self._unsubscribe = None
|
|
87
|
+
if self._proc and self._proc.poll() is None:
|
|
88
|
+
self._proc.terminate()
|
|
89
|
+
try:
|
|
90
|
+
self._proc.wait(timeout=5)
|
|
91
|
+
except subprocess.TimeoutExpired:
|
|
92
|
+
self._proc.kill()
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def _format_relay_message(message: Any) -> str:
|
|
96
|
+
location = f" [#{message.channel}]" if getattr(message, "channel", None) else ""
|
|
97
|
+
return f"Relay message from {message.sender}{location}: {message.text}"
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _start_reader(session: PiRpcSession) -> None:
|
|
101
|
+
def _read_stdout() -> None:
|
|
102
|
+
proc = session._proc
|
|
103
|
+
if proc.stdout is None:
|
|
104
|
+
return
|
|
105
|
+
for line in proc.stdout:
|
|
106
|
+
line = line.strip()
|
|
107
|
+
if not line:
|
|
108
|
+
continue
|
|
109
|
+
try:
|
|
110
|
+
event = json.loads(line)
|
|
111
|
+
except json.JSONDecodeError:
|
|
112
|
+
continue
|
|
113
|
+
event_type = event.get("type", "")
|
|
114
|
+
if event_type in ("agent_start", "turn_start"):
|
|
115
|
+
session._is_streaming = True
|
|
116
|
+
elif event_type in ("agent_end", "turn_end"):
|
|
117
|
+
session._is_streaming = False
|
|
118
|
+
for cb in list(session._event_callbacks):
|
|
119
|
+
cb(event)
|
|
120
|
+
|
|
121
|
+
thread = threading.Thread(target=_read_stdout, daemon=True)
|
|
122
|
+
thread.start()
|
|
123
|
+
session._reader_thread = thread
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def on_relay(
|
|
127
|
+
name: str,
|
|
128
|
+
config: dict[str, Any] | None = None,
|
|
129
|
+
relay: "Relay | None" = None,
|
|
130
|
+
) -> PiRpcSession:
|
|
131
|
+
"""Spawn Pi in RPC mode and bridge relay communication.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
name: Agent name for relay registration.
|
|
135
|
+
config: Optional dict with ``model``, ``provider``, or extra Pi CLI flags.
|
|
136
|
+
relay: Optional pre-configured Relay instance.
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
A :class:`PiRpcSession` managing the subprocess and relay bridge.
|
|
140
|
+
"""
|
|
141
|
+
if config is None:
|
|
142
|
+
config = {}
|
|
143
|
+
if relay is None:
|
|
144
|
+
from ..core import Relay
|
|
145
|
+
|
|
146
|
+
relay = Relay(name)
|
|
147
|
+
|
|
148
|
+
cmd = ["pi", "--mode", "rpc", "--no-session"]
|
|
149
|
+
if "model" in config:
|
|
150
|
+
cmd.extend(["--model", config["model"]])
|
|
151
|
+
if "provider" in config:
|
|
152
|
+
cmd.extend(["--provider", config["provider"]])
|
|
153
|
+
|
|
154
|
+
proc = subprocess.Popen(
|
|
155
|
+
cmd,
|
|
156
|
+
stdin=subprocess.PIPE,
|
|
157
|
+
stdout=subprocess.PIPE,
|
|
158
|
+
stderr=subprocess.PIPE,
|
|
159
|
+
text=True,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
session = PiRpcSession(proc, relay)
|
|
163
|
+
|
|
164
|
+
def handle_relay_message(message: Any) -> None:
|
|
165
|
+
formatted = _format_relay_message(message)
|
|
166
|
+
if session.is_streaming:
|
|
167
|
+
session.steer(formatted)
|
|
168
|
+
else:
|
|
169
|
+
session.follow_up(formatted)
|
|
170
|
+
|
|
171
|
+
session._unsubscribe = relay.on_message(handle_relay_message)
|
|
172
|
+
|
|
173
|
+
_start_reader(session)
|
|
174
|
+
|
|
175
|
+
return session
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Swarms adapter for on_relay()."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from ..core import Relay
|
|
8
|
+
|
|
9
|
+
def on_relay(agent: Any, relay: "Relay | None" = None) -> Any:
|
|
10
|
+
"""Wrap Swarms Agent to connect it to the relay."""
|
|
11
|
+
if relay is None:
|
|
12
|
+
from ..core import Relay
|
|
13
|
+
relay = Relay(getattr(agent, "name", "Agent"))
|
|
14
|
+
|
|
15
|
+
# 1. Add tools (as callables)
|
|
16
|
+
async def relay_send(to: str, text: str) -> str:
|
|
17
|
+
"""Send a private message to another agent."""
|
|
18
|
+
await relay.send(to, text)
|
|
19
|
+
return "Message sent"
|
|
20
|
+
|
|
21
|
+
async def relay_inbox() -> str:
|
|
22
|
+
"""Check for new messages in the inbox."""
|
|
23
|
+
messages = await relay.inbox()
|
|
24
|
+
if not messages: return "No new messages"
|
|
25
|
+
return "\n".join([f"From {m.sender}: {m.text}" for m in messages])
|
|
26
|
+
|
|
27
|
+
async def relay_post(channel: str, text: str) -> str:
|
|
28
|
+
"""Post a message to a shared channel."""
|
|
29
|
+
await relay.post(channel, text)
|
|
30
|
+
return "Message posted"
|
|
31
|
+
|
|
32
|
+
async def relay_agents() -> str:
|
|
33
|
+
"""List all agents currently on the relay."""
|
|
34
|
+
agents = await relay.agents()
|
|
35
|
+
return ", ".join(agents)
|
|
36
|
+
|
|
37
|
+
agent.tools.extend([relay_send, relay_inbox, relay_post, relay_agents])
|
|
38
|
+
|
|
39
|
+
# 2. Receiving: bridge relay on_message to agent.receive_message
|
|
40
|
+
def _handle_relay_message(message: Any) -> None:
|
|
41
|
+
agent.receive_message(message.sender, message.text)
|
|
42
|
+
|
|
43
|
+
relay.on_message(_handle_relay_message)
|
|
44
|
+
return agent
|
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""High-level Relay facade for communicate mode."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import atexit
|
|
6
|
+
import asyncio
|
|
7
|
+
import threading
|
|
8
|
+
import warnings
|
|
9
|
+
from inspect import isawaitable
|
|
10
|
+
from typing import Any, Callable
|
|
11
|
+
|
|
12
|
+
from .transport import RelayTransport
|
|
13
|
+
from .types import Message, MessageCallback, RelayConfig, RelayConfigError, RelayAuthError
|
|
14
|
+
|
|
15
|
+
MAX_PENDING_MESSAGES = 10_000
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class Relay:
|
|
19
|
+
"""Relay client with buffered inbox access and callback subscriptions."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, agent_name: str, config: RelayConfig | None = None) -> None:
|
|
22
|
+
self.agent_name = agent_name
|
|
23
|
+
self.config = config if config is not None else RelayConfig.resolve()
|
|
24
|
+
self.transport = RelayTransport(agent_name, self.config)
|
|
25
|
+
|
|
26
|
+
self._pending: list[Message] = []
|
|
27
|
+
self._callbacks: list[MessageCallback] = []
|
|
28
|
+
self._state_lock = threading.Lock()
|
|
29
|
+
self._connect_task: asyncio.Task[None] | None = None
|
|
30
|
+
self._connect_future: asyncio.Future[None] | None = None
|
|
31
|
+
self._connected = False
|
|
32
|
+
self._ws_connected = False
|
|
33
|
+
self._poll_task: asyncio.Task[None] | None = None
|
|
34
|
+
|
|
35
|
+
self.transport.on_ws_message(self._handle_transport_message)
|
|
36
|
+
|
|
37
|
+
if self.config.auto_cleanup:
|
|
38
|
+
atexit.register(self.close_sync)
|
|
39
|
+
|
|
40
|
+
async def send(self, to: str, text: str) -> None:
|
|
41
|
+
await self._ensure_connected()
|
|
42
|
+
await self.transport.send_dm(to, text)
|
|
43
|
+
|
|
44
|
+
async def post(self, channel: str, text: str) -> None:
|
|
45
|
+
await self._ensure_connected()
|
|
46
|
+
await self.transport.post_message(channel, text)
|
|
47
|
+
|
|
48
|
+
async def reply(self, message_id: str, text: str) -> None:
|
|
49
|
+
await self._ensure_connected()
|
|
50
|
+
await self.transport.reply(message_id, text)
|
|
51
|
+
|
|
52
|
+
async def inbox(self) -> list[Message]:
|
|
53
|
+
await self._ensure_connected()
|
|
54
|
+
if not self._ws_connected:
|
|
55
|
+
polled = await self.transport.check_inbox()
|
|
56
|
+
for msg in polled:
|
|
57
|
+
await self._handle_transport_message(msg)
|
|
58
|
+
with self._state_lock:
|
|
59
|
+
messages = list(self._pending)
|
|
60
|
+
self._pending.clear()
|
|
61
|
+
return messages
|
|
62
|
+
|
|
63
|
+
async def peek(self) -> list[Message]:
|
|
64
|
+
"""Return buffered messages without draining them."""
|
|
65
|
+
await self._ensure_connected()
|
|
66
|
+
if not self._ws_connected:
|
|
67
|
+
polled = await self.transport.check_inbox()
|
|
68
|
+
for msg in polled:
|
|
69
|
+
await self._handle_transport_message(msg)
|
|
70
|
+
with self._state_lock:
|
|
71
|
+
return list(self._pending)
|
|
72
|
+
|
|
73
|
+
def on_message(self, callback: MessageCallback) -> Callable[[], None]:
|
|
74
|
+
with self._state_lock:
|
|
75
|
+
self._callbacks.append(callback)
|
|
76
|
+
|
|
77
|
+
self._schedule_connect()
|
|
78
|
+
|
|
79
|
+
def unsubscribe() -> None:
|
|
80
|
+
with self._state_lock:
|
|
81
|
+
try:
|
|
82
|
+
self._callbacks.remove(callback)
|
|
83
|
+
except ValueError:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
return unsubscribe
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def join(self, channel: str) -> None:
|
|
90
|
+
await self._ensure_connected()
|
|
91
|
+
await self.transport.join_channel(channel)
|
|
92
|
+
|
|
93
|
+
async def agents(self) -> list[str]:
|
|
94
|
+
await self._ensure_connected()
|
|
95
|
+
return await self.transport.list_agents()
|
|
96
|
+
|
|
97
|
+
async def close(self) -> None:
|
|
98
|
+
connect_task = self._connect_task
|
|
99
|
+
self._connect_task = None
|
|
100
|
+
|
|
101
|
+
if connect_task is not None and not connect_task.done():
|
|
102
|
+
connect_task.cancel()
|
|
103
|
+
try:
|
|
104
|
+
await connect_task
|
|
105
|
+
except asyncio.CancelledError:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
poll_task = self._poll_task
|
|
109
|
+
self._poll_task = None
|
|
110
|
+
if poll_task is not None and not poll_task.done():
|
|
111
|
+
poll_task.cancel()
|
|
112
|
+
try:
|
|
113
|
+
await poll_task
|
|
114
|
+
except asyncio.CancelledError:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
await self.transport.disconnect()
|
|
118
|
+
self._connected = False
|
|
119
|
+
self._ws_connected = False
|
|
120
|
+
|
|
121
|
+
def send_sync(self, to: str, text: str) -> None:
|
|
122
|
+
return self._run_sync(self.send(to, text))
|
|
123
|
+
|
|
124
|
+
def post_sync(self, channel: str, text: str) -> None:
|
|
125
|
+
return self._run_sync(self.post(channel, text))
|
|
126
|
+
|
|
127
|
+
def inbox_sync(self) -> list[Message]:
|
|
128
|
+
return self._run_sync(self.inbox())
|
|
129
|
+
|
|
130
|
+
def agents_sync(self) -> list[str]:
|
|
131
|
+
return self._run_sync(self.agents())
|
|
132
|
+
|
|
133
|
+
def close_sync(self) -> None:
|
|
134
|
+
return self._run_sync(self.close())
|
|
135
|
+
|
|
136
|
+
async def __aenter__(self) -> "Relay":
|
|
137
|
+
return self
|
|
138
|
+
|
|
139
|
+
async def __aexit__(self, exc_type: Any, exc: Any, tb: Any) -> None:
|
|
140
|
+
await self.close()
|
|
141
|
+
|
|
142
|
+
async def _ensure_connected(self) -> None:
|
|
143
|
+
if self._connected:
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
# Deduplicate concurrent callers — reuse in-flight connect
|
|
147
|
+
if self._connect_future is not None and not self._connect_future.done():
|
|
148
|
+
await self._connect_future
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
current_task = asyncio.current_task()
|
|
152
|
+
connect_task = self._connect_task
|
|
153
|
+
if connect_task is not None and connect_task is not current_task and not connect_task.done():
|
|
154
|
+
await connect_task
|
|
155
|
+
return
|
|
156
|
+
|
|
157
|
+
loop = asyncio.get_running_loop()
|
|
158
|
+
self._connect_future = loop.create_future()
|
|
159
|
+
try:
|
|
160
|
+
try:
|
|
161
|
+
await self.transport.connect()
|
|
162
|
+
self._ws_connected = True
|
|
163
|
+
except Exception:
|
|
164
|
+
# WebSocket failed — register agent via HTTP and fall back to polling
|
|
165
|
+
await self.transport.register_agent()
|
|
166
|
+
self._ws_connected = False
|
|
167
|
+
self._start_poll_loop()
|
|
168
|
+
|
|
169
|
+
from contextlib import suppress
|
|
170
|
+
for ch in self.config.channels:
|
|
171
|
+
with suppress(Exception):
|
|
172
|
+
await self.transport.join_channel(ch)
|
|
173
|
+
|
|
174
|
+
self._connected = True
|
|
175
|
+
self._connect_future.set_result(None)
|
|
176
|
+
self._connect_future = None
|
|
177
|
+
except Exception as exc:
|
|
178
|
+
# Ensure future is always resolved so waiters don't hang
|
|
179
|
+
if not self._connect_future.done():
|
|
180
|
+
self._connect_future.set_exception(exc)
|
|
181
|
+
self._connect_future = None
|
|
182
|
+
raise
|
|
183
|
+
|
|
184
|
+
def _schedule_connect(self) -> None:
|
|
185
|
+
if self._connected:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if self._connect_task is not None and not self._connect_task.done():
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
try:
|
|
192
|
+
loop = asyncio.get_running_loop()
|
|
193
|
+
except RuntimeError:
|
|
194
|
+
self._run_sync(self._ensure_connected())
|
|
195
|
+
return
|
|
196
|
+
|
|
197
|
+
task = loop.create_task(self._ensure_connected())
|
|
198
|
+
self._connect_task = task
|
|
199
|
+
task.add_done_callback(self._clear_connect_task)
|
|
200
|
+
|
|
201
|
+
def _clear_connect_task(self, task: asyncio.Task[None]) -> None:
|
|
202
|
+
if self._connect_task is task:
|
|
203
|
+
self._connect_task = None
|
|
204
|
+
|
|
205
|
+
def _start_poll_loop(self) -> None:
|
|
206
|
+
if self._poll_task is not None and not self._poll_task.done():
|
|
207
|
+
return
|
|
208
|
+
try:
|
|
209
|
+
loop = asyncio.get_running_loop()
|
|
210
|
+
except RuntimeError:
|
|
211
|
+
return
|
|
212
|
+
self._poll_task = loop.create_task(self._poll_loop())
|
|
213
|
+
|
|
214
|
+
async def _poll_loop(self) -> None:
|
|
215
|
+
interval = self.config.poll_interval_ms / 1000.0
|
|
216
|
+
while self._connected and not self._ws_connected:
|
|
217
|
+
try:
|
|
218
|
+
messages = await self.transport.check_inbox()
|
|
219
|
+
for msg in messages:
|
|
220
|
+
await self._handle_transport_message(msg)
|
|
221
|
+
except asyncio.CancelledError:
|
|
222
|
+
raise
|
|
223
|
+
except Exception:
|
|
224
|
+
pass
|
|
225
|
+
await asyncio.sleep(interval)
|
|
226
|
+
|
|
227
|
+
async def _handle_transport_message(self, message: Message) -> None:
|
|
228
|
+
with self._state_lock:
|
|
229
|
+
callbacks = list(self._callbacks)
|
|
230
|
+
# Always buffer the message (spec: "both" case — callbacks AND inbox)
|
|
231
|
+
if len(self._pending) >= MAX_PENDING_MESSAGES:
|
|
232
|
+
self._pending.pop(0)
|
|
233
|
+
warnings.warn(
|
|
234
|
+
"Relay pending buffer exceeded 10,000 messages; dropping oldest message.",
|
|
235
|
+
UserWarning,
|
|
236
|
+
stacklevel=2,
|
|
237
|
+
)
|
|
238
|
+
self._pending.append(message)
|
|
239
|
+
|
|
240
|
+
for callback in callbacks:
|
|
241
|
+
result = callback(message)
|
|
242
|
+
if isawaitable(result):
|
|
243
|
+
await result
|
|
244
|
+
|
|
245
|
+
@staticmethod
|
|
246
|
+
def _run_sync(awaitable: Any) -> Any:
|
|
247
|
+
try:
|
|
248
|
+
asyncio.get_running_loop()
|
|
249
|
+
except RuntimeError:
|
|
250
|
+
return asyncio.run(awaitable)
|
|
251
|
+
# Running inside an active event loop — execute in a separate thread
|
|
252
|
+
import concurrent.futures
|
|
253
|
+
|
|
254
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
255
|
+
return pool.submit(asyncio.run, awaitable).result()
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def on_relay(agent: Any, relay: Relay | None = None) -> Any:
|
|
259
|
+
"""Auto-detect and apply the correct relay adapter for the given agent."""
|
|
260
|
+
if relay is None:
|
|
261
|
+
# Resolve a default relay if none provided
|
|
262
|
+
relay = Relay(getattr(agent, "name", "Agent"))
|
|
263
|
+
|
|
264
|
+
cls_module = type(agent).__module__
|
|
265
|
+
if cls_module.startswith("claude_agent_sdk"):
|
|
266
|
+
agent_name = getattr(agent, "name", "Agent")
|
|
267
|
+
from .adapters.claude_sdk import on_relay as _adapt
|
|
268
|
+
return _adapt(agent_name, agent, relay)
|
|
269
|
+
if cls_module.startswith("agents"):
|
|
270
|
+
from .adapters.openai_agents import on_relay as _adapt
|
|
271
|
+
return _adapt(agent, relay)
|
|
272
|
+
if cls_module.startswith("google.adk"):
|
|
273
|
+
from .adapters.google_adk import on_relay as _adapt
|
|
274
|
+
return _adapt(agent, relay)
|
|
275
|
+
if cls_module.startswith("agno"):
|
|
276
|
+
from .adapters.agno import on_relay as _adapt
|
|
277
|
+
return _adapt(agent, relay)
|
|
278
|
+
if cls_module.startswith("swarms"):
|
|
279
|
+
from .adapters.swarms import on_relay as _adapt
|
|
280
|
+
return _adapt(agent, relay)
|
|
281
|
+
if cls_module.startswith("crewai"):
|
|
282
|
+
from .adapters.crewai import on_relay as _adapt
|
|
283
|
+
return _adapt(agent, relay)
|
|
284
|
+
|
|
285
|
+
raise TypeError(
|
|
286
|
+
f"on_relay() doesn't recognize {type(agent).__name__} from {cls_module}. "
|
|
287
|
+
"Supported frameworks: Claude Agent SDK, OpenAI Agents, Google ADK, Agno, Swarms, CrewAI (Python). "
|
|
288
|
+
"For Claude Agent SDK, you can also import directly: "
|
|
289
|
+
"from agent_relay.communicate.adapters.claude_sdk import on_relay"
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
__all__ = ["Relay", "on_relay"]
|