mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-06-09 07:42:42 +08:00
Merge pull request #3679 from sususu98/codex/system-role-to-user-upstream-dev
fix(translator): normalize message-level system roles for Gemini
This commit is contained in:
@@ -308,6 +308,8 @@ func ConvertClaudeRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
||||
role := originalRole
|
||||
if role == "assistant" {
|
||||
role = "model"
|
||||
} else if role == "system" {
|
||||
role = "user"
|
||||
}
|
||||
clientContentJSON := []byte(`{"role":"","parts":[]}`)
|
||||
clientContentJSON, _ = sjson.SetBytes(clientContentJSON, "role", role)
|
||||
|
||||
@@ -133,6 +133,53 @@ func TestConvertClaudeRequestToAntigravity_StripsClaudeCodeAttribution(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToAntigravity_ConvertsMessageSystemRoleToUserContent(t *testing.T) {
|
||||
inputJSON := []byte(`{
|
||||
"model": "gemini-3.5-flash",
|
||||
"system": [{"type": "text", "text": "Top-level rules"}],
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello"}]},
|
||||
{"role": "system", "content": "String mid-conversation rule"},
|
||||
{"role": "system", "content": [{"type": "text", "text": "Array mid-conversation rule"}]}
|
||||
]
|
||||
}`)
|
||||
|
||||
output := ConvertClaudeRequestToAntigravity("gemini-3-flash-agent", inputJSON, false)
|
||||
outputStr := string(output)
|
||||
|
||||
if systemContent := gjson.Get(outputStr, `request.contents.#(role=="system")`); systemContent.Exists() {
|
||||
t.Fatalf("system role should not be emitted in request.contents: %s", systemContent.Raw)
|
||||
}
|
||||
|
||||
contents := gjson.Get(outputStr, "request.contents").Array()
|
||||
if len(contents) != 3 {
|
||||
t.Fatalf("Expected the user and message-level system turns in request.contents, got %d: %s", len(contents), gjson.Get(outputStr, "request.contents").Raw)
|
||||
}
|
||||
if got := contents[0].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected first content role user, got %q", got)
|
||||
}
|
||||
if got := contents[1].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected message-level system content to be downgraded to user role, got %q", got)
|
||||
}
|
||||
if got := contents[1].Get("parts.0.text").String(); got != "String mid-conversation rule" {
|
||||
t.Fatalf("Unexpected string message-level system content text: %q", got)
|
||||
}
|
||||
if got := contents[2].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected array message-level system content to be downgraded to user role, got %q", got)
|
||||
}
|
||||
if got := contents[2].Get("parts.0.text").String(); got != "Array mid-conversation rule" {
|
||||
t.Fatalf("Unexpected array message-level system content text: %q", got)
|
||||
}
|
||||
|
||||
parts := gjson.Get(outputStr, "request.systemInstruction.parts").Array()
|
||||
if len(parts) != 1 {
|
||||
t.Fatalf("Expected only top-level system parts, got %d: %s", len(parts), gjson.Get(outputStr, "request.systemInstruction.parts").Raw)
|
||||
}
|
||||
if got := parts[0].Get("text").String(); got != "Top-level rules" {
|
||||
t.Fatalf("Unexpected first system part: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func testNonAnthropicRawSignature(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
|
||||
@@ -77,6 +77,8 @@ func ConvertClaudeRequestToCLI(modelName string, inputRawJSON []byte, _ bool) []
|
||||
role := roleResult.String()
|
||||
if role == "assistant" {
|
||||
role = "model"
|
||||
} else if role == "system" {
|
||||
role = "user"
|
||||
}
|
||||
|
||||
contentJSON := []byte(`{"role":"","parts":[]}`)
|
||||
|
||||
@@ -61,3 +61,49 @@ func TestConvertClaudeRequestToCLI_StripsClaudeCodeAttribution(t *testing.T) {
|
||||
t.Fatalf("Unexpected system part: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToCLI_ConvertsMessageSystemRoleToUserContent(t *testing.T) {
|
||||
inputJSON := []byte(`{
|
||||
"model": "gemini-3-flash-preview",
|
||||
"system": [{"type": "text", "text": "Top-level rules"}],
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello"}]},
|
||||
{"role": "system", "content": "String mid-conversation rule"},
|
||||
{"role": "system", "content": [{"type": "text", "text": "Array mid-conversation rule"}]}
|
||||
]
|
||||
}`)
|
||||
|
||||
output := ConvertClaudeRequestToCLI("gemini-3-flash-preview", inputJSON, false)
|
||||
|
||||
if systemContent := gjson.GetBytes(output, `request.contents.#(role=="system")`); systemContent.Exists() {
|
||||
t.Fatalf("system role should not be emitted in request.contents: %s", systemContent.Raw)
|
||||
}
|
||||
|
||||
contents := gjson.GetBytes(output, "request.contents").Array()
|
||||
if len(contents) != 3 {
|
||||
t.Fatalf("Expected the user and message-level system turns in request.contents, got %d: %s", len(contents), gjson.GetBytes(output, "request.contents").Raw)
|
||||
}
|
||||
if got := contents[0].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected first content role user, got %q", got)
|
||||
}
|
||||
if got := contents[1].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected message-level string system content to be downgraded to user role, got %q", got)
|
||||
}
|
||||
if got := contents[1].Get("parts.0.text").String(); got != "String mid-conversation rule" {
|
||||
t.Fatalf("Unexpected string message-level system content text: %q", got)
|
||||
}
|
||||
if got := contents[2].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected message-level array system content to be downgraded to user role, got %q", got)
|
||||
}
|
||||
if got := contents[2].Get("parts.0.text").String(); got != "Array mid-conversation rule" {
|
||||
t.Fatalf("Unexpected array message-level system content text: %q", got)
|
||||
}
|
||||
|
||||
parts := gjson.GetBytes(output, "request.systemInstruction.parts").Array()
|
||||
if len(parts) != 1 {
|
||||
t.Fatalf("Expected only top-level system parts, got %d: %s", len(parts), gjson.GetBytes(output, "request.systemInstruction.parts").Raw)
|
||||
}
|
||||
if got := parts[0].Get("text").String(); got != "Top-level rules" {
|
||||
t.Fatalf("Unexpected first system part: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,8 @@ func ConvertClaudeRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
||||
role := roleResult.String()
|
||||
if role == "assistant" {
|
||||
role = "model"
|
||||
} else if role == "system" {
|
||||
role = "user"
|
||||
}
|
||||
|
||||
contentJSON := []byte(`{"role":"","parts":[]}`)
|
||||
|
||||
@@ -107,6 +107,52 @@ func TestConvertClaudeRequestToGemini_StripsClaudeCodeAttribution(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToGemini_ConvertsMessageSystemRoleToUserContent(t *testing.T) {
|
||||
inputJSON := []byte(`{
|
||||
"model": "gemini-3-flash-preview",
|
||||
"system": [{"type": "text", "text": "Top-level rules"}],
|
||||
"messages": [
|
||||
{"role": "user", "content": [{"type": "text", "text": "Hello"}]},
|
||||
{"role": "system", "content": "String mid-conversation rule"},
|
||||
{"role": "system", "content": [{"type": "text", "text": "Array mid-conversation rule"}]}
|
||||
]
|
||||
}`)
|
||||
|
||||
output := ConvertClaudeRequestToGemini("gemini-3-flash-preview", inputJSON, false)
|
||||
|
||||
if systemContent := gjson.GetBytes(output, `contents.#(role=="system")`); systemContent.Exists() {
|
||||
t.Fatalf("system role should not be emitted in contents: %s", systemContent.Raw)
|
||||
}
|
||||
|
||||
contents := gjson.GetBytes(output, "contents").Array()
|
||||
if len(contents) != 3 {
|
||||
t.Fatalf("Expected the user and message-level system turns in contents, got %d: %s", len(contents), gjson.GetBytes(output, "contents").Raw)
|
||||
}
|
||||
if got := contents[0].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected first content role user, got %q", got)
|
||||
}
|
||||
if got := contents[1].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected message-level string system content to be downgraded to user role, got %q", got)
|
||||
}
|
||||
if got := contents[1].Get("parts.0.text").String(); got != "String mid-conversation rule" {
|
||||
t.Fatalf("Unexpected string message-level system content text: %q", got)
|
||||
}
|
||||
if got := contents[2].Get("role").String(); got != "user" {
|
||||
t.Fatalf("Expected message-level array system content to be downgraded to user role, got %q", got)
|
||||
}
|
||||
if got := contents[2].Get("parts.0.text").String(); got != "Array mid-conversation rule" {
|
||||
t.Fatalf("Unexpected array message-level system content text: %q", got)
|
||||
}
|
||||
|
||||
parts := gjson.GetBytes(output, "system_instruction.parts").Array()
|
||||
if len(parts) != 1 {
|
||||
t.Fatalf("Expected only top-level system parts, got %d: %s", len(parts), gjson.GetBytes(output, "system_instruction.parts").Raw)
|
||||
}
|
||||
if got := parts[0].Get("text").String(); got != "Top-level rules" {
|
||||
t.Fatalf("Unexpected first system part: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertClaudeRequestToGemini_SkipsEmptyTextParts(t *testing.T) {
|
||||
inputJSON := []byte(`{
|
||||
"model": "claude-3-5-sonnet",
|
||||
|
||||
Reference in New Issue
Block a user