diff --git a/provider/a2aprovider/a2a.go b/provider/a2aprovider/a2a.go index e90a7f0f..4cf6f418 100644 --- a/provider/a2aprovider/a2a.go +++ b/provider/a2aprovider/a2a.go @@ -296,6 +296,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 + } + } + } artifactMetadata := make([]map[string]any, 0, len(task.Artifacts)) for _, artifact := range task.Artifacts { var err error @@ -311,7 +327,7 @@ func yieldTask(yield func(*agent.ResponseUpdate, error) bool, task *a2a.Task) bo // task-level metadata, matching .NET's A2A conversion which preserves // artifact metadata rather than dropping it. metadata := mergeMetadata(task.Metadata, artifactMetadata...) - update := newResponseUpdate(task, metadata, string(task.ID), "", message.RoleAssistant, contents, timestamp) + update := newResponseUpdate(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 80d94db9..aa3999b9 100644 --- a/provider/a2aprovider/a2a_test.go +++ b/provider/a2aprovider/a2a_test.go @@ -1058,6 +1058,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 {