Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions provider/a2aprovider/a2a.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,16 @@ func (a *a2aProvider) subscribeToTaskWithFallback(ctx context.Context, taskID a2
}
}

// agentRole maps an A2A message role onto the framework role, preserving a
// user-role response instead of assuming assistant. Mirrors the hosting-side
// toAgentMessage mapping and the Python client.
func agentRole(role a2a.MessageRole) message.Role {
if role == a2a.MessageRoleAgent {
return message.RoleAssistant
}
return message.RoleUser
}

func sendMsg(session *agent.Session, contextID string, seq iter.Seq2[a2a.Event, error], stream bool, yield func(*agent.ResponseUpdate, error) bool) {
var taskID string
var taskState a2a.TaskState
Expand Down Expand Up @@ -281,15 +291,17 @@ func sendMsg(session *agent.Session, contextID string, seq iter.Seq2[a2a.Event,
}
case *a2a.TaskStatusUpdateEvent:
var messageID string
role := message.RoleAssistant
if e.Status.Message != nil {
messageID = e.Status.Message.ID
role = agentRole(e.Status.Message.Role)
}
contents, err := statusContents(&e.Status)
if err != nil {
yield(nil, err)
return
}
update := newResponseUpdate(e, e.Metadata, string(e.TaskID), messageID, message.RoleAssistant, contents)
update := newResponseUpdate(e, e.Metadata, string(e.TaskID), messageID, role, contents)
update.FinishReason = finishReasonForTaskState(e.Status.State)
if !forward(update, nil) {
return
Expand All @@ -310,7 +322,7 @@ func sendMsg(session *agent.Session, contextID string, seq iter.Seq2[a2a.Event,
yield(nil, err)
return
}
update := newResponseUpdate(e, e.Metadata, e.ID, e.ID, message.RoleAssistant, contents)
update := newResponseUpdate(e, e.Metadata, e.ID, e.ID, agentRole(e.Role), contents)
update.FinishReason = "stop"
if !forward(update, nil) {
return
Expand Down
25 changes: 25 additions & 0 deletions provider/a2aprovider/a2a_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,31 @@ func TestRunAllowsNonUserRoleMessages(t *testing.T) {
}
}

// A response message carrying the A2A user role must map to RoleUser, not be
// hardcoded to assistant, matching the Python client.
func TestRunPreservesInboundUserRole(t *testing.T) {
Comment on lines +413 to +415
transport := &mockA2ATransport{
responseToReturn: &a2a.Message{
ID: "m1",
Role: a2a.MessageRoleUser,
Parts: a2a.ContentParts{a2a.NewTextPart("hi")},
},
}
a := newTestAgent(transport, agent.Config{})

result, err := a.RunText(t.Context(), "hello").Collect()
if err != nil {
t.Fatalf("error = %v, want nil", err)
}
if len(result.Messages) != 1 {
t.Fatalf("len(result.Messages) = %d, want 1", len(result.Messages))
}
if result.Messages[0].Role != message.RoleUser {
t.Errorf("Role = %q, want %q", result.Messages[0].Role, message.RoleUser)
}
}

// TestRunWithValidUserMessage tests successful run with valid user message.
func TestRunRejectsMessagesWithoutSendableParts(t *testing.T) {
tests := []struct {
name string
Expand Down
Loading