Skip to content

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

Open
PratikDhanave wants to merge 1 commit into
microsoft:mainfrom
PratikDhanave: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
PratikDhanave: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
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 83ddec6 to 97125b2 Compare July 22, 2026 04:07
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.

2 participants