✨ Feat: Add direct chat functionality for external A2A agents#2820
✨ Feat: Add direct chat functionality for external A2A agents#2820
Conversation
…t discovery - A2A Server: Enable publishing platform agents as A2A agents with configurable endpoints, including database schema for agent registration and management - A2A Client: Support discovering and invoking external A2A agents through agent discovery modal, HTTP client, and agent adapter integration - Database: Add `a2a_agent` and `a2a_agent_endpoint` tables with foreign key relationships to existing agent tables - SDK: Implement `A2AAgentProxy` for SDK-level A2A agent invocation support - Frontend: Add A2A Server Settings Panel, Agent Discovery Modal, and corresponding hooks (`useA2AServerAgents`, `useExternalAgents`) - Backend API: Add `a2a_client_app.py` and `a2a_server_app.py` endpoints with service layer implementations - i18n: Update English and Chinese localization files with A2A-related translations - System Prompts: Update manager system prompt templates to support A2A protocol context
… through an exception' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
… through an exception' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…0行才定义。这构成了循环引用/前向引用
…时,之前会错误地使用 REST 的 url,现在会正确使用 JSON-RPC 的 url
… 'agent_id' is not defined,根本走不到 return 语句
# Conflicts: # backend/database/db_models.py
Made-with: Cursor
There was a problem hiding this comment.
Pull request overview
Adds a direct “chat with external A2A agent” capability end-to-end (DB schema, backend endpoint, and frontend UI).
Changes:
- Introduces new A2A-related tables for external agent discovery, server registration, and task/message/artifact tracking.
- Adds a backend API route to send a chat message to an external agent (
POST /a2a/client/agents/{id}/chat). - Adds a frontend chat modal + API client method, wiring it into the external agent discovery UI and adding i18n strings.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| k8s/helm/nexent/charts/nexent-common/files/init.sql | Adds A2A protocol tables to Helm DB bootstrap script |
| docker/init.sql | Adds the same A2A tables to Docker DB bootstrap script |
| backend/apps/a2a_client_app.py | Adds external-agent chat endpoint and request model |
| frontend/services/api.ts | Adds API endpoint builder for external-agent chat |
| frontend/services/a2aService.ts | Adds sendChatMessage API wrapper for chat |
| frontend/public/locales/zh/common.json | Adds Chinese translations for chat UI and error text |
| frontend/public/locales/en/common.json | Adds English translations for chat UI and error text |
| frontend/app/[locale]/agents/components/a2a/A2AChatModal.tsx | New modal UI to chat with an external agent |
| frontend/app/[locale]/agents/components/a2a/A2AAgentDiscoveryModal.tsx | Adds “chat” action button to open the new modal |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const response = await fetchWithErrorHandling(API_ENDPOINTS.a2a.agentChat(agentId), { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify({ message }), | ||
| }); | ||
| const data = await response.json(); | ||
|
|
||
| if (response.ok && data.status === 'success') { | ||
| return { success: true, data: data.data }; | ||
| } | ||
|
|
||
| return { success: false, message: data.detail || t('a2a.service.chatFailed') }; | ||
| } catch (error) { |
There was a problem hiding this comment.
fetchWithErrorHandling throws on non-2xx responses, so the response.ok / data.detail fallback branch here will never run for HTTP errors, and backend error details will be lost. Consider catching ApiError (from api.ts) and returning error.message, or avoid fetchWithErrorHandling for this call if you want to handle non-OK responses manually.
| # Build A2A message format following A2A protocol with parts array | ||
| a2a_message = { | ||
| "role": "user", | ||
| "parts": [ | ||
| { | ||
| "text": request_body.message.strip(), | ||
| "mediaType": "text/plain" | ||
| } | ||
| ], |
There was a problem hiding this comment.
The outbound A2A message uses role: "user" and the part object lacks the type field. Elsewhere in the codebase (e.g., A2A server adapter) roles are represented as ROLE_USER/ROLE_AGENT and parts include {type, text, mediaType}; aligning to that format will improve interoperability with Nexent A2A servers and spec compliance.
| "metadata": { | ||
| "user_id": user_id, | ||
| "tenant_id": tenant_id, | ||
| } | ||
| } |
There was a problem hiding this comment.
The chat request payload includes metadata with user_id and tenant_id, which will be sent to the external agent. For third‑party/external agents this is potentially sensitive information leakage; consider omitting these fields, redacting them, or making their inclusion an explicit opt-in configuration.
| raise HTTPException( | ||
| status_code=HTTPStatus.BAD_REQUEST, | ||
| detail=str(e) |
There was a problem hiding this comment.
a2a_client_service.call_agent() raises AgentCallError when the agent ID is not found or is unavailable, but this handler maps all AgentCallError cases to HTTP 400. Consider returning 404 for the "not found" case and 503 (or 409) when the agent is unavailable, so the endpoint’s status codes match the underlying failure semantics.
| raise HTTPException( | |
| status_code=HTTPStatus.BAD_REQUEST, | |
| detail=str(e) | |
| error_detail = str(e) | |
| error_message = error_detail.lower() | |
| if "not found" in error_message or "does not exist" in error_message: | |
| status_code = HTTPStatus.NOT_FOUND | |
| elif ( | |
| "unavailable" in error_message | |
| or "temporarily unavailable" in error_message | |
| or "timeout" in error_message | |
| or "timed out" in error_message | |
| or "connection refused" in error_message | |
| or "service unavailable" in error_message | |
| ): | |
| status_code = HTTPStatus.SERVICE_UNAVAILABLE | |
| else: | |
| status_code = HTTPStatus.BAD_REQUEST | |
| raise HTTPException( | |
| status_code=status_code, | |
| detail=error_detail |
| @router.post("/agents/{external_agent_id}/chat") | ||
| async def chat_with_external_agent( | ||
| external_agent_id: int, | ||
| request_body: ChatRequest, | ||
| authorization: Annotated[Optional[str], Header()] = None, | ||
| http_request: Request = None | ||
| ): | ||
| """Send a chat message to an external A2A agent and get a response. | ||
|
|
||
| This endpoint allows users to directly interact with external A2A agents | ||
| without the need to add them as sub-agents first. | ||
| """ |
There was a problem hiding this comment.
This PR adds a new API route (POST /a2a/client/agents/{external_agent_id}/chat) with multiple success/error branches, but there are no app-level tests covering the new endpoint behavior (success, empty message -> 400, agent not found, agent call failure). Adding FastAPI client tests similar to existing northbound A2A route tests would prevent regressions.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Uh oh!
There was an error while loading. Please reload this page.