Claude Code Claude Opus 4.6 commited on
Commit
0fd72f9
·
1 Parent(s): baf67a7

Claude Code: Complete A2A routing - add target_agent validation with 400 error

Browse files

- Modify POST /api/say to accept optional target_agent string
- Return 400 error if target_agent not found in registry
- Default to broadcast when target_agent is null/missing
- Log routing decision (Broadcast vs Direct) to console

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. app.py +38 -35
app.py CHANGED
@@ -282,64 +282,67 @@ async def api_say(request_data: dict):
282
  Payload:
283
  {
284
  "message": "Hello, Agent!",
285
- "target_agent": "AgentName", // Optional - defaults to "all" if missing
286
  "sender": "sender_name" // Optional - for logging
287
  }
288
  """
289
  global _registered_agents, _agent_name_routing
290
 
291
  message = request_data.get("message", "")
292
- target_agent = request_data.get("target_agent", "")
293
  sender = request_data.get("sender", "unknown")
294
 
295
  if not message:
296
  raise HTTPException(status_code=400, detail="message field is required")
297
 
298
- # Handle missing target_agent with warning log for observability
299
- if not target_agent:
300
- logger.warning(f"[A2A] No target_agent specified by '{sender}', defaulting to broadcast (all). "
301
- f"This may cause unnecessary network traffic.")
302
- target_agent = "all"
303
-
304
- results = {"delivered": [], "failed": [], "target_type": target_agent}
305
 
306
- if target_agent.lower() == "all":
307
- # Broadcast to all registered agents using dict iteration (still O(n) but explicit)
308
- logger.info(f"[A2A] Broadcasting message from '{sender}' to all agents")
 
309
  for agent_id, agent_info in _registered_agents.items():
310
  agent_name = agent_info["agentName"]
311
  results["delivered"].append({
312
  "agentId": agent_id,
313
  "agentName": agent_name
314
  })
315
- # In a real implementation, you'd push to each agent here
316
  results["broadcast_count"] = len(results["delivered"])
 
 
 
 
317
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
318
  else:
319
- # Dictionary-based routing - O(1) lookup prevents broadcast storms
320
- if target_agent in _agent_name_routing:
321
- target_id = _agent_name_routing[target_agent]
322
- agent_info = _registered_agents.get(target_id)
323
- if agent_info:
324
- results["delivered"].append({
325
- "agentId": target_id,
326
- "agentName": target_agent
327
- })
328
- logger.info(f"[A2A] Message from '{sender}' delivered to '{target_agent}'")
329
- else:
330
- results["failed"].append({
331
- "agentName": target_agent,
332
- "error": "Agent registered in routing but not in registry"
333
- })
334
- else:
335
- results["failed"].append({
336
- "agentName": target_agent,
337
- "error": "Agent not found in routing table"
338
- })
339
- logger.warning(f"[A2A] Target agent '{target_agent}' not found in routing table")
340
 
341
  return {
342
- "success": len(results["failed"]) == 0,
343
  "results": results
344
  }
345
 
 
282
  Payload:
283
  {
284
  "message": "Hello, Agent!",
285
+ "target_agent": "AgentName", // Optional - if null/missing, broadcast to all
286
  "sender": "sender_name" // Optional - for logging
287
  }
288
  """
289
  global _registered_agents, _agent_name_routing
290
 
291
  message = request_data.get("message", "")
292
+ target_agent = request_data.get("target_agent")
293
  sender = request_data.get("sender", "unknown")
294
 
295
  if not message:
296
  raise HTTPException(status_code=400, detail="message field is required")
297
 
298
+ results = {"delivered": [], "failed": [], "routing_type": ""}
 
 
 
 
 
 
299
 
300
+ # Handle missing or null target_agent - default to broadcast
301
+ if not target_agent:
302
+ logger.info(f"[A2A] Routing: BROADCAST (no target specified, sending to all agents)")
303
+ results["routing_type"] = "Broadcast"
304
  for agent_id, agent_info in _registered_agents.items():
305
  agent_name = agent_info["agentName"]
306
  results["delivered"].append({
307
  "agentId": agent_id,
308
  "agentName": agent_name
309
  })
 
310
  results["broadcast_count"] = len(results["delivered"])
311
+ return {
312
+ "success": True,
313
+ "results": results
314
+ }
315
 
316
+ # Direct routing to specific agent
317
+ logger.info(f"[A2A] Routing: DIRECT to '{target_agent}'")
318
+ results["routing_type"] = "Direct"
319
+
320
+ # Look up agent in routing table - return 400 if not found
321
+ if target_agent not in _agent_name_routing:
322
+ logger.warning(f"[A2A] Agent '{target_agent}' not found in registry")
323
+ raise HTTPException(
324
+ status_code=400,
325
+ detail=f"Agent '{target_agent}' not found in registry. Available agents: {list(_agent_name_routing.keys())}"
326
+ )
327
+
328
+ target_id = _agent_name_routing[target_agent]
329
+ agent_info = _registered_agents.get(target_id)
330
+ if agent_info:
331
+ results["delivered"].append({
332
+ "agentId": target_id,
333
+ "agentName": target_agent
334
+ })
335
+ logger.info(f"[A2A] Message from '{sender}' delivered to '{target_agent}'")
336
  else:
337
+ # This should never happen if routing table is consistent
338
+ logger.error(f"[A2A] Inconsistent state: '{target_agent}' in routing but not in registry")
339
+ raise HTTPException(
340
+ status_code=500,
341
+ detail=f"Internal error: Agent '{target_agent}' found in routing table but not in registry"
342
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
343
 
344
  return {
345
+ "success": True,
346
  "results": results
347
  }
348