Skip to content

Surface Status.Message text from non-streaming A2A task responses - #552

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:fix-a2a-nonstreaming-status-message
Open

Surface Status.Message text from non-streaming A2A task responses#552
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanaveFork:fix-a2a-nonstreaming-status-message

Conversation

@PratikDhanave

Copy link
Copy Markdown
Contributor

The streaming path already extracts Status.Message text for input-required and terminal task states — added deliberately (TestRunStreamingWithTaskStatusUpdateEvent_WithMessage, mirroring .NET microsoft/agent-framework#6043):

case *a2a.TaskStatusUpdateEvent:
    if e.Status.Message != nil {
        messageID = e.Status.Message.ID
        if e.Status.State == a2a.TaskStateInputRequired || e.Status.State.Terminal() {
            contents, err = partsToContents(e.Status.Message.Parts, nil)
            ...

But yieldTask — the *a2a.Task object path used by non-streaming SendMessage and the GetTask continuation — built its update only from task.Artifacts and never read task.Status.Message:

var contents []message.Content
for _, artifact := range task.Artifacts {
    contents, err = partsToContents(artifact.Parts, contents)
    ...
}
update := newResponseUpdate(task, task.Metadata, string(task.ID), "", message.RoleAssistant, contents, timestamp)

Per the A2A spec a server may return a Task (not a Message) from SendMessage/GetTask, and TaskStatus.Message is the natural place for an input-required follow-up question or a terminal summary when there are no artifacts. In that case the non-streaming caller got an update with empty contents — the agent’s question was silently lost — while the exact same logical response over streaming surfaced it. A pure streaming-vs-non-streaming parity gap.

Fix

Mirror the streaming branch in yieldTask: set the message ID from Status.Message and, for input-required/terminal states, extract its parts before appending any artifact contents.

Test

TestRunWithInputRequiredTaskMessage returns a non-streaming *a2a.Task in InputRequired state whose Status.Message holds a question and no artifacts, then asserts the response text equals the question. It fails on the old code (empty) and passes with the fix. Full package tests remain green (no existing *a2a.Task test exercised Status.Message).

Copilot AI review requested due to automatic review settings July 19, 2026 10:38
@PratikDhanave
PratikDhanave requested a review from a team as a code owner July 19, 2026 10:38

Copilot AI 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.

Pull request overview

This PR improves response fidelity and usage accounting in provider implementations: it makes non-streaming A2A task responses surface Status.Message text (matching the streaming path), and it extends Gemini usage mapping to include reasoning/thought token counts.

Changes:

  • A2A provider: in non-streaming *a2a.Task responses, extract Status.Message parts for InputRequired and terminal task states and propagate the MessageID.
  • A2A provider: add a regression test covering an InputRequired task whose response is carried only in Status.Message.
  • Gemini provider: map thoughtsTokenCount to UsageDetails.ReasoningTokenCount and add a test to verify the mapping.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
provider/geminiprovider/agent.go Adds Gemini usage mapping for reasoning/thought token counts.
provider/geminiprovider/agent_test.go Adds coverage ensuring thoughtsTokenCount is surfaced as ReasoningTokenCount.
provider/a2aprovider/a2a.go Updates non-streaming task handling to include Status.Message content and message ID for input-required/terminal states.
provider/a2aprovider/a2a_test.go Adds regression test verifying Status.Message text is returned for non-streaming input-required tasks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread provider/geminiprovider/agent.go
@PratikDhanave
PratikDhanave force-pushed the fix-a2a-nonstreaming-status-message branch 3 times, most recently from 0636dbe to f7fa87f Compare July 23, 2026 15:44
@github-actions github-actions Bot added the parity-approved Go API consistency review found no parity issues label Jul 23, 2026
@github-actions

This comment has been minimized.

@PratikDhanave
PratikDhanave force-pushed the fix-a2a-nonstreaming-status-message branch from f7fa87f to 5acf6e9 Compare July 24, 2026 01:42
@github-actions

This comment has been minimized.

yieldTask, which handles the *a2a.Task object path used by non-streaming
SendMessage and the GetTask continuation, built its ResponseUpdate only
from task.Artifacts and ignored task.Status.Message. When an A2A server
returns a Task in an input-required or terminal state that carries its
text in Status.Message with no artifacts (e.g. a follow-up question), the
text was silently dropped.

The streaming TaskStatusUpdateEvent path already extracts Status.Message
for these states. Mirror that in yieldTask so the streaming and
non-streaming paths agree.
@PratikDhanave
PratikDhanave force-pushed the fix-a2a-nonstreaming-status-message branch from 5acf6e9 to 064bdd2 Compare July 24, 2026 09:37
@github-actions

Copy link
Copy Markdown
Contributor

Parity Review — PR #552

Scope: Internal A2A provider fix — yieldTask (unexported function) in provider/a2aprovider/a2a.go.

Change summary: Surfaces task.Status.Message content in non-streaming A2A responses for InputRequired and Terminal task states, matching the already-correct streaming branch in the same file.

No exported Go API changes — no public-api-change label needed.

Cross-repo parity: The PR explicitly references .NET upstream issue microsoft/agent-framework#6043 as the source of truth. The fix aligns Go behavior with both the A2A spec (§ TaskStatus.Message) and the .NET implementation. The streaming vs. non-streaming parity gap closed here mirrors an equivalent fix upstream.

Anthropic/OpenAI diffs: These appear in git diff main..pr-552 only because the PR branch has not been rebased onto the main commit that introduced stream-close calls (PR #718). They are not part of this PR's semantic change.

No parity issues found. The parity-approved label is correct and should remain.

Warning

Firewall blocked 1 domain

The following domain was blocked by the firewall during workflow execution:

  • awmgmcpg

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "awmgmcpg"

See Network Configuration for more information.

Generated by Go API Consistency Review Agent · 31.4 AIC · ⌖ 4.92 AIC · ⊞ 5.9K ·

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

Labels

parity-approved Go API consistency review found no parity issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants