From f2ac833149962a4b651644b13ccb339e8a8ceb5c Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Sun, 20 Sep 2026 10:00:29 +0530 Subject: [PATCH 1/2] Preserve inbound A2A message role instead of hardcoding assistant The client inbound path hardcoded message.RoleAssistant for A2A messages and task-status messages, discarding the actual A2A role. A peer message with the user role was reported as assistant. Map the role via a shared agentRole helper (agent -> assistant, otherwise user), matching the hosting-side toAgentMessage and the Python client. Artifacts carry no role and stay assistant. --- provider/a2aprovider/a2a.go | 18 +++++++++++++++--- provider/a2aprovider/a2a_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/provider/a2aprovider/a2a.go b/provider/a2aprovider/a2a.go index 679209c6..4f251175 100644 --- a/provider/a2aprovider/a2a.go +++ b/provider/a2aprovider/a2a.go @@ -220,6 +220,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, seq iter.Seq2[a2a.Event, error], stream bool, yield func(*agent.ResponseUpdate, error) bool) { var contextID, taskID string var taskState a2a.TaskState @@ -259,8 +269,10 @@ func sendMsg(session *agent.Session, seq iter.Seq2[a2a.Event, error], stream boo messageID string contents []message.Content ) + role := message.RoleAssistant if e.Status.Message != nil { messageID = e.Status.Message.ID + role = agentRole(e.Status.Message.Role) if e.Status.State == a2a.TaskStateInputRequired { var err error contents, err = partsToContents(e.Status.Message.Parts, nil) @@ -270,7 +282,7 @@ func sendMsg(session *agent.Session, seq iter.Seq2[a2a.Event, error], stream boo } } } - 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 !yield(update, nil) { return @@ -291,7 +303,7 @@ func sendMsg(session *agent.Session, seq iter.Seq2[a2a.Event, error], stream boo 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 !yield(update, nil) { return @@ -367,7 +379,7 @@ func yieldTask(yield func(*agent.ResponseUpdate, error) bool, task *a2a.Task, sp yield(nil, err) return false } - update := newResponseUpdate(task.Status, nil, string(task.ID), task.Status.Message.ID, message.RoleAssistant, contents) + update := newResponseUpdate(task.Status, nil, string(task.ID), task.Status.Message.ID, agentRole(task.Status.Message.Role), contents) update.ContinuationToken = continuationToken update.FinishReason = finishReason yielded = true diff --git a/provider/a2aprovider/a2a_test.go b/provider/a2aprovider/a2a_test.go index 92a08a81..67cd4bca 100644 --- a/provider/a2aprovider/a2a_test.go +++ b/provider/a2aprovider/a2a_test.go @@ -300,6 +300,30 @@ func TestRunAllowsNonUserRoleMessages(t *testing.T) { } // TestRunWithValidUserMessage tests successful run with valid user message +// 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) { + 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) + } +} + func TestRunWithValidUserMessage(t *testing.T) { transport := &mockA2ATransport{ responseToReturn: &a2a.Message{ From 146d5f624452c6eb1c0ac35b19dbdfcd0280e8eb Mon Sep 17 00:00:00 2001 From: PratikDhanave Date: Mon, 21 Sep 2026 17:11:05 +0530 Subject: [PATCH 2/2] Address review: cover task-status user-role mapping; fix comments Add a role assertion for the input-required task-status message path (yieldTask) so the user-role mapping cannot regress, and restore the TestRunWithValidUserMessage doc comment to its own test. --- go.mod | 21 +++++++-------- go.sum | 44 +++++++++++++++++--------------- provider/a2aprovider/a2a_test.go | 38 ++++++++++++++++++++++++++- 3 files changed, 72 insertions(+), 31 deletions(-) diff --git a/go.mod b/go.mod index d18de353..0a9307b3 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.1 github.com/a2aproject/a2a-go/v2 v2.5.0 github.com/ag-ui-protocol/ag-ui/sdks/community/go v0.0.0-20260312103001-8e7ab1df34c8 - github.com/anthropics/anthropic-sdk-go v1.72.0 - github.com/github/copilot-sdk/go v1.0.13 + github.com/anthropics/anthropic-sdk-go v1.73.0 + github.com/github/copilot-sdk/go v1.0.14 github.com/gofrs/flock v0.13.1 github.com/google/jsonschema-go v0.4.3 github.com/google/uuid v1.6.0 @@ -22,12 +22,12 @@ require ( go.opentelemetry.io/otel/trace v1.46.0 golang.org/x/sync v0.23.0 google.golang.org/genai v1.71.0 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 ) require ( cloud.google.com/go v0.116.0 // indirect - cloud.google.com/go/auth v0.18.2 // indirect + cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.8.0 // indirect @@ -36,14 +36,14 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coder/websocket v1.8.15 // indirect github.com/ebitengine/purego v0.10.1 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/go-logr/logr v1.4.4 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/s2a-go v0.1.9 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect - github.com/googleapis/gax-go/v2 v2.17.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect + github.com/googleapis/gax-go/v2 v2.22.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/invopop/jsonschema v0.14.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect @@ -59,7 +59,7 @@ require ( github.com/tidwall/sjson v1.2.5 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.yaml.in/yaml/v4 v4.0.0-rc.2 // indirect golang.org/x/crypto v0.55.0 // indirect golang.org/x/mod v0.38.0 // indirect @@ -68,7 +68,8 @@ require ( golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.41.0 // indirect golang.org/x/time v0.15.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect + google.golang.org/api v0.278.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 // indirect google.golang.org/protobuf v1.36.11 // indirect ) diff --git a/go.sum b/go.sum index 9ff26b78..2cc359b8 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= -cloud.google.com/go/auth v0.18.2 h1:+Nbt5Ev0xEqxlNjd6c+yYUeosQ5TtEUaNcN/3FozlaM= -cloud.google.com/go/auth v0.18.2/go.mod h1:xD+oY7gcahcu7G2SG2DsBerfFxgPAJz17zz2joOFF3M= +cloud.google.com/go/auth v0.20.0 h1:kXTssoVb4azsVDoUiF8KvxAqrsQcQtB53DcSgta74CA= +cloud.google.com/go/auth v0.20.0/go.mod h1:942/yi/itH1SsmpyrbnTMDgGfdy2BUqIKyd0cyYLc5Q= cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs= cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= github.com/Azure/azure-sdk-for-go/sdk/azcore v1.23.1 h1:zvXfGJCWvywnCA814d8ZiVyt+fm9nnTE8xSb99zRyfo= @@ -20,8 +20,8 @@ github.com/a2aproject/a2a-go/v2 v2.5.0 h1:ZdcFoxv+nZTUV0i2ue5hES76YCANFPG9vjqd7v github.com/a2aproject/a2a-go/v2 v2.5.0/go.mod h1:NcRp/ZHxgMzDj12/BteIC2gOjljuEBKaGRfEdJ2lNSI= github.com/ag-ui-protocol/ag-ui/sdks/community/go v0.0.0-20260312103001-8e7ab1df34c8 h1:gcgM2iVSJ+5v1Li2O2xBm8jdY9bIo0coZdOlXOd0uLU= github.com/ag-ui-protocol/ag-ui/sdks/community/go v0.0.0-20260312103001-8e7ab1df34c8/go.mod h1:ERAMOexUee4AIuoxksuuGoEcHl3aqLwaazjGwlR9ZCI= -github.com/anthropics/anthropic-sdk-go v1.72.0 h1:T0qQWWfygiL24lqEQaffRXCvEeno3yiv/mFzUdY1E88= -github.com/anthropics/anthropic-sdk-go v1.72.0/go.mod h1:x+lPk/cCl48uRegeP0hlYYBN1b7bEBTveInIMgLicnY= +github.com/anthropics/anthropic-sdk-go v1.73.0 h1:brLt0oVV6LH0YcG6O6G3pBnIVDJQ/QPoXul+OJHXEFc= +github.com/anthropics/anthropic-sdk-go v1.73.0/go.mod h1:x+lPk/cCl48uRegeP0hlYYBN1b7bEBTveInIMgLicnY= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= @@ -36,10 +36,10 @@ github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= github.com/ebitengine/purego v0.10.1 h1:dewVBCBT2GaMu1SrNTYxQhgQBethzfhiwvZiLGP/qyY= github.com/ebitengine/purego v0.10.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/github/copilot-sdk/go v1.0.13 h1:sc4kLSJksYb6lsUwFV48P4jDJ06PQR+4OzpqOhvxho0= -github.com/github/copilot-sdk/go v1.0.13/go.mod h1:U0STg9Jdv9LDwtkUtcUJCzPQ4KTnKakRfP+1LhEx248= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= +github.com/github/copilot-sdk/go v1.0.14 h1:DeHPF2CbxRCe4EeAjYIo7v3CovGEEMHsrURUNaogf5A= +github.com/github/copilot-sdk/go v1.0.14/go.mod h1:U0STg9Jdv9LDwtkUtcUJCzPQ4KTnKakRfP+1LhEx248= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -59,10 +59,10 @@ github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0= github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao= -github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= -github.com/googleapis/gax-go/v2 v2.17.0 h1:RksgfBpxqff0EZkDWYuz9q/uWsTVz+kf43LsZ1J6SMc= -github.com/googleapis/gax-go/v2 v2.17.0/go.mod h1:mzaqghpQp4JDh3HvADwrat+6M3MOIDp5YKHhb9PAgDY= +github.com/googleapis/enterprise-certificate-proxy v0.3.15 h1:xolVQTEXusUcAA5UgtyRLjelpFFHWlPQ4XfWGc7MBas= +github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/gax-go/v2 v2.22.0 h1:PjIWBpgGIVKGoCXuiCoP64altEJCj3/Ei+kSU5vlZD4= +github.com/googleapis/gax-go/v2 v2.22.0/go.mod h1:irWBbALSr0Sk3qlqb9SyJ1h68WjgeFuiOzI4Rqw5+aY= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/invopop/jsonschema v0.14.0 h1:MHQqLhvpNUZfw+hM3AZDYK7jxO8FZoQeQM77g8iyZjg= @@ -107,8 +107,8 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.46.0 h1:FHt5/CDyVxi/8IM1CH7VE/rRgq3kLHa2mSTVMO8AWyc= go.opentelemetry.io/otel v1.46.0/go.mod h1:Gj3SEScelsNC45tp4nSxRYlS+f5iez7W8XPMCt905kE= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.46.0 h1:KdRxPiAoMptR3vfWzvjjvutTsSiwbC2uG0496rzZNfo= @@ -151,14 +151,18 @@ golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/api v0.278.0 h1:W7jiRvRi53VYFfZ/HoZjQBtJk7gOFbHD8ot1RzVZU6E= +google.golang.org/api v0.278.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= google.golang.org/genai v1.71.0 h1:Wfo9n0uSzMhZH7d+rP7QxxSWELEDSD4z6O8W/C9s3oM= google.golang.org/genai v1.71.0/go.mod h1:mDdPDFXo1Ats7f1WXVyZgWb/CkMzFWTWJruIMy7hGIU= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa h1:Kjn0N0tCrDgiAFW+lGO4JZ3ck44CehvJQMAwj9QF0G8= -google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgnawEVsOn6OFsnpyxNPRY9QV01dNB0= +google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= +google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800 h1:admdQBe8jR3VWhBsUrAOaF2Qw6K/+p5pSm1GN8+6Fw4= +google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800/go.mod h1:FPk7EXUKMtImne7AmknoYjT4QXqKIzzRbeQIXzLk6fQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800 h1:qEHAMpSaUhtD0p3NbEEI83HwNGFxEwaSJ1G9PLnCBZE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/provider/a2aprovider/a2a_test.go b/provider/a2aprovider/a2a_test.go index 67cd4bca..075f34d8 100644 --- a/provider/a2aprovider/a2a_test.go +++ b/provider/a2aprovider/a2a_test.go @@ -299,7 +299,6 @@ func TestRunAllowsNonUserRoleMessages(t *testing.T) { } } -// TestRunWithValidUserMessage tests successful run with valid user message // 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) { @@ -324,6 +323,7 @@ func TestRunPreservesInboundUserRole(t *testing.T) { } } +// TestRunWithValidUserMessage tests successful run with valid user message. func TestRunWithValidUserMessage(t *testing.T) { transport := &mockA2ATransport{ responseToReturn: &a2a.Message{ @@ -1331,6 +1331,42 @@ func TestRunWithInputRequiredTaskMessage(t *testing.T) { } } +// An input-required task-status message carrying the A2A user role must map to +// RoleUser, covering the yieldTask status-message role mapping. +func TestRunWithInputRequiredTaskMessagePreservesUserRole(t *testing.T) { + transport := &mockA2ATransport{ + responseToReturn: &a2a.Task{ + ID: a2a.TaskID("task-role"), + ContextID: "context-1", + Status: a2a.TaskStatus{ + State: a2a.TaskStateInputRequired, + Message: &a2a.Message{ + ID: "msg-role", + Role: a2a.MessageRoleUser, + Parts: a2a.ContentParts{a2a.NewTextPart("need input")}, + }, + }, + }, + } + a := newTestAgent(transport, agent.Config{}) + session, err := a.CreateSession(t.Context()) + if err != nil { + t.Fatal(err) + } + + result, err := a.RunText(t.Context(), "go", agent.WithSession(session)).Collect() + if err != nil { + t.Fatalf("error = %v, want nil", err) + } + if len(result.Messages) == 0 { + t.Fatal("no messages returned") + } + last := result.Messages[len(result.Messages)-1] + if last.Role != message.RoleUser { + t.Errorf("status message Role = %q, want %q", last.Role, message.RoleUser) + } +} + // TestRunWithVariousTaskStates tests continuation token behavior for different task states func TestRunWithVariousTaskStates(t *testing.T) { tests := []struct {