fix(a2a): client tools take args-as-dict positional; accept agent_name alias

Live Tier-3 testing (CLI agent -> a2a tools -> live peer gateway -> model)
surfaced two bugs the kwarg-style unit tests masked:

1. registry.dispatch calls handlers as handler(args, **kwargs) — args is the
   whole dict positional. The handlers used keyword params (url=, agent=), so
   the dict bound to the first param and .strip() raised
   'dict object has no attribute strip'. Rewrote all three handlers to take
   args: dict (matching the spotify/google_meet convention). Added a
   registry-dispatch regression test that exercises the real call path the
   direct-kwarg tests never hit.

2. The model repeatedly reached for agent_name= instead of agent= (6 retries
   before success). Accept agent_name/name and message/text/task aliases so a
   reasonable guess succeeds first try.

Verified live: client agent discovers the peer's Agent Card, calls it, and
gets the reply back (PONG round-trip confirmed on both client audit log and
peer conversation log). 39 plugin tests pass.
This commit is contained in:
teknium1 2026-06-07 22:23:35 -07:00
parent 582f155753
commit 9bf2dac6b9
No known key found for this signature in database
2 changed files with 67 additions and 13 deletions

View file

@ -101,9 +101,9 @@ def _rpc_url(base_url: str, card: Optional[dict]) -> str:
# Tool handlers
# --------------------------------------------------------------------------
def a2a_discover(url: str = "", **_: Any) -> str:
def a2a_discover(args: dict, **_: Any) -> str:
"""Fetch and summarize the Agent Card at ``url``."""
url = (url or "").strip()
url = str(args.get("url") or "").strip()
if not url:
return "Error: 'url' is required (e.g. http://localhost:9999)."
try:
@ -130,14 +130,16 @@ def a2a_discover(url: str = "", **_: Any) -> str:
return "\n".join(lines)
def a2a_call(agent: str = "", message: str = "", context_id: str = "", **_: Any) -> str:
def a2a_call(args: dict, **_: Any) -> str:
"""Send a task to a peer agent and return its reply.
``agent`` is a configured peer name (from ``a2a_agents``) or a direct URL.
``context_id`` continues a prior exchange (multi-turn) when provided.
"""
agent = (agent or "").strip()
message = (message or "").strip()
# Accept common aliases models reach for (observed live: 'agent_name').
agent = str(args.get("agent") or args.get("agent_name") or args.get("name") or "").strip()
message = str(args.get("message") or args.get("text") or args.get("task") or "").strip()
context_id = str(args.get("context_id") or args.get("contextId") or "").strip()
if not agent or not message:
return "Error: both 'agent' and 'message' are required."
@ -217,7 +219,7 @@ def _reply_text_from_result(result: Any) -> str:
return protocol.extract_text(result)
def a2a_list(**_: Any) -> str:
def a2a_list(args: dict | None = None, **_: Any) -> str:
"""List configured A2A peers and any persisted conversations."""
cfg = _load_config()
peers = cfg.get("a2a_agents") or {}