Files

183 lines
5.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""A2A mode — ``AgentMode`` for A2A agent communication.
Exposes ``/a2a`` slash command via ``SlashCommandRegistry``.
"""
from __future__ import annotations
import json
import logging
from pathlib import Path
from typing import TYPE_CHECKING, Optional
from agentscope.message import Msg, TextBlock
from pineagents.modes.base import AgentMode
from pineagents.runtime.slash_command_registry import CommandSpec
if TYPE_CHECKING:
from pineagents.runtime.hooks import HookContext
logger = logging.getLogger("qwenpaw").getChild(
__name__.replace("plugin_cloudpaw.", ""),
)
_A2A_CONFIG_FILENAME = "a2a_config.json"
def _load_a2a_agents(workspace_dir: Path) -> dict[str, dict]:
"""Load per-agent A2A config from workspace."""
path = workspace_dir / _A2A_CONFIG_FILENAME
if not path.exists():
return {}
try:
data = json.loads(path.read_text(encoding="utf-8"))
return data.get("agents", {})
except (json.JSONDecodeError, OSError) as exc:
logger.warning("Failed to load %s: %s", path, exc)
return {}
def _info_msg(text: str) -> Msg:
"""Wrap text into a system Msg for display."""
return Msg(
name="system",
content=[TextBlock(type="text", text=text)],
role="system",
)
def _rewrite_user_msg(ctx: "HookContext", text: str) -> None:
"""Replace the last user message with *text*."""
msgs = getattr(ctx, "input_msgs", None)
if not msgs:
return
last = msgs[-1]
if not isinstance(last, Msg):
return
last.content = [TextBlock(type="text", text=text)]
class A2AMode(AgentMode):
"""Bundle for A2A mode behaviour."""
name = "a2a"
def commands(self) -> list[CommandSpec]:
"""Register ``/a2a`` as a standard command."""
return [
CommandSpec(
name="a2a",
handler=self._a2a_handler,
category="plugin",
help_text="List or call remote A2A agents",
metadata={"plugin": "cloudpaw"},
),
]
async def _a2a_handler(
self,
ctx: "HookContext",
args: str,
) -> Optional[Msg]:
"""Handle ``/a2a [args]``.
* ``/a2a`` (no args) — list registered A2A agents
* ``/a2a list`` — list registered A2A agents
* ``/a2a <alias> <message>`` — rewrite for a2a_call
"""
workspace_dir = getattr(ctx, "workspace_dir", None)
if not workspace_dir:
return _info_msg("Error: workspace_dir not available")
agents_cfg = _load_a2a_agents(Path(workspace_dir))
# No args or "list": list agents
if not args or not args.strip() or args.strip().lower() == "list":
return await self._handle_list(agents_cfg)
# Parse args: <alias> <message>
parts = args.strip().split(None, 1)
if len(parts) < 2:
return _info_msg(
"用法:`/a2a <agent_name> <message>`\n\n"
"使用 `/a2a` 或 `/a2a list` 查看可用的 agent 列表。",
)
agent_name, message = parts[0].strip(), parts[1].strip()
# Check if agent exists
if agent_name not in agents_cfg:
available = ", ".join(agents_cfg.keys()) if agents_cfg else ""
return _info_msg(
f"未找到别名为 '{agent_name}' 的已注册 A2A Agent。\n\n"
f"可用别名:{available}",
)
# Rewrite message for agent to call a2a_call
prompt = (
f"请使用 a2a_call 工具调用远程 A2A Agent。\n"
f'调用参数:agent_alias="{agent_name}"'
f'message="{message}"\n\n'
f"请直接调用 a2a_call 工具完成此任务,不需要做其他额外操作。"
)
_rewrite_user_msg(ctx, prompt)
logger.info("[CloudPaw] /a2a rewritten for agent processing")
return None # Let agent process the rewritten message
async def _handle_list(self, agents_cfg: dict[str, dict]) -> Msg:
"""List registered A2A agents."""
if not agents_cfg:
return _info_msg(
"暂无已注册的远程 A2A Agent。\n\n"
"使用 POST /api/a2a/agents 注册新的 Agent"
"或在 A2A 管理页面添加。",
)
try:
from modules.a2a.client_manager import get_a2a_manager
except ImportError:
# Fallback: just list aliases
lines = ["**已注册的远程 A2A Agent\n"]
for alias in agents_cfg.keys():
lines.append(f"- {alias}")
footer = "\n---\n*Use `/a2a <alias> <message>` to send a message.*"
lines.append(footer)
return _info_msg("\n".join(lines))
manager = get_a2a_manager()
lines = ["**已注册的远程 A2A Agent**\n"]
for alias, reg in agents_cfg.items():
card_info = await manager.get_card_info(reg["url"])
status = (
card_info.get("status", "disconnected")
if card_info
else "disconnected"
)
name = card_info.get("name", "") if card_info else ""
desc = card_info.get("description", "") if card_info else ""
status_icon = "🟢" if status == "connected" else ""
line = f"\n{status_icon} **{alias}**"
if name:
line += f"{name}"
if desc:
line += f"\n {desc[:80]}"
if status != "connected":
line += f"\n 状态: {status}"
lines.append(line)
footer = (
"\n---\n"
"*Use `/a2a <alias> <message>` to send a message "
"to a remote Agent.*"
)
lines.append(footer)
return _info_msg("\n".join(lines))
__all__ = ["A2AMode"]