From 064bdd2e8b7d9f72fe3cc337bad684e860d7beef Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Sun, 19 Jul 2026 16:08:12 +0530 Subject: [PATCH] Surface Status.Message text from non-streaming A2A task responses 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. --- provider/a2aprovider/a2a.go | 18 +++++++++++++++- provider/a2aprovider/a2a_test.go | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/provider/a2aprovider/a2a.go b/provider/a2aprovider/a2a.go index 110be087..aa1de622 100644 --- a/provider/a2aprovider/a2a.go +++ b/provider/a2aprovider/a2a.go @@ -264,6 +264,22 @@ func yieldTask(yield func(*agent.ResponseUpdate, error) bool, task *a2a.Task) bo timestamp = *task.Status.Timestamp } var contents []message.Content + messageID := "" + if task.Status.Message != nil { + messageID = task.Status.Message.ID + // Mirror the streaming TaskStatusUpdateEvent path: surface the status + // message text for states where it carries the agent's response (an + // input-required follow-up question or a terminal summary), rather than + // dropping it when the task has no artifacts. + if task.Status.State == a2a.TaskStateInputRequired || task.Status.State.Terminal() { + var err error + contents, err = partsToContents(task.Status.Message.Parts, contents) + if err != nil { + yield(nil, err) + return false + } + } + } for _, artifact := range task.Artifacts { var err error contents, err = partsToContents(artifact.Parts, contents) @@ -273,7 +289,7 @@ func yieldTask(yield func(*agent.ResponseUpdate, error) bool, task *a2a.Task) bo } } - update := newResponseUpdate(task, task.Metadata, string(task.ID), "", message.RoleAssistant, contents, timestamp) + update := newResponseUpdate(task, task.Metadata, string(task.ID), messageID, message.RoleAssistant, contents, timestamp) update.ContinuationToken = continuationToken return yield(update, nil) } diff --git a/provider/a2aprovider/a2a_test.go b/provider/a2aprovider/a2a_test.go index 0b47df58..7bafe209 100644 --- a/provider/a2aprovider/a2a_test.go +++ b/provider/a2aprovider/a2a_test.go @@ -921,6 +921,41 @@ func TestRunWithAgentTaskResponse(t *testing.T) { } } +// TestRunWithInputRequiredTaskMessage verifies that a non-streaming Task response +// in an InputRequired state surfaces the text carried in Status.Message (when the +// task has no artifacts), matching the streaming TaskStatusUpdateEvent path. +func TestRunWithInputRequiredTaskMessage(t *testing.T) { + const question = "What color should the background be?" + transport := &mockA2ATransport{ + responseToReturn: &a2a.Task{ + ID: a2a.TaskID("task-1"), + ContextID: "context-1", + Status: a2a.TaskStatus{ + State: a2a.TaskStateInputRequired, + Message: &a2a.Message{ + ID: "msg-1", + Role: a2a.MessageRoleAgent, + Parts: a2a.ContentParts{a2a.NewTextPart(question)}, + }, + }, + }, + } + a := newTestAgent(transport, agent.Config{}) + + session, err := a.CreateSession(t.Context()) + if err != nil { + t.Fatal(err) + } + + result, err := a.RunText(t.Context(), "make the background transparent", agent.WithSession(session)).Collect() + if err != nil { + t.Fatalf("error = %v, want nil", err) + } + if got := result.String(); got != question { + t.Errorf("response text = %q, want %q", got, question) + } +} + // TestRunWithVariousTaskStates tests continuation token behavior for different task states func TestRunWithVariousTaskStates(t *testing.T) { tests := []struct {