Skip to content

feat(agents): support invoke over the a2a protocol#8989

Merged
huimiu merged 6 commits into
mainfrom
glharper/8956-invoke-a2a-protocol
Jul 8, 2026
Merged

feat(agents): support invoke over the a2a protocol#8989
huimiu merged 6 commits into
mainfrom
glharper/8956-invoke-a2a-protocol

Conversation

@glharper

@glharper glharper commented Jul 6, 2026

Copy link
Copy Markdown
Member

Closes #8956

What

Adds A2A (Agent2Agent) protocol support to azd ai agent invoke, alongside the existing responses and invocations protocols. A2A was previously deployment-only and excluded from invocation.

# Invoke a deployed agent over A2A (wraps the message as a JSON-RPC message/send)
azd ai agent invoke --protocol a2a "Hello!"

# Send a complete JSON-RPC request from a file
azd ai agent invoke --protocol a2a -f a2a-request.json

# Dump the raw JSON-RPC response
azd ai agent invoke --protocol a2a --output raw "Hello!"

Changes

  • Make a2a invocable — added to InvocableProtocols() / IsInvocable(). A2A is remote-only, gated by a new IsLocalInvocable(); activity_protocol remains fully deployment-only.
  • Routing--protocol a2a and agent.yaml protocol auto-detection route to a new a2aRemote handler (new file invoke_a2a.go), modeled on the invocations handler (session-based memory, --new-session resets, --new-conversation no-op). --local a2a fails fast with actionable guidance.
  • Request body — a plain message is wrapped in a JSON-RPC 2.0 message/send request with a single user text part; --input-file contents are forwarded verbatim as a complete JSON-RPC request.
  • Response — the reply text is extracted from the JSON-RPC result across A2A result shapes (Message parts, Task status.message / artifacts[] / history[]), a JSON-RPC error surfaces as an agent error, and --output raw dumps the response verbatim.
  • Endpoint plumbing — added buildA2AInvokeURL and --agent-endpoint parsing for the .../endpoint/protocols/a2a tail; updated --protocol help, the unsupported-protocol / local-protocol validation messages, the multi-protocol guidance, and command examples.

Tests

  • models_test.go: a2a is now IsInvocable()==true and IsLocalInvocable()==false.
  • agent_endpoint_test.go: parse + buildA2AInvokeURL (api-version/session-id escaping, default api-version).
  • helpers_test.go: single a2a auto-detects; a non-invocable case retained via activity_protocol.
  • invoke_a2a_test.go: JSON-RPC request wrapping, file passthrough, reply-text extraction across Message/Task shapes, JSON-RPC error detection, --local rejection, request-URL fallback.

Validation

go build ./..., full go test ./..., go vet, gofmt, golangci-lint run (0 issues), and cspell — all clean.

⚠️ Note for reviewers — Foundry A2A wire format

The A2A protocol itself is a public open standard (JSON-RPC 2.0 message/send, https://a2a-protocol.org), so the request/response envelope is well-defined. What is not publicly specced is the Foundry hosted-agent A2A invoke endpoint path. This PR follows the codebase's established responses/invocations convention: {projectEndpoint}/agents/{name}/endpoint/protocols/a2a?api-version=...[&agent_session_id=...].

These assumptions are centralized in buildA2AInvokeURL, buildA2ARequestBody, and handleA2AResponse, so if the Foundry contract differs they can be corrected in one place, and --output raw is a format-independent escape hatch. Please confirm the exact Foundry A2A endpoint path (and whether it expects the vanilla JSON-RPC message/send body) before merge. A2A local invocation is intentionally not supported (A2A is a remote agent-to-agent protocol).

Adds A2A (Agent2Agent) protocol support to `azd ai agent invoke`, alongside
the existing responses and invocations protocols (issue #8956). A2A was
previously deployment-only and excluded from invocation.

- Make a2a invocable: include it in InvocableProtocols()/IsInvocable(). A2A is
  remote-only, gated by a new IsLocalInvocable() (activity_protocol stays
  fully deployment-only).
- Route --protocol a2a (and agent.yaml auto-detection) to a new a2aRemote
  handler; --local a2a fails fast with actionable guidance.
- Wrap a plain message in a JSON-RPC 2.0 message/send request with a single
  user text part; forward --input-file contents verbatim as a complete
  JSON-RPC request.
- Parse the JSON-RPC response for the agent's reply text across A2A result
  shapes (Message parts, Task status.message/artifacts/history), surface
  JSON-RPC error envelopes, and support --output raw passthrough.
- Add a2a endpoint URL builder/parsing (.../protocols/a2a) and update
  --protocol help, validation messages, and command examples.

The A2A endpoint path tail and Foundry request/response wire shape are
centralized in buildA2AInvokeURL / buildA2ARequestBody / handleA2AResponse so
they can be adjusted in one place if the Foundry A2A contract differs; the
JSON-RPC envelope follows the A2A spec (a2a-protocol.org). `--output raw`
provides a format-independent escape hatch.

Fixes #8956

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 6, 2026 16:25
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

📋 Prioritization Note

Thanks for the contribution! The linked issue isn't in the current milestone yet.
Thank you for logging this issue; our team is reviewing it. If you need urgent prioritization, tag @RickWinter and @kristenwomack to let us know.

Copilot AI 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.

Pull request overview

This PR adds A2A (Agent2Agent) protocol support to azd ai agent invoke in the azure.ai.agents extension. A2A was previously deployment-only; it is now invocable against deployed Foundry agents (remote-only), alongside the existing responses and invocations protocols. The implementation closely mirrors the established invocationsRemote flow (session-bound memory, bearer-token acquisition after body validation, session-header capture, raw-output escape hatch, next-step guidance) and centralizes the A2A-specific wire format in three well-scoped helpers.

Changes:

  • Makes AgentProtocolA2A invocable via InvocableProtocols()/IsInvocable(), and adds IsLocalInvocable() to keep A2A remote-only; --local a2a fails fast with guidance.
  • Adds a new a2aRemote handler (invoke_a2a.go) that wraps a plain message into a JSON-RPC 2.0 message/send request (or forwards --input-file verbatim), and extracts reply text across A2A Message/Task result shapes.
  • Wires --agent-endpoint parsing and a buildA2AInvokeURL builder for the .../protocols/a2a tail; updates help text, validation messages, examples, and the changelog.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
internal/pkg/agents/agent_api/models.go Adds A2A to invocable protocols and introduces IsLocalInvocable() (A2A remote-only).
internal/pkg/agents/agent_api/models_test.go Updates IsInvocable case for A2A and adds TestIsLocalInvocable.
internal/cmd/invoke.go Routes A2A local/remote, adds local-invocability validation, updates help/examples/flag text.
internal/cmd/invoke_a2a.go New A2A handler: request wrapping, remote POST flow, JSON-RPC error/result parsing, raw output.
internal/cmd/invoke_a2a_test.go Unit tests for request wrapping, text extraction, error envelopes, local rejection, URL fallback.
internal/cmd/agent_endpoint.go Extends endpoint regex/parsing for a2a and adds buildA2AInvokeURL.
internal/cmd/agent_endpoint_test.go Adds A2A parse and URL-builder tests.
internal/cmd/helpers.go Updates auto-detection error text to mention a2a.
internal/cmd/helpers_test.go Adds single-a2a auto-detect case; retargets the non-invocable case to activity_protocol.
CHANGELOG.md Adds an unreleased 1.0.0-beta.5 feature entry.

Notes for the human reviewer:

  • The routing, header handling, session capture, raw-mode Accept-Encoding: identity, trace-ID printing, and next-step emission are consistent with the existing invocationsRemote implementation, and test coverage matches the pattern used for the other protocols.
  • Per the author's own note, the Foundry hosted-agent A2A endpoint path and whether it accepts the vanilla JSON-RPC message/send body are not publicly specced and remain unverified; these assumptions cannot be confirmed from the codebase and warrant confirmation before merge.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/agent_endpoint_test.go Outdated

@jongio jongio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the full changeset (10 files, +734/-15). The A2A handler follows the same session-based pattern as invocations, URL construction and request wrapping are centralized for easy adjustment if the Foundry contract changes, and test coverage spans all A2A result shapes (Message parts, Task status/artifacts/history, JSON-RPC errors, local rejection). No issues found.

@RickWinter RickWinter left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This makes a2a an invocable protocol for azd ai agent invoke, remote-only, routed to a new a2aRemote handler that wraps a plain message in a JSON-RPC 2.0 message/send request (or forwards --input-file verbatim) and extracts the reply from the JSON-RPC result across Message and Task shapes. The approach is the right shape: it follows the existing responses/invocations conventions (session-bound memory, --new-session reset, --new-conversation no-op, --output raw passthrough), and the new IsLocalInvocable() split cleanly expresses that A2A is deployable and remotely invocable but has no local surface.

I checked the parts that depend on code outside the diff. The --local a2a rejection is covered twice, once as early flag validation in RunE when --protocol is explicit, and once as the runtime a2aLocal handler for the agent.yaml auto-detected path, so there is no gap. captureResponseSession reads only the x-agent-session-id header, so calling it before handleA2AResponse does not consume the body. buildA2AInvokeURL matches buildInvocationsURL (query escaping on api-version and session id, agent name left unescaped consistent with the sibling builders since it is pattern-validated upstream).

The one open item is external, not code: the Foundry hosted-agent A2A endpoint path and body contract are unconfirmed. You have already centralized those assumptions in buildA2AInvokeURL / buildA2ARequestBody / handleA2AResponse and left --output raw as an escape hatch, which is the correct containment. Worth confirming the wire contract with the Foundry team before merge, but that does not block the code review.

One nit inline on multi-part text joining, otherwise this is ready. Disposition: mergeable pending the external endpoint confirmation; nothing here requires a code change before merge.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke_a2a.go Outdated
Co-authored-by: Rick Winter <rick.winter@microsoft.com>
@glharper

glharper commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Co-authored-by: glharper <64209257+glharper@users.noreply.github.com>

Copilot AI commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved by merging origin/main and fixing the changelog conflict in ba0d55051.

@jongio jongio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-approving after the merge of main and the one-line fix to use newline as the text-parts separator in a2aTextFromParts. The change makes multi-part A2A responses more readable. No issues.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

@jongio jongio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re-approving. The only change since my last review is a test comment rename (TestBuildA2AURL -> TestBuildA2AInvokeURL) to match the actual function name. No logic changes.

Comment thread cli/azd/extensions/azure.ai.agents/internal/cmd/invoke_a2a.go Outdated
Addresses review feedback (@huimiu): the request body uses the v0.3 JSON-RPC
message/send shape, and the A2A spec treats an omitted version as a 0.3
compatibility default. Send the A2A-Version header explicitly so a Foundry
endpoint on a newer contract negotiates deterministically (returning
VersionNotSupportedError if unsupported) rather than silently applying a
default and potentially misparsing the body.

The version is a constant tied to the request shape we emit, not derived from
the agent's advertised protocol version, because the header must describe the
body we actually send. Extracts applyA2ARequestHeaders and adds a unit test.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@jongio jongio left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the new commit (6c1ea5e). The explicit A2A-Version header addition is correct per spec section 3.2, and centralizing header setup in applyA2ARequestHeaders keeps the call site clean. Test covers all three headers. No issues.

@huimiu huimiu merged commit c4ccbef into main Jul 8, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ext-agents azure.ai.agents extension

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for a2a protocal

7 participants