Skip to content

feat: A2A Protocol v1.0 compliance with backward compat for v0.3 agents - #145

Merged
darrelmiller merged 8 commits into
a2aproject:mainfrom
zeroasterisk:feat/a2a-v1
Aug 7, 2026
Merged

feat: A2A Protocol v1.0 compliance with backward compat for v0.3 agents#145
darrelmiller merged 8 commits into
a2aproject:mainfrom
zeroasterisk:feat/a2a-v1

Conversation

@zeroasterisk

Copy link
Copy Markdown
Contributor

Summary

The A2A Inspector was frozen at A2A protocol v0.3. This PR migrates to v1.0 (a2a-sdk 1.0.0a0) while maintaining backward compat with v0.3 agents.

Breaking Changes in A2A v1.0 (addressed)

Method names → PascalCase

  • message/send → SendMessage, tasks/get → GetTask, tasks/cancel → CancelTask, etc.

Part type restructure

  • v0.3: TextPart/FilePart/DataPart discriminated union
  • v1.0: flat protobuf Part with oneof (text, raw, url, data)
  • Frontend processPart() handles both formats

AgentCard restructure

  • v0.3: top-level url + preferredTransport
  • v1.0: supportedInterfaces array
  • Validators accept either format

TaskStatusUpdateEvent: final field removed

  • v1.0 drops the final boolean; validators updated accordingly

TaskState → SCREAMING_SNAKE_CASE

  • v0.3: 'working', 'completed' / v1.0: 'TASK_STATE_WORKING', 'TASK_STATE_COMPLETED'
  • Backend normalizes; frontend normalizeTaskState() handles display

Role → SCREAMING_SNAKE_CASE

  • v0.3: 'agent' / v1.0: 'ROLE_AGENT' (or int 2)
  • Validators accept all variants

SDK API changes

  • ClientConfig: supported_transports → supported_protocol_bindings
  • client.send_message(message) → client.send_message(SendMessageRequest(request=message))
  • ClientEvent: now (StreamResponse, Task|None); unwrap via WhichOneof
  • Serialization: model_dump() → MessageToDict()

Files Changed

  • backend/app.py: full v1.0 migration with v0.3 fallback shims
  • backend/validators.py: dual-format validation (v0.3 + v1.0)
  • frontend/src/script.ts: v1.0 Part handling + TaskState normalization
  • pyproject.toml: a2a-sdk[all]>=1.0.0a0

Tests

49 tests pass (all existing + 12 new v1.0-specific tests)

## A2A Protocol v1.0 Compliance Audit & Migration

### Breaking changes addressed

**SDK upgrade**
- Bump a2a-sdk[all] from >=0.3.10 to >=1.0.0a0

**JSON-RPC method names (PascalCase)**
- v0.3: message/send, tasks/get, tasks/cancel, message/stream, etc.
- v1.0: SendMessage, GetTask, CancelTask, SendStreamingMessage, etc.
- Backend debug_log now shows 'SendMessage' (v1.0 PascalCase)

**Part type restructure**
- v0.3: TextPart(text=), FilePart(file=FileWithBytes(...)), DataPart(data=...)
  wrapped in a discriminated union Part(root=...)
- v1.0: Flat protobuf Part with oneof content: text=, raw=bytes, url=, data=
- Backend: _make_text_part / _make_file_part helpers handle both versions
- Frontend processPart: handles v0.3 {file:{bytes,uri,mimeType}} AND
  v1.0 {url, mediaType} / {raw, mediaType} flat format

**AgentCard restructure**
- v0.3: top-level 'url', 'preferredTransport', 'additionalInterfaces'
- v1.0: 'supportedInterfaces' array replaces url+preferredTransport
- Backend: _get_transport_from_card() and _get_agent_card_dict() normalize
- Validators: accept either 'url' (v0.3) or 'supportedInterfaces' (v1.0)

**TaskStatusUpdateEvent**
- v1.0 removes the 'final' boolean field (was in v0.3)
- Validators updated: 'final' is no longer validated or required

**TaskState values**
- v0.3: lowercase strings ('working', 'completed', 'canceled', etc.)
- v1.0: SCREAMING_SNAKE_CASE ('TASK_STATE_WORKING', 'TASK_STATE_COMPLETED', etc.)
  or integer enum values
- Backend: _normalize_task_state() converts v1.0 → lowercase for frontend
- Frontend: normalizeTaskState() handles display, TASK_STATE_DISPLAY map

**Role values**
- v0.3: Role.user ('user' string)
- v1.0: Role.ROLE_USER (int 1), serializes as 'ROLE_USER' in JSON
- Validators: accept 'agent', 'ROLE_AGENT', or 2 as valid agent roles
- Frontend types updated to accept TaskState as string (both formats)

**ClientConfig API**
- v0.3: ClientConfig(supported_transports=[...])
- v1.0: ClientConfig(supported_protocol_bindings=[...])
- TransportProtocol enum: v0.3 lowercase (.jsonrpc), v1.0 UPPER (.JSONRPC)
- _make_client_config() tries v1.0 first, falls back to v0.3

**Serialization**
- v0.3: Pydantic model_dump(exclude_none=True)
- v1.0: Protobuf MessageToDict(preserve_proto_field_name=False) (camelCase)
- _to_dict() helper handles both

**ClientEvent / StreamResponse**
- v0.3: ClientEvent = Message | (TaskStatusUpdateEvent | TaskArtifactUpdateEvent, Task)
- v1.0: ClientEvent = (StreamResponse, Task | None)
  StreamResponse has oneof: task, message, status_update, artifact_update
- _process_a2a_response() unwraps both formats
- 'kind' field synthesized from class name for v1.0 protobuf events

### New tests (49 total, all passing)
- TestValidateAgentCardV10: v1.0 card validation
- test_valid_task_v10: SCREAMING_SNAKE_CASE state accepted
- test_valid_status_update_v10: no 'final' field required
- test_status_update_no_final_field_required: explicit regression
- test_valid_message_v10_role_enum: ROLE_AGENT accepted
- test_valid_message_v10_role_int: int role (2) accepted

### Frontend changes
- processPart(): handles v1.0 {url, raw, mediaType, filename} Part fields
- normalizeTaskState(): TASK_STATE_* → display name
- status-update handler: shows state label even with no message parts
- message handler: renders all parts (not just first text part)
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the A2A Inspector to align with version 1.0 of the A2A Protocol. The primary goal was to integrate the new protocol specifications while meticulously preserving full backward compatibility for existing v0.3 agents. This involved extensive refactoring in both the backend and frontend to adapt to changes in data structures, serialization, and API calls, ensuring a smooth transition and continued functionality across different protocol versions.

Highlights

  • A2A Protocol v1.0 Compliance: Migrated the backend and frontend to comply with A2A Protocol v1.0, updating data structures, method names, and serialization logic.
  • Backward Compatibility: Implemented shims and helper functions in the backend to ensure backward compatibility with v0.3 agents, allowing the system to process both old and new protocol versions seamlessly.
  • Dual-Format Validation: Updated validators to support both v0.3 (Pydantic-based) and v1.0 (protobuf-based) formats for AgentCard and message types, including handling new SCREAMING_SNAKE_CASE enum values.
  • Frontend Adaptations: Modified the frontend to correctly display and process A2A v1.0 Part formats (flat protobuf with oneof) and normalize v1.0 TaskState values for consistent user experience.
  • Dependency Updates: Updated the a2a-sdk dependency to version 1.0.0a0 and refreshed lock files (bun.lock, uv.lock) to reflect new and updated Python and Node.js packages.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@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

The pull request successfully migrates the A2A Inspector to protocol v1.0 while maintaining backward compatibility with v0.3 agents. The changes are well-structured, utilizing helper functions to encapsulate version-specific logic in the backend and updating the frontend to handle new data structures and naming conventions. The test suite has also been expanded to cover the new v1.0 formats, which is a good practice for ensuring robustness.

Comment thread backend/app.py Outdated
Comment thread backend/app.py Outdated
Comment thread backend/app.py Outdated
Comment thread backend/app.py Outdated
Comment thread backend/app.py Outdated
Comment thread frontend/src/script.ts Outdated
Comment thread backend/validators.py
- _to_dict: raise TypeError instead of broad dict() fallback
- Narrow except Exception to specific types (TypeError, AttributeError, ImportError)
- Replace A2APart Record<string, any> with proper interface definition
- Fix event typing from Any to object
- Update validator error message for protocolBinding vs transport
@zeroasterisk

Copy link
Copy Markdown
Contributor Author

@lkawka — could you review this when you get a chance? It brings the inspector up to A2A Protocol v1.0 compliance (PascalCase methods, protobuf Part types, AgentCard restructure, SCREAMING_SNAKE_CASE enums) while keeping backward compat with v0.3 agents. Just pushed a follow-up commit addressing the Code Assist feedback (narrower exception handling, better typing). Thanks!

- _get_transport_from_card: wrap in try/except to handle v0.3 cards
  that don't have supported_interfaces; fall back to preferred_transport
  (fixes 'AttributeError: transport' on client init)

- _send_message_compat: fix SendMessageRequest field name from 'request'
  to 'message' (the actual protobuf field name in v1.0.0a0);
  add ValueError to caught exceptions since protobuf raises ValueError
  for unknown field names, not TypeError/AttributeError
  (fixes 'Failed to send message: configuration' on every chat message)
@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Follow-up: found 2 critical runtime bugs during live testing with the helloworld sample agent (connecting to a v0.3 agent + sending messages both failed). Fixed in commit 0f17a79:

  1. AttributeError: transport on connect_get_transport_from_card() accessed card.supported_interfaces[0].transport but v0.3 cards have preferred_transport at the top level, not a supported_interfaces list. Wrapped in try/except with proper fallback.

  2. AttributeError: configuration on sendSendMessageRequest in v1.0.0a0 uses field name message= not request= (pre-alpha naming). Also, the SDK raises ValueError (not TypeError/AttributeError) for unknown protobuf field names, so that needed to be caught too.

With these fixes, end-to-end testing works: helloworld agent connects, agent card validates as ✅, chat sends messages, and "Hello World" comes back correctly.

@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Testing Summary

Automated Tests

  • 49 tests passing (37 existing + 12 new v1.0-specific)
  • Tests cover both v0.3 and v1.0 wire formats for AgentCard validation, Part rendering, TaskState/Role normalization

Live End-to-End Testing (Headless Chromium + Playwright)

Ran the full inspector against the helloworld sample agent from a2a-samples (v0.3 agent on a2a-sdk>=0.3.0):

Step Result
Load inspector at localhost:5001 ✅ Page loads, all UI elements present
Enter agent URL localhost:9999 ✅ Input accepts URL
Click Connect ✅ Agent card fetched and displayed
Agent card validation ✅ "Agent card is valid" (green)
supportedInterfaces normalization ✅ v0.3 preferredTransport correctly mapped
Send "hello" message ✅ Message appears in chat
Receive agent response ✅ "Hello World" returned with ✅ compliant badge
Debug console ✅ Shows outbound JSON-RPC with correct SendMessage method

Bugs Found & Fixed During Testing

Three critical runtime bugs were discovered that only surfaced during live E2E testing (not caught by unit tests):

  1. AttributeError: transport on connect (commit 0f17a79)

    • _get_transport_from_card() assumed card.supported_interfaces[0].transport existed, but v0.3 cards use preferredTransport at the top level
    • Fix: wrapped in try/except with getattr for protocol_bindingtransportpreferred_transport fallback chain
  2. ValueError: SendMessageRequest has no "request" field (commit 0f17a79)

    • The protobuf field name in v1.0.0a0 is message=, not request= (pre-alpha naming)
    • Fix: changed SendMessageRequest(request=message)SendMessageRequest(message=message)
  3. AttributeError: configuration on send (commit 0f17a79)

    • The compat shim caught TypeError/AttributeError but protobuf raises ValueError for unknown fields
    • Fix: added ValueError to the caught exception types

Pre-existing UX Issues Noted (not addressed in this PR)

  • Chat area shows no placeholder before first message
  • Unlabeled "+" button next to message input
  • Debug console only shows outbound requests, not inbound responses
  • Error responses can be marked with ✅ compliant badge (confusing)

These are pre-existing issues unrelated to the v1.0 migration.

- Error responses now show ❌ instead of ✅ compliant badge
  (errors can be protocol-compliant but marking them with a green
  checkmark is confusing for users)
- Attach button: changed from '+' to 📎 icon, added aria-label
- Chat empty state: improved placeholder text with 💬 emoji and
  clearer wording about chatting with the agent
- Added .validation-status.error CSS class for error badge styling

Closes a2aproject#146
@bookernath

Copy link
Copy Markdown

Just putting a comment in support of this work - having a validator for the new 1.0 spec, especially an official one from the project itself, would inspire a lot more confidence as the community updates their implementations.

@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Just putting a comment in support of this work - having a validator for the new 1.0 spec, especially an official one from the project itself, would inspire a lot more confidence as the community updates their implementations.

Thanks @bookernath . Did you know that we have an https://github.com/a2aproject/a2a-tck from the Red Hat team who own the Java SDK, which has a dev branch for v1.0, and we have an https://github.com/a2aproject/a2a-samples/tree/main/itk from Google team owning some of the SDKs.

We are in a period flux right now, migrating to 1.0 and making sure 0.3 is backwards compatible at the SDK level, but the spec is rock solid and these other tools like this inspector will be brought up to speed soon.

@liujuanjuan1984

Copy link
Copy Markdown
Contributor

Thanks for putting this PR together.

I’m currently hoping to use an a2a-inspector that supports a2a-python 1.0+, and I noticed this PR appears to be the main effort in that direction.

I also saw that it is currently based on a2a-sdk>=1.0.0a0, while the Python SDK has now moved on to stable 1.0.x releases. I just wanted to check whether this PR is still the preferred route for bringing 1.0+ support to the inspector, and whether a refresh against the latest stable SDK would be useful.

If that would help, I’d be glad to contribute.

@liujuanjuan1984

liujuanjuan1984 commented May 1, 2026

Copy link
Copy Markdown
Contributor

@zeroasterisk @lkawka

I have now prepared the follow-up stable SDK refresh here:

It is now ready for review on top of feat/a2a-v1, and it updates the work from the a2a-sdk>=1.0.0a0 alpha line to stable a2a-sdk 1.0.2, with the minimum compatibility fixes and regression coverage needed for that path.

If continuing from #145 is still the preferred route, I would appreciate any feedback on that follow-up PR when convenient.

@bookernath

Copy link
Copy Markdown

@zeroasterisk I see you have an approval, do you plan to merge soon? I'd love to have this and I'm sure others in the community would love to see the fruits of your labor as well

@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Pushed 5d1325a — applied ruff format to the 3 backend files that were failing ruff format --check (backend/app.py, validators.py, tests/test_validators.py; formatting-only, no logic change). ruff format --check . and ruff check . both pass clean locally now. Should clear the Lint Code Base job once the workflow run is approved. Thanks!

@zeroasterisk

Copy link
Copy Markdown
Contributor Author

Review follow-up + rebase status

Rebase: feat/a2a-v1 is already current with upstream/main (8098818 is an ancestor of the branch head 5d1325a), so no rebase/merge commits were needed — the branch applies cleanly on top of main.

All local gates green (Python 3.13, uv):

  • ruff format --check . → 5 files already formatted
  • ruff check . → All checks passed
  • mypy . → Success: no issues found in 5 source files
  • pytest49 passed
  • Frontend: tsc compile clean, vitest74 passed, esbuild build OK
  • Live smoke test: app boots, GET / → 200 (HTML served), POST /agent-card → 400 with proper error JSON

All CI checks passing: Lint Code Base ✓, Test (3.12) ✓, Test (3.13) ✓, Validate PR Title ✓

Review comments addressed (all were already resolved in earlier commits 5890c63/0f17a79; I've now marked the threads resolved):

  1. _to_dict fallback too broad (backend/app.py) — now raises TypeError for unsupported types instead of a blind dict(obj); handles protobuf (DESCRIPTOR), Pydantic (model_dump), and plain dict.
  2. event: Any typing (backend/app.py) — replaced with event: object plus a comment documenting the v0.3/v1.0 union it unwraps.
  3. Broad except Exception in _make_text_part — narrowed to except (TypeError, AttributeError).
  4. Broad except Exception in _make_file_part — narrowed to except (TypeError, AttributeError).
  5. Broad except Exception in _send_message_compat — narrowed to except (TypeError, AttributeError, ValueError). Note: ValueError is required because the v1.0.0a0 SDK raises it for unknown protobuf field names (real runtime fix from live testing).
  6. A2APart = Record<string, any> (frontend/src/script.ts) — replaced with a documented interface A2APart capturing both v0.3 (text/file/data) and v1.0 flat protobuf (text/url/raw/mediaType/filename) shapes, with a typed catch-all for forward-compat.
  7. supportedInterfaces transport/protocolBinding validation (backend/validators.py) — error message now names protocolBinding as the canonical v1.0 field with transport flagged as the legacy alias.

mergeStateStatus is now CLEAN (resolving the open review threads cleared the conversation-resolution gate). Ready to merge. Thanks!

@zeroasterisk

Copy link
Copy Markdown
Contributor Author

I don't seem to have merge rights, @darrelmiller ?

@ksolo

ksolo commented Jun 10, 2026

Copy link
Copy Markdown

@darrelmiller Will this be merged soon? I'm preparing a talk on A2A for a user group and wanted to show the inspector as part of the demo with an A2A agent on V1.

@s-celles

Copy link
Copy Markdown

An independent data point in support of this PR, in case it helps prioritise it.

I hit exactly the AgentCard restructure breakage listed above while pointing the inspector (main) at a third-party A2A v1.0 agent — one built on the official Go SDK (a2a-go/v2@v2.3.1), not on any of the in-repo samples. The inspector refuses to connect, at card validation:

Failed to validate agent card structure from http://…/.well-known/agent-card.json:
[{"type":"missing","loc":["url"],"msg":"Field required",
  "input":{"supportedInterfaces":[{"url":"http://…/a2a",
            "protocolBinding":"JSONRPC","protocolVersion":"1.0"}], …}}]

The card is v1.0-correct — specification/a2a.proto has no top-level url on AgentCard, only supported_interfaces — and it is accepted by the official Python SDK at v1.0 (a2a-sdk 1.1.0), whose A2ACardResolver parses it without error and drives the agent end to end (SendMessage, streaming, artifacts, GetTask, CancelTask). So the disagreement is squarely the one this PR fixes: main still resolves a2a-sdk>=0.3.10, whose AgentCard model has url as a required field and no supportedInterfaces at all.

The practical effect today is that the inspector cannot be used to debug any v1.0 agent, whatever SDK it was written with — connection fails before the chat or the compliance checks are ever reached. That also makes it hard for people to tell a genuine non-compliance in their own agent from the tool being a protocol version behind: the error message reads as "your card is invalid", which is the opposite of what is happening. It is plausible that #119 and #104 are the same root cause seen from further downstream.

Nothing to act on beyond what this PR already does — just confirming the breakage is not limited to the sample agents, and that it is now reachable by anyone building against the current specification.

Prepared with AI assistance; the error output above is verbatim, and each claim was verified against the pinned SDK versions.

@ksolo

ksolo commented Jul 16, 2026

Copy link
Copy Markdown

@allen-stephen Can you review this one and merge if things look good? Is there an alternate path for v1 coming?

Zaf and others added 3 commits July 29, 2026 11:03
- Fixes Ruff and Mypy errors in backend/app.py and backend/validators.py.
- Resolves issues around multi-line imports and version-specific type fallbacks.
…ponse handling)

- Bump a2a-sdk[all] from >=1.0.0a0 (alpha) to >=1.1.2 (stable)
- Add explicit fastapi>=0.115.0 dependency (no longer transitive in stable)
- Remove ClientEvent import (removed in stable SDK)
- Extract _unwrap_stream_response() helper to handle all SDK variants:
  * stable 1.0.x+: StreamResponse yielded directly from send_message()
  * v1.0 alpha: tuple[StreamResponse, Task|None]
  * v0.3: tuple[TaskStatusUpdateEvent|TaskArtifactUpdateEvent, Task] | Message
- Fix _process_a2a_response() to route through new helper (reduces branch count)
- All 49 tests pass; ruff + mypy clean
Remove the redundant 'if Part is not None' checks in _make_text_part and _make_file_part, resolving pyright type narrowing issues where Part is incorrectly inferred as None.
@darrelmiller
darrelmiller merged commit 2eb9306 into a2aproject:main Aug 7, 2026
4 checks passed
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.

6 participants