Skip to content

node: keep the interface path in the regenerated A2A agent card - #677

Merged
aojea merged 2 commits into
google:mainfrom
vbcherepanov:fix/a2a-interface-paths
Oct 12, 2026
Merged

aojea merged 2 commits into
google:mainfrom
vbcherepanov:fix/a2a-interface-paths

Conversation

@vbcherepanov

Copy link
Copy Markdown
Contributor

Fixes #673.

The caller's node set every HTTP interface in the regenerated card to the service root, so an agent that serves JSON-RPC at /invoke (the a2a-go samples) or HTTP+JSON under /a2a/rest was unreachable through the mesh.

  • Provider: A2AService rewrites each JSONRPC or HTTP+JSON interface under target_url to a relative reference (invoke, a2a/rest, or "") when it relays the card.
  • Caller: meshInterfaceURL resolves that reference under the mesh URL. An absolute URL, a URL outside the target path, or a reference with a dot segment, a leading /, a scheme or a host maps to the service root as before, so mixed node versions behave as today and a provider cannot point the caller at another path on the caller's node.

Tests: new unit tests in a2a_service_test.go; TestA2ACUJ now serves the agent at /invoke (404 on main, passes with the change). Through two nodes the A2A TCK (263b9cf, Python reference SUT) goes from 77 to 122 passed. go test -race ./... and the integration suite pass on Linux; golangci-lint reports 0 issues.

The Python and JS SDKs rewrite the card the same way; they are not changed here.

@vbcherepanov
vbcherepanov requested a review from aojea as a code owner October 12, 2026 07:26

@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 updates the A2A service to preserve sub-paths of agent interfaces (such as /invoke or /rest) below the mesh path when regenerating agent cards, rather than pointing all interfaces to the bare service root. It introduces response modification in the reverse proxy to relativize the URLs in the agent card, along with validation to prevent directory traversal or external redirects. Unit and integration tests, as well as documentation, have been updated accordingly. Feedback focuses on aligning with the repository style guide: specifically, logging a warning when skipping relativization due to compressed responses (Section 6) and ensuring test assertions explicitly print both observed and expected values (Section 3).

Comment thread internal/node/a2a_service.go
Comment on lines +153 to 155
if len(card.SupportedInterfaces) != 1 || card.SupportedInterfaces[0].URL != meshBase+"/invoke" {
t.Errorf("interfaces not regenerated / gRPC not dropped: %+v", card.SupportedInterfaces)
}

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.

medium

3. Test coverage must follow the pyramid

The assertion message does not state both the observed and the expected value, which violates the repository style guide (Section 3, Rule 8). Please update the assertion to explicitly print both.

Suggested change
if len(card.SupportedInterfaces) != 1 || card.SupportedInterfaces[0].URL != meshBase+"/invoke" {
t.Errorf("interfaces not regenerated / gRPC not dropped: %+v", card.SupportedInterfaces)
}
if len(card.SupportedInterfaces) != 1 || card.SupportedInterfaces[0].URL != meshBase+"/invoke" {
t.Errorf("interfaces not regenerated / gRPC not dropped: got %+v, want exactly one interface with URL %q", card.SupportedInterfaces, meshBase+"/invoke")
}
References
  1. Section 3, Rule 8: Tests are plain Go on the standard testing package... Flag assertion messages that do not state both the observed and the expected value. (link)

@aojea

aojea commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

need to rebase also we like to have good visibility on errors #677 (comment) otherwise in a p2p network is hard to debug problems, that comment from the bot reviewer is legit

@vbcherepanov
vbcherepanov force-pushed the fix/a2a-interface-paths branch from f921673 to 7390b71 Compare October 12, 2026 08:25
@vbcherepanov

Copy link
Copy Markdown
Contributor Author

Rebased on main. A compressed card is now logged ([A2A] agent card of %q is compressed (%s), relayed with its interface URLs unchanged) instead of being skipped silently, and the integration assertion prints both values. Thanks for the review.

@aojea
aojea force-pushed the fix/a2a-interface-paths branch from 7390b71 to b9857a2 Compare October 12, 2026 09:10
@aojea

aojea commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

I just realized the e2e test added previously with streaming was not exercising the datapath https://github.com/google/agentmesh/compare/7390b7164c778df87855fdf4b423faeadf54472b..b9857a221d98f2e230d6174acc0980837d8d725a

@aojea

aojea commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

sdk/js (rewriteAgentCardinsdk/js/src/libp2p-http.ts) and sdk/python (rewrite_agent_cardinsdk/python/src/agent_mesh/libp2p_http.py) still overwrite iface.url = base`, let's follow up with the same relative subpath resolution in another commit so we merge all fixes together

rewriteAgentCard (JS) and rewrite_agent_card (Python) pointed every
JSONRPC and HTTP+JSON interface at the bare service root, so an agent
whose endpoint is "/invoke" or whose HTTP+JSON binding lives under
"/rest" was unreachable through an SDK member, the same bug the node
fix addresses.

Both SDKs now resolve the relative reference the provider's node writes
under the mesh URL, with the node's rules: anything absolute, rooted,
with a scheme, a host or a dot segment maps to the service root. The
JS and Python tests carry the cases of TestMeshInterfaceURLStaysInService
and TestA2AServiceCardKeepsInterfacePaths.
@vbcherepanov

Copy link
Copy Markdown
Contributor Author

Added the SDK side in the same PR: rewriteAgentCard (JS) and rewrite_agent_card (Python) now resolve the provider's relative reference under the mesh URL with the node's rules, and the JS and Python tests carry the cases of TestMeshInterfaceURLStaysInService and TestA2AServiceCardKeepsInterfacePaths. I also checked both against the Go meshInterfaceURL on 400k random inputs, with no differences. JS 129/129, Python 168 passed on 3.12, go test ./internal/node/ ok. One thing left as before: an SDK member acting as a provider does not relativize its own card yet, so a Go caller still sees the service root for it. Happy to add that in a follow-up if you want it.

@aojea
aojea merged commit 9fa539d into google:main Oct 12, 2026
22 checks passed
@aojea

aojea commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

Thanks

@aojea

aojea commented Oct 12, 2026

Copy link
Copy Markdown
Collaborator

One thing left as before: an SDK member acting as a provider does not relativize its own card yet, so a Go caller still sees the service root for it. Happy to add that in a follow-up if you want it.

sure, remember to add an integration or e2e test that validates the full code path and datapath ... integration test are faster ...

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.

a2a: regenerated agent card drops the interface path, so an agent not served at its root is unreachable

2 participants