sdk: rewrite the card of an agent behind a node for the mesh - #556
Merged
Merged
Conversation
A stock A2A client on session.fetch() or MeshTransport follows the URL in the agent card. An agent behind a sam-node serves a card naming its own address, so the client left the mesh and the SDK refused the URL. sam-node's egress proxy impersonates that card endpoint for its own callers. The SDKs now do the same at their /libp2p-http client: a GET of /a2a/<name>/.well-known/agent-card.json or of the bare service root is held, the SDK fetches the card itself at the well-known path with identity encoding, and answers with it regenerated. The rewrite points every HTTP interface at the mesh URL, drops gRPC interfaces and signatures, and answers 502 for a card that is not JSON or has no interface left; the agent's own non-200 is relayed as it is. Streaming stays as the agent declares it, unlike the node's rewrite, since this transport streams and SDK-hosted agents advertise it.
Contributor
There was a problem hiding this comment.
Code Review
This pull request implements agent card rewriting in both the JavaScript and Python SDKs, aligning their behavior with sam-node's egress proxy. When a client requests an agent's card (at .well-known/agent-card.json or the service root), the SDK fetches the card, rewrites its supported interfaces to point to the mesh URL, filters out unsupported protocols like gRPC, and strips signatures. This allows stock A2A clients to bootstrap seamlessly. The changes include comprehensive unit and integration tests in both JS and Python, as well as updates to the documentation. I have no feedback to provide as there are no review comments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A stock A2A client used over an SDK session (
session.fetch()in JS,MeshTransportin Python) fails against an agent hosted behind asam-node. The agent's card names its own address, the client sends every request there, and the SDK refuses a URL that is not a mesh URL.sam-node's egress proxy regenerates the card for its callers (a2aServeAgentCard); the SDKs never did.Both SDKs now do at their
/libp2p-httpclient whata2aServeAgentCarddoes: a GET of/a2a/<name>/.well-known/agent-card.jsonor of the bare service root is held, the SDK fetches the card itself at the well-known path with identity encoding (httpx sendsaccept-encoding: gzip, deflateby default, and the node deletes it for the same reason), and answers with the card regenerated asregenerateAgentCardForMeshdoes. The rewrite points every HTTP interface at the mesh URL, drops gRPC interfaces and signatures, and answers 502 for a card that is not JSON or has no interface left. The agent's own non-200 is relayed as it is.session.request()and the transports share that path, so a raw GET reads the same card.Scope
rewriteAgentCard,AGENT_CARD_PATHandserveAgentCardbehindfetchOverStream(sdk/js/src/libp2p-http.ts), the first two exported from the package.rewrite_agent_card,AGENT_CARD_PATH,mesh_urland_serve_agent_cardbehindopen_http_request(sdk/python/src/agent_mesh/libp2p_http.py);MESH_PATH_PREFIXmoves there fromhttpx_transport.py, which re-exports it;MeshSession.mesh_urldelegates.TestNativeSDKsMeshhosts a stock a2a card behind the fixture node and asserts each SDK member reads it rewritten.sdk/README.md,site/content/docs/guides/native-sdks.md.Tradeoffs
capabilities.streamingoff because its egress proxy is unverified for SSE; this transport streams bodies (unit-tested), and SDK-hosted agents advertise streaming, so forcing it off would regress SDK-to-SDK calls./a2a/<name>as a synthetic path for stream-reset checks moved to/inference/<name>.Blast Radius
Every card read through an SDK session, including cards of SDK-hosted agents, passes through the rewrite. For those the interface URL is already the mesh URL, so the rewrite is a no-op apart from dropping signatures. The stock A2A example clients in both languages still pass against each other's agents.
Verification
TestNativeSDKsMeshfailed before the fix with both members returninghttp://127.0.0.1:7777/and the gRPC interface, and passes after; the wholeTestNativeSDKfamily passes with both toolchains present.npm testinsdk/js: 90 passed.pytest sdk/python/tests: 101 passed. The unit tests cover the root path, the relayed 404, the 502 for a gRPC-only card, and that the client'saccept-encodingnever reaches the agent.make lintandgen-sdk-docs -checkpass.