Skip to content

fix(a2a): keep metadata-only messages in convert_a2a_message_to_event - #7705

Open
danieldutu wants to merge 1 commit into
google:mainfrom
danieldutu:fix/a2a-keep-metadata-only-events
Open

danieldutu wants to merge 1 commit into
google:mainfrom
danieldutu:fix/a2a-keep-metadata-only-events

Conversation

@danieldutu

Copy link
Copy Markdown

Link to Issue

Fixes #5185

Problem

convert_a2a_message_to_event returns None for an A2A message with parts=[] and only restored metadata. On main (f08f9d6), a message carrying adk_custom_metadata={"flag": True} was dropped in 1 of 1 runs, and the peer's custom_metadata, usage_metadata, error_code, grounding_metadata and citation_metadata were lost with it. _create_event (to_adk_event.py:215-220) returns None when there are no parts and no actions. The message converter extracts the metadata fields at :656 but does not mark them as content.

Solution

convert_a2a_message_to_event now passes allow_empty_event=any(metadata_fields.values()). The event keeps content=None and carries the restored metadata. Peer actions stay filtered by _PEER_SETTABLE_ACTION_FIELDS, so the untrusted-metadata rule is unchanged.

Relation to earlier PRs: #5222 (closed 2026-05-12, formatting requested) and #5205 (closed 2026-08-12 as superseded) both changed _create_event for all callers and added their own metadata extraction. Main now restores the metadata fields through _extract_genai_metadata, so this change adds only the missing message-path condition.

I did not change _create_event for all callers. That variant fails 3 cases of test_contentless_progress_does_not_emit_a_final_response: a content-less event on task and status-update paths counts as a final response. I can widen the change to those paths if you prefer, together with a change to that test.

Testing Plan

Unit tests:

uv run pytest tests/unittests/a2a/converters/test_to_adk.py
98 passed, 1 warning
uv run pytest tests/unittests/a2a -n 2
949 passed, 57 skipped, 28 warnings

The new test test_convert_a2a_message_to_event_returns_metadata_only_event fails on main (1 failed, 97 passed) and passes with the change.

E2E log, repro from the issue (PYTHONPATH=src uv run python repro5185.py):

before: event: None
after:  event: ... content=None ... custom_metadata={'flag': True} ...

uv run pre-commit run --files <touched files>: pyink, addlicense, ADK compliance checks, doc links, codespell passed.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas. (one-line change; no new logic needing comments)
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules. (none)

Not covered: long_running_tool_ids is not read from message-level metadata. GWeale asked for a separate issue. The task and status-update converters still drop metadata-only progress, as the existing tests require. Tested with a2a protobuf 1.x only; the 0.3.x path was not run.

A2A message with parts=[] and adk_custom_metadata came back as None
(1 of 1 repro runs on f08f9d6). The peer's custom_metadata, usage_metadata,
error_code, grounding_metadata and citation_metadata were lost.

_create_event (to_adk_event.py:215-220) returns None when there are no
parts and no actions. convert_a2a_message_to_event extracted the metadata
fields but never told _create_event that they count as content.

The message converter now passes allow_empty_event=True when any extracted
metadata field is set. The event keeps content=None. The task and status
update converters are unchanged: test_contentless_progress_does_not_emit_a_final_response
requires metadata-only progress to stay dropped on those paths.

Evidence: the new test fails on main (1 failed, 97 passed) and passes
with the change (98 passed). tests/unittests/a2a: 949 passed, 57 skipped.

Fixes google#5185

@BichengWang BichengWang 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.

Validated at 667a8a1a4cf3bb2e95ad2a00139558cab25d53fd. The new regression fails on current main and passes here. The A2A suite passes locally: 949 passed, 57 skipped; changed-file pre-commit checks pass.

I also checked each restored metadata field with native and JSON-string inputs, including zero token usage, plus empty messages and rejected peer state/transfer actions. The message-only condition preserves those fields without changing task/status progress filtering. I found no blocking issue in this change. Validation used Python 3.11 and a2a-sdk 1.2.3; I did not run the 0.3 SDK path or full tox matrix.

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 inbound converters do not restore custom_metadata and drop metadata-only events

2 participants