From 7a31ba70512e053618a6b3a0d35cf2099e0fe4b2 Mon Sep 17 00:00:00 2001 From: "microsoft-agent-framework-for-go[bot]" <324824725+microsoft-agent-framework-for-go[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 02:47:15 +0000 Subject: [PATCH] fix(a2a): reject empty outbound messages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- provider/a2aprovider/a2a.go | 6 +++--- provider/a2aprovider/a2a_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/provider/a2aprovider/a2a.go b/provider/a2aprovider/a2a.go index 34eb58a7..45e1fa69 100644 --- a/provider/a2aprovider/a2a.go +++ b/provider/a2aprovider/a2a.go @@ -119,9 +119,6 @@ func (a *a2aProvider) run(ctx context.Context, messages []*message.Message, opti }, false, yield) return } - if len(messages) == 0 { - return - } userMsg, err := messagesToA2AMessage(session, messages) if err != nil { yield(nil, err) @@ -170,6 +167,9 @@ func messagesToA2AMessage(session *agent.Session, messages []*message.Message) ( maps.Copy(metadata, msg.AdditionalProperties) } } + if len(parts) == 0 { + return nil, errors.New("a2aprovider: the provided messages contain no content that can be sent to the remote agent") + } combined := &message.Message{ID: msgID, AdditionalProperties: metadata} return createA2AMessage(session, combined, parts), nil } diff --git a/provider/a2aprovider/a2a_test.go b/provider/a2aprovider/a2a_test.go index 49c205fe..642730bc 100644 --- a/provider/a2aprovider/a2a_test.go +++ b/provider/a2aprovider/a2a_test.go @@ -410,6 +410,34 @@ func TestRunAllowsNonUserRoleMessages(t *testing.T) { } } +func TestRunRejectsMessagesWithoutSendableParts(t *testing.T) { + tests := []struct { + name string + messages []*message.Message + }{ + {name: "empty messages"}, + {name: "empty contents", messages: []*message.Message{{Role: message.RoleUser}}}, + {name: "empty text", messages: []*message.Message{{Role: message.RoleUser, Contents: message.Contents{&message.TextContent{}}}}}, + {name: "unsupported contents", messages: []*message.Message{{Role: message.RoleUser, Contents: message.Contents{&message.RawContent{}}}}}, + } + for _, test := range tests { + for _, streaming := range []bool{false, true} { + t.Run(fmt.Sprintf("%s/streaming=%t", test.name, streaming), func(t *testing.T) { + transport := &mockA2ATransport{} + a := newTestAgent(transport, agent.Config{}) + + _, err := a.Run(t.Context(), test.messages, agent.Stream(streaming)).Collect() + if err == nil || !strings.Contains(err.Error(), "no content that can be sent to the remote agent") { + t.Fatalf("error = %v, want no-sendable-content error", err) + } + if transport.sendMessageCallCount != 0 || transport.sendStreamingCallCount != 0 { + t.Fatalf("A2A send calls = (%d, %d), want (0, 0)", transport.sendMessageCallCount, transport.sendStreamingCallCount) + } + }) + } + } +} + // An ErrorContent must be sent to A2A as its human-readable text, not as an // opaque JSON blob (the default-branch behavior), matching the Python client. func TestRunSendsErrorContentAsText(t *testing.T) {