Skip to content

fix(a2a): return long_running_tool_ids from the message and artifact update converters - #6989

Open
Yatsuiii wants to merge 2 commits into
google:mainfrom
Yatsuiii:fix/a2a-inbound-long-running-tool-ids
Open

fix(a2a): return long_running_tool_ids from the message and artifact update converters#6989
Yatsuiii wants to merge 2 commits into
google:mainfrom
Yatsuiii:fix/a2a-inbound-long-running-tool-ids

Conversation

@Yatsuiii

@Yatsuiii Yatsuiii commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

_convert_a2a_parts_to_adk_parts recovers long-running function call ids from
the is_long_running part marker for all four inbound converters. Two of them
discard the result before building the Event.

converter passes ids to _create_event
convert_a2a_task_to_event yes
convert_a2a_status_update_to_event yes
convert_a2a_message_to_event no
convert_a2a_artifact_update_to_event no

Both failing sites now keep the ids and pass them positionally, matching the
two that already worked.

Behaviour change

Both are defaults on A2aRemoteAgentConfig. On the new integration extension
path _should_pause_invocation saw an empty long_running_tool_ids and did
not pause, so a long-running call ran on as if it had completed. It now pauses,
matching the legacy event_converter path.

Tests

One parametrized case across all four converters. Without the source change it
fails on message and artifact_update and passes on the other two.
tests/unittests/a2a is green: 460 passed, 48 skipped.

Fixes #6988

…update converters

_convert_a2a_parts_to_adk_parts recovers long-running function call ids
from the is_long_running part marker for all four inbound converters.
convert_a2a_message_to_event and convert_a2a_artifact_update_to_event
discarded that result and never passed it to _create_event, so the
returned Event had long_running_tool_ids=None, while
convert_a2a_task_to_event and convert_a2a_status_update_to_event
populate it from the same marker.

Both are defaults on A2aRemoteAgentConfig, so on the new integration
extension path a long-running call arrived looking complete and
_should_pause_invocation did not pause. Adds coverage across all four
converters.

Fixes google#6988
@Yatsuiii
Yatsuiii force-pushed the fix/a2a-inbound-long-running-tool-ids branch from 0054de6 to f9b865d Compare September 2, 2026 18:03
@mahirhir

mahirhir commented Sep 4, 2026

Copy link
Copy Markdown

The same discard is still on line 510, in convert_a2a_task_to_event, which is one of the four converters your test parametrises:

output_parts, _ = _convert_a2a_parts_to_adk_parts(
    artifact_parts, part_converter
)

The task fixture in _LONG_RUNNING_INBOUND_CONVERTERS builds a Task with a status.message and no artifacts, so it takes the status branch on line 523, which was already keeping the ids before this PR. A Task that carries the long-running call in an artifact never reaches that branch, and that is the same shape you just fixed in convert_a2a_artifact_update_to_event.

There is a second reason to cover it. _create_mock_function_call_for_required_user_input returns early only when long_running_function_ids is non-empty. With the ids dropped on 510 the set is empty, so an input-required task that already carried a genuine long-running call falls into the synthesis path instead of being left alone. If any converted part has text, that part is replaced by a synthetic call under a fresh uuid, and the caller is then holding an id to answer that is not the id of the call that is actually pending. Losing the id and inventing a different one are not the same failure, and the second one is quieter.

The repair looks like the one you already wrote twice: bind the ids at 510 and long_running_function_ids.update(ids) before the status branch adds its own.

convert_a2a_task_to_event has two branches. The status branch already
merged the ids recovered by _convert_a2a_parts_to_adk_parts; the artifact
branch discarded them, so a task carrying the long-running call in an
artifact still lost it.

That also reached _create_mock_function_call_for_required_user_input,
which synthesises a call under a fresh uuid only when no ids survived. An
input-required task with a genuine pending call was handed a fabricated
id rather than its own.

The parametrised task case never covered this: its fixture builds a task
with a status message and no artifacts, so it took the status branch that
was already correct. Adds a task_artifact case that routes through the
artifact branch, and a test pinning that a real call is not replaced.
@Yatsuiii

Yatsuiii commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Good catch, thanks. Confirmed both parts.

Line 510 was a third discard site, and my parametrised task case never reached it. The fixture builds a Task with a status message and no artifacts, so it took the status branch that was already keeping the ids. The case passed without covering the path it was named for.

The synthesis point holds too. With the ids dropped there, an input-required task falls past the early return in _create_mock_function_call_for_required_user_input, and a genuine pending call is replaced by one under a fresh uuid. Quieter than losing the id outright, as you said.

Fixed in 2a32cc9, same shape as the other three sites. Added a task_artifact case that routes a Task through the artifact branch, plus a test pinning that a real call is not replaced. Both fail without the change; tests/unittests/a2a is green at 462 passed.

@llalitkumarrr flagging that the commit you tested was missing this third site, so it is worth re-running before review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants