From f6d19a329c68e41ab1751e53e14054791faf6bdc Mon Sep 17 00:00:00 2001 From: sususu Date: Fri, 4 Sep 2026 18:08:20 +0800 Subject: [PATCH] fix(gemini): ensure functionResponse normalizes to user role in Gemini request normalizer --- .../gemini/gemini/gemini_gemini_request.go | 18 +++++++++++++++--- .../gemini/gemini_gemini_request_test.go | 17 +++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/internal/translator/gemini/gemini/gemini_gemini_request.go b/internal/translator/gemini/gemini/gemini_gemini_request.go index e8026f98..c309d105 100644 --- a/internal/translator/gemini/gemini/gemini_gemini_request.go +++ b/internal/translator/gemini/gemini/gemini_gemini_request.go @@ -78,7 +78,11 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte contents.ForEach(func(_, value gjson.Result) bool { role := value.Get("role").String() if role != "user" && role != "model" { - role = nextGeminiRole(prevRole) + if translatorcommon.ContentHasGeminiFunctionResponse([]byte(value.Raw)) { + role = "user" + } else { + role = nextGeminiRole(prevRole) + } rolesChanged = true } prevRole = role @@ -91,7 +95,11 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte role := value.Get("role").String() item := []byte(value.Raw) if role != "user" && role != "model" { - role = nextGeminiRole(prevRole) + if translatorcommon.ContentHasGeminiFunctionResponse([]byte(value.Raw)) { + role = "user" + } else { + role = nextGeminiRole(prevRole) + } item, _ = sjson.SetBytes(item, "role", role) } prevRole = role @@ -105,7 +113,11 @@ func ConvertGeminiRequestToGemini(_ string, inputRawJSON []byte, _ bool) []byte contents.ForEach(func(_ gjson.Result, value gjson.Result) bool { role := value.Get("role").String() if role != "user" && role != "model" { - role = nextGeminiRole(prevRole) + if translatorcommon.ContentHasGeminiFunctionResponse([]byte(value.Raw)) { + role = "user" + } else { + role = nextGeminiRole(prevRole) + } out, _ = sjson.SetBytes(out, fmt.Sprintf("contents.%d.role", idx), role) } prevRole = role diff --git a/internal/translator/gemini/gemini/gemini_gemini_request_test.go b/internal/translator/gemini/gemini/gemini_gemini_request_test.go index f5402ef0..ba70a905 100644 --- a/internal/translator/gemini/gemini/gemini_gemini_request_test.go +++ b/internal/translator/gemini/gemini/gemini_gemini_request_test.go @@ -253,3 +253,20 @@ func TestBackfillEmptyFunctionResponseNames_MultipleGroups(t *testing.T) { t.Errorf("Expected second group name 'Grep', got '%s'", name1) } } + +func TestConvertGeminiRequestToGemini_FunctionResponseWithInvalidRoleNormalizesToUser(t *testing.T) { + inputJSON := []byte(`{ + "contents": [ + {"role": "user", "parts": [{"text": "reminder"}]}, + {"role": "invalid", "parts": [{"functionResponse": {"name": "lookup", "response": {}}}]} + ] + }`) + out := ConvertGeminiRequestToGemini("gemini-3-flash", inputJSON, false) + contents := gjson.GetBytes(out, "contents").Array() + if len(contents) != 2 { + t.Fatalf("expected 2 contents, got %d", len(contents)) + } + if got := contents[1].Get("role").String(); got != "user" { + t.Fatalf("functionResponse invalid role should normalize to user, got %q", got) + } +} -- 2.51.2