Skip to content

feat: add basic A2A workflow adapter#1404

Open
Ziliang-H wants to merge 1 commit into
iflytek:mainfrom
Ziliang-H:feat-a2a-basic-adapter
Open

feat: add basic A2A workflow adapter#1404
Ziliang-H wants to merge 1 commit into
iflytek:mainfrom
Ziliang-H:feat-a2a-basic-adapter

Conversation

@Ziliang-H

Copy link
Copy Markdown

Summary

  • add minimal Agent2Agent (A2A) discovery metadata for Astron Agent workflows
  • expose a root /.well-known/agent-card.json and versioned /workflow/v1/a2a/agent-card.json agent card
  • add an authenticated /workflow/v1/a2a/message:send text-message adapter that maps A2A requests onto the existing workflow chat completion path

Closes #709

Validation

  • python -m compileall core\workflow\main.py core\workflow\api\v1\router.py core\workflow\api\v1\chat\__init__.py core\workflow\api\v1\chat\a2a.py core\workflow\domain\entities\a2a.py core\workflow\extensions\fastapi\base.py
  • git diff --check

Note: full pytest execution was not available in the local environment because pytest and runtime dependencies such as pydantic are not installed in the system Python.

Copilot AI review requested due to automatic review settings June 13, 2026 18:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds minimal Agent2Agent (A2A) support to the workflow service by introducing discovery and message forwarding endpoints, plus wiring them into the existing FastAPI router setup.

Changes:

  • Introduced A2A Pydantic entities and FastAPI endpoints (agent card discovery + message forwarding).
  • Registered new routers (root /.well-known discovery + /workflow/v1/a2a/* endpoints) in the app routing.
  • Added the A2A send endpoint to the “open API paths” allowlist.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
core/workflow/main.py Registers a new root router to serve A2A discovery endpoints.
core/workflow/extensions/fastapi/base.py Adds the A2A send endpoint to the open-path allowlist.
core/workflow/domain/entities/a2a.py Introduces Pydantic models for A2A agent card + message sending.
core/workflow/api/v1/router.py Wires A2A routers into existing v1 routing (both root discovery and workflow-prefixed API).
core/workflow/api/v1/chat/a2a.py Implements A2A discovery endpoints and forwards messages into chat_open.
core/workflow/api/v1/chat/init.py Exports A2A routers for inclusion in the v1 router module.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 19 to 23
CHAT_OPEN_API_PATHS = [
"/workflow/v1/chat/completions",
"/workflow/v1/a2a/message:send",
"/workflow/v1/resume",
]
Comment thread core/workflow/api/v1/chat/a2a.py Outdated
Comment on lines +64 to +65
parameters = dict(request.parameters)
parameters.setdefault("input", request.text())
Comment thread core/workflow/domain/entities/a2a.py Outdated
Comment on lines +45 to +50
class A2AMessage(BaseModel):
"""Subset of the A2A message shape needed for text chat."""

role: str = "user"
parts: List[A2ATextPart]
message_id: Optional[str] = Field("", alias="messageId")
Comment thread core/workflow/api/v1/chat/a2a.py Outdated

def build_agent_card() -> A2AAgentCard:
"""Build public A2A discovery metadata for Astron Agent workflows."""
base_url = os.getenv("A2A_PUBLIC_BASE_URL", "")
"forwarding to published workflows."
),
version=os.getenv("A2A_AGENT_VERSION", "0.1.0"),
url=f"{base_url}/workflow/v1/a2a/message:send" if base_url else "",

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces minimal Agent2Agent (A2A) endpoints for published workflows, including public discovery metadata and a synchronous text message adapter mapping A2A messages to the existing workflow chat completion API. Feedback suggests avoiding decorating a single function with multiple routes to prevent duplicate OpenAPI operationIds, adding validation to reject empty messages early, and removing the new endpoint from CHAT_OPEN_API_PATHS to ensure standard JSON error payloads are returned instead of forcing SSE format for early validation failures.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +46 to +53
@discovery_router.get("/.well-known/agent-card.json", response_model=A2AAgentCard)
@router.get("/agent-card.json", response_model=A2AAgentCard)
@router.get("/.well-known/agent-card.json", response_model=A2AAgentCard)
async def agent_card() -> A2AAgentCard:
"""
Return public A2A discovery metadata for Astron Agent workflows.
"""
return build_agent_card()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Decorating a single function with multiple routes across different routers can cause FastAPI to generate duplicate operationIds in the OpenAPI schema, which violates the OpenAPI specification and can break client generation tools. It is highly recommended to define separate, clearly named handler functions for each route to ensure unique operation IDs and cleaner routing.

@discovery_router.get("/.well-known/agent-card.json", response_model=A2AAgentCard)
async def get_well_known_agent_card() -> A2AAgentCard:
    """Return public A2A discovery metadata at the root well-known path."""
    return build_agent_card()


@router.get("/agent-card.json", response_model=A2AAgentCard)
async def get_a2a_agent_card() -> A2AAgentCard:
    """Return public A2A discovery metadata at the versioned path."""
    return build_agent_card()


@router.get("/.well-known/agent-card.json", response_model=A2AAgentCard)
async def get_a2a_well_known_agent_card() -> A2AAgentCard:
    """Return public A2A discovery metadata at the versioned well-known path."""
    return build_agent_card()

Comment thread core/workflow/api/v1/chat/a2a.py Outdated
Comment on lines +57 to +65
async def send_message(
x_consumer_username: Annotated[str, Header()],
request: A2ASendMessageRequest,
) -> Union[StreamingResponse, JSONResponse]:
"""
Map an A2A text message to the existing workflow chat completion API.
"""
parameters = dict(request.parameters)
parameters.setdefault("input", request.text())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the incoming A2A message contains no text parts or only empty text, request.text() will return an empty string. Forwarding an empty input to the downstream workflow engine can cause unexpected failures or errors. Adding a validation check to reject empty messages early improves robustness and adheres to defensive programming principles.

async def send_message(
    x_consumer_username: Annotated[str, Header()],
    request: A2ASendMessageRequest,
) -> Union[StreamingResponse, JSONResponse]:
    """
    Map an A2A text message to the existing workflow chat completion API.
    """
    message_text = request.text().strip()
    if not message_text:
        from fastapi import HTTPException
        raise HTTPException(status_code=400, detail="Message content cannot be empty")

    parameters = dict(request.parameters)
    parameters.setdefault("input", message_text)

Comment on lines 19 to 23
CHAT_OPEN_API_PATHS = [
"/workflow/v1/chat/completions",
"/workflow/v1/a2a/message:send",
"/workflow/v1/resume",
]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Including /workflow/v1/a2a/message:send in CHAT_OPEN_API_PATHS forces all validation and middleware errors for this endpoint to be returned as SSE (text/event-stream) responses. However, A2A clients may send non-streaming requests (where stream=False) or expect standard JSON error payloads for HTTP-level failures (like 400 Bad Request). Returning SSE format for early validation errors will break standard HTTP clients. It is safer to remove this path from CHAT_OPEN_API_PATHS so that early errors are returned as standard JSON, while active stream errors are still handled gracefully within the handler.

CHAT_OPEN_API_PATHS = [
    "/workflow/v1/chat/completions",
    "/workflow/v1/resume",
]

@Ziliang-H Ziliang-H force-pushed the feat-a2a-basic-adapter branch from 65b65d3 to 1495b28 Compare June 13, 2026 18:27
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@Ziliang-H

Copy link
Copy Markdown
Author

Follow-up update: I pushed commit 1495b28 to address the automated review feedback:

  • moved /workflow/v1/a2a/message:send out of CHAT_OPEN_API_PATHS and into the authenticated API path list so it keeps the standard auth middleware while returning normal JSON for early HTTP errors
  • always derives parameters["input"] from the A2A message text instead of allowing parameters.input to override it
  • rejects empty A2A text messages with 400
  • normalizes A2A_PUBLIC_BASE_URL with rstrip("/")
  • split agent-card handlers so generated OpenAPI operation IDs remain distinct
  • amended the commit with DCO sign-off; the DCO check is now green

Current remaining blocker appears to be CLA assistant, which I will handle from the account side.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Support A2A(agent2agent) Protocol

3 participants