Spaces:
Sleeping
Sleeping
Claude Code Claude Opus 4.6 commited on
Commit ·
adb357b
1
Parent(s): fd7f636
Claude Code: Fix A2A communication - add /a2a/jsonrpc endpoint
Browse files- Add A2A JSON-RPC endpoint to app.py with message/send, health/check, ping, agent/info methods
- Enable a2a-gateway plugin in openclaw.json
- Fix missing inter-agent communication endpoint that was causing A2A failures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- app.py +144 -0
- openclaw/.openclaw/openclaw.json +6 -0
app.py
CHANGED
|
@@ -252,6 +252,150 @@ async def agent_push(request_data: dict):
|
|
| 252 |
"recordedStatus": status
|
| 253 |
}
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
if __name__ == "__main__":
|
| 256 |
port = int(os.environ.get("PORT", 7860))
|
| 257 |
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
|
|
| 252 |
"recordedStatus": status
|
| 253 |
}
|
| 254 |
|
| 255 |
+
|
| 256 |
+
# A2A JSON-RPC endpoint for agent-to-agent communication
|
| 257 |
+
@app.post("/a2a/jsonrpc")
|
| 258 |
+
async def a2a_jsonrpc(request_data: dict):
|
| 259 |
+
"""
|
| 260 |
+
Agent-to-Agent communication endpoint using JSON-RPC 2.0 protocol.
|
| 261 |
+
|
| 262 |
+
Expected format:
|
| 263 |
+
{
|
| 264 |
+
"jsonrpc": "2.0",
|
| 265 |
+
"id": "msg-123",
|
| 266 |
+
"method": "message/send",
|
| 267 |
+
"params": {...}
|
| 268 |
+
}
|
| 269 |
+
"""
|
| 270 |
+
# Validate JSON-RPC format
|
| 271 |
+
jsonrpc_version = request_data.get("jsonrpc")
|
| 272 |
+
if jsonrpc_version != "2.0":
|
| 273 |
+
return {
|
| 274 |
+
"jsonrpc": "2.0",
|
| 275 |
+
"id": request_data.get("id", ""),
|
| 276 |
+
"error": {
|
| 277 |
+
"code": -32600,
|
| 278 |
+
"message": "Invalid Request - jsonrpc version must be 2.0"
|
| 279 |
+
}
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
method = request_data.get("method", "")
|
| 283 |
+
params = request_data.get("params", {})
|
| 284 |
+
req_id = request_data.get("id", "")
|
| 285 |
+
|
| 286 |
+
# Handle different A2A methods
|
| 287 |
+
try:
|
| 288 |
+
if method == "message/send":
|
| 289 |
+
# Handle incoming message from another agent
|
| 290 |
+
message = params.get("message", {})
|
| 291 |
+
message_id = message.get("messageId", req_id)
|
| 292 |
+
|
| 293 |
+
logger.info(f"[A2A] Received message: {message_id}")
|
| 294 |
+
|
| 295 |
+
# Process the message using Cain's brain
|
| 296 |
+
from error_handlers import handle_brain_response
|
| 297 |
+
content = message.get("parts", [{}])[0].get("text", "")
|
| 298 |
+
response_text = handle_brain_response(content)
|
| 299 |
+
|
| 300 |
+
return {
|
| 301 |
+
"jsonrpc": "2.0",
|
| 302 |
+
"id": req_id,
|
| 303 |
+
"result": {
|
| 304 |
+
"messageId": f"response-{message_id}",
|
| 305 |
+
"role": "assistant",
|
| 306 |
+
"parts": [{"type": "text", "text": response_text}],
|
| 307 |
+
"agent": "cain"
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
elif method == "health/check":
|
| 312 |
+
# Health check from another agent
|
| 313 |
+
from error_handlers import handle_status_file_read
|
| 314 |
+
status = handle_status_file_read()
|
| 315 |
+
|
| 316 |
+
return {
|
| 317 |
+
"jsonrpc": "2.0",
|
| 318 |
+
"id": req_id,
|
| 319 |
+
"result": {
|
| 320 |
+
"agent": "cain",
|
| 321 |
+
"status": status.get("health", "HEALTHY"),
|
| 322 |
+
"state": status.get("current_state", "idle"),
|
| 323 |
+
"timestamp": datetime.utcnow().isoformat() + "+00:00"
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
+
elif method == "ping":
|
| 328 |
+
# Simple ping for connectivity check
|
| 329 |
+
return {
|
| 330 |
+
"jsonrpc": "2.0",
|
| 331 |
+
"id": req_id,
|
| 332 |
+
"result": {
|
| 333 |
+
"agent": "cain",
|
| 334 |
+
"pong": True,
|
| 335 |
+
"timestamp": datetime.utcnow().isoformat() + "+00:00"
|
| 336 |
+
}
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
elif method == "agent/info":
|
| 340 |
+
# Return agent information
|
| 341 |
+
from error_handlers import handle_status_file_read
|
| 342 |
+
status = handle_status_file_read()
|
| 343 |
+
|
| 344 |
+
return {
|
| 345 |
+
"jsonrpc": "2.0",
|
| 346 |
+
"id": req_id,
|
| 347 |
+
"result": {
|
| 348 |
+
"agent_id": "cain",
|
| 349 |
+
"name": "Cain",
|
| 350 |
+
"role": "interaction",
|
| 351 |
+
"status": status.get("health", "HEALTHY"),
|
| 352 |
+
"state": status.get("current_state", "idle"),
|
| 353 |
+
"capabilities": [
|
| 354 |
+
"message_send",
|
| 355 |
+
"conversation_process",
|
| 356 |
+
"memory_read",
|
| 357 |
+
"memory_write",
|
| 358 |
+
"session_archive",
|
| 359 |
+
"context_manage",
|
| 360 |
+
"health_check",
|
| 361 |
+
"get_status",
|
| 362 |
+
"agent_ping"
|
| 363 |
+
],
|
| 364 |
+
"parents": ["Adam", "Eve"],
|
| 365 |
+
"workspace": "tao-shen/HuggingClaw-Cain"
|
| 366 |
+
}
|
| 367 |
+
}
|
| 368 |
+
|
| 369 |
+
else:
|
| 370 |
+
# Unknown method
|
| 371 |
+
return {
|
| 372 |
+
"jsonrpc": "2.0",
|
| 373 |
+
"id": req_id,
|
| 374 |
+
"error": {
|
| 375 |
+
"code": -32601,
|
| 376 |
+
"message": f"Method not found: {method}",
|
| 377 |
+
"available_methods": [
|
| 378 |
+
"message/send",
|
| 379 |
+
"health/check",
|
| 380 |
+
"ping",
|
| 381 |
+
"agent/info"
|
| 382 |
+
]
|
| 383 |
+
}
|
| 384 |
+
}
|
| 385 |
+
|
| 386 |
+
except Exception as e:
|
| 387 |
+
logger.error(f"[A2A] Error processing request: {e}")
|
| 388 |
+
return {
|
| 389 |
+
"jsonrpc": "2.0",
|
| 390 |
+
"id": req_id,
|
| 391 |
+
"error": {
|
| 392 |
+
"code": -32603,
|
| 393 |
+
"message": "Internal error",
|
| 394 |
+
"data": str(e)
|
| 395 |
+
}
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
|
| 399 |
if __name__ == "__main__":
|
| 400 |
port = int(os.environ.get("PORT", 7860))
|
| 401 |
uvicorn.run(app, host="0.0.0.0", port=port)
|
openclaw/.openclaw/openclaw.json
CHANGED
|
@@ -48,6 +48,12 @@
|
|
| 48 |
},
|
| 49 |
"coding-agent": {
|
| 50 |
"enabled": true
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
}
|
| 53 |
},
|
|
|
|
| 48 |
},
|
| 49 |
"coding-agent": {
|
| 50 |
"enabled": true
|
| 51 |
+
},
|
| 52 |
+
"a2a-gateway": {
|
| 53 |
+
"enabled": true,
|
| 54 |
+
"port": 18800,
|
| 55 |
+
"protocol": "jsonrpc",
|
| 56 |
+
"timeout": 90
|
| 57 |
}
|
| 58 |
}
|
| 59 |
},
|