mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
Fixed: #551
fix(translator): standardize content node handling across translators for assistant and tool calls
This commit is contained in:
@@ -222,17 +222,17 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
}
|
}
|
||||||
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
||||||
} else if role == "assistant" {
|
} else if role == "assistant" {
|
||||||
|
node := []byte(`{"role":"model","parts":[]}`)
|
||||||
|
p := 0
|
||||||
if content.Type == gjson.String {
|
if content.Type == gjson.String {
|
||||||
// Assistant text -> single model content
|
node, _ = sjson.SetBytes(node, "parts.-1.text", content.String())
|
||||||
node := []byte(`{"role":"model","parts":[{"text":""}]}`)
|
|
||||||
node, _ = sjson.SetBytes(node, "parts.0.text", content.String())
|
|
||||||
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
||||||
} else if !content.Exists() || content.Type == gjson.Null {
|
p++
|
||||||
|
}
|
||||||
|
|
||||||
// Tool calls -> single model content with functionCall parts
|
// Tool calls -> single model content with functionCall parts
|
||||||
tcs := m.Get("tool_calls")
|
tcs := m.Get("tool_calls")
|
||||||
if tcs.IsArray() {
|
if tcs.IsArray() {
|
||||||
node := []byte(`{"role":"model","parts":[]}`)
|
|
||||||
p := 0
|
|
||||||
fIDs := make([]string, 0)
|
fIDs := make([]string, 0)
|
||||||
for _, tc := range tcs.Array() {
|
for _, tc := range tcs.Array() {
|
||||||
if tc.Get("type").String() != "function" {
|
if tc.Get("type").String() != "function" {
|
||||||
@@ -282,7 +282,6 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// tools -> request.tools[0].functionDeclarations + request.tools[0].googleSearch passthrough
|
// tools -> request.tools[0].functionDeclarations + request.tools[0].googleSearch passthrough
|
||||||
tools := gjson.GetBytes(rawJSON, "tools")
|
tools := gjson.GetBytes(rawJSON, "tools")
|
||||||
@@ -361,18 +360,3 @@ func ConvertOpenAIRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
|
|
||||||
// itoa converts int to string without strconv import for few usages.
|
// itoa converts int to string without strconv import for few usages.
|
||||||
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
||||||
|
|
||||||
// quoteIfNeeded ensures a string is valid JSON value (quotes plain text), pass-through for JSON objects/arrays.
|
|
||||||
func quoteIfNeeded(s string) string {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if s == "" {
|
|
||||||
return "\"\""
|
|
||||||
}
|
|
||||||
if len(s) > 0 && (s[0] == '{' || s[0] == '[') {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
// escape quotes minimally
|
|
||||||
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|
||||||
s = strings.ReplaceAll(s, "\"", "\\\"")
|
|
||||||
return "\"" + s + "\""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -205,17 +205,18 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
|
|||||||
}
|
}
|
||||||
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
||||||
} else if role == "assistant" {
|
} else if role == "assistant" {
|
||||||
|
p := 0
|
||||||
|
node := []byte(`{"role":"model","parts":[]}`)
|
||||||
if content.Type == gjson.String {
|
if content.Type == gjson.String {
|
||||||
// Assistant text -> single model content
|
// Assistant text -> single model content
|
||||||
node := []byte(`{"role":"model","parts":[{"text":""}]}`)
|
node, _ = sjson.SetBytes(node, "parts.-1.text", content.String())
|
||||||
node, _ = sjson.SetBytes(node, "parts.0.text", content.String())
|
|
||||||
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
out, _ = sjson.SetRawBytes(out, "request.contents.-1", node)
|
||||||
} else if !content.Exists() || content.Type == gjson.Null {
|
p++
|
||||||
|
}
|
||||||
|
|
||||||
// Tool calls -> single model content with functionCall parts
|
// Tool calls -> single model content with functionCall parts
|
||||||
tcs := m.Get("tool_calls")
|
tcs := m.Get("tool_calls")
|
||||||
if tcs.IsArray() {
|
if tcs.IsArray() {
|
||||||
node := []byte(`{"role":"model","parts":[]}`)
|
|
||||||
p := 0
|
|
||||||
fIDs := make([]string, 0)
|
fIDs := make([]string, 0)
|
||||||
for _, tc := range tcs.Array() {
|
for _, tc := range tcs.Array() {
|
||||||
if tc.Get("type").String() != "function" {
|
if tc.Get("type").String() != "function" {
|
||||||
@@ -255,7 +256,6 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// tools -> request.tools[0].functionDeclarations + request.tools[0].googleSearch passthrough
|
// tools -> request.tools[0].functionDeclarations + request.tools[0].googleSearch passthrough
|
||||||
tools := gjson.GetBytes(rawJSON, "tools")
|
tools := gjson.GetBytes(rawJSON, "tools")
|
||||||
@@ -334,18 +334,3 @@ func ConvertOpenAIRequestToGeminiCLI(modelName string, inputRawJSON []byte, _ bo
|
|||||||
|
|
||||||
// itoa converts int to string without strconv import for few usages.
|
// itoa converts int to string without strconv import for few usages.
|
||||||
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
||||||
|
|
||||||
// quoteIfNeeded ensures a string is valid JSON value (quotes plain text), pass-through for JSON objects/arrays.
|
|
||||||
func quoteIfNeeded(s string) string {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if s == "" {
|
|
||||||
return "\"\""
|
|
||||||
}
|
|
||||||
if len(s) > 0 && (s[0] == '{' || s[0] == '[') {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
// escape quotes minimally
|
|
||||||
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|
||||||
s = strings.ReplaceAll(s, "\"", "\\\"")
|
|
||||||
return "\"" + s + "\""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -207,15 +207,16 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
}
|
}
|
||||||
out, _ = sjson.SetRawBytes(out, "contents.-1", node)
|
out, _ = sjson.SetRawBytes(out, "contents.-1", node)
|
||||||
} else if role == "assistant" {
|
} else if role == "assistant" {
|
||||||
if content.Type == gjson.String {
|
|
||||||
// Assistant text -> single model content
|
|
||||||
node := []byte(`{"role":"model","parts":[{"text":""}]}`)
|
|
||||||
node, _ = sjson.SetBytes(node, "parts.0.text", content.String())
|
|
||||||
out, _ = sjson.SetRawBytes(out, "contents.-1", node)
|
|
||||||
} else if content.IsArray() {
|
|
||||||
// Assistant multimodal content (e.g. text + image) -> single model content with parts
|
|
||||||
node := []byte(`{"role":"model","parts":[]}`)
|
node := []byte(`{"role":"model","parts":[]}`)
|
||||||
p := 0
|
p := 0
|
||||||
|
|
||||||
|
if content.Type == gjson.String {
|
||||||
|
// Assistant text -> single model content
|
||||||
|
node, _ = sjson.SetBytes(node, "parts.-1.text", content.String())
|
||||||
|
out, _ = sjson.SetRawBytes(out, "contents.-1", node)
|
||||||
|
p++
|
||||||
|
} else if content.IsArray() {
|
||||||
|
// Assistant multimodal content (e.g. text + image) -> single model content with parts
|
||||||
for _, item := range content.Array() {
|
for _, item := range content.Array() {
|
||||||
switch item.Get("type").String() {
|
switch item.Get("type").String() {
|
||||||
case "text":
|
case "text":
|
||||||
@@ -237,12 +238,11 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
out, _ = sjson.SetRawBytes(out, "contents.-1", node)
|
out, _ = sjson.SetRawBytes(out, "contents.-1", node)
|
||||||
} else if !content.Exists() || content.Type == gjson.Null {
|
}
|
||||||
|
|
||||||
// Tool calls -> single model content with functionCall parts
|
// Tool calls -> single model content with functionCall parts
|
||||||
tcs := m.Get("tool_calls")
|
tcs := m.Get("tool_calls")
|
||||||
if tcs.IsArray() {
|
if tcs.IsArray() {
|
||||||
node := []byte(`{"role":"model","parts":[]}`)
|
|
||||||
p := 0
|
|
||||||
fIDs := make([]string, 0)
|
fIDs := make([]string, 0)
|
||||||
for _, tc := range tcs.Array() {
|
for _, tc := range tcs.Array() {
|
||||||
if tc.Get("type").String() != "function" {
|
if tc.Get("type").String() != "function" {
|
||||||
@@ -282,7 +282,6 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// tools -> tools[0].functionDeclarations + tools[0].googleSearch passthrough
|
// tools -> tools[0].functionDeclarations + tools[0].googleSearch passthrough
|
||||||
tools := gjson.GetBytes(rawJSON, "tools")
|
tools := gjson.GetBytes(rawJSON, "tools")
|
||||||
@@ -363,18 +362,3 @@ func ConvertOpenAIRequestToGemini(modelName string, inputRawJSON []byte, _ bool)
|
|||||||
|
|
||||||
// itoa converts int to string without strconv import for few usages.
|
// itoa converts int to string without strconv import for few usages.
|
||||||
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
func itoa(i int) string { return fmt.Sprintf("%d", i) }
|
||||||
|
|
||||||
// quoteIfNeeded ensures a string is valid JSON value (quotes plain text), pass-through for JSON objects/arrays.
|
|
||||||
func quoteIfNeeded(s string) string {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if s == "" {
|
|
||||||
return "\"\""
|
|
||||||
}
|
|
||||||
if len(s) > 0 && (s[0] == '{' || s[0] == '[') {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
// escape quotes minimally
|
|
||||||
s = strings.ReplaceAll(s, "\\", "\\\\")
|
|
||||||
s = strings.ReplaceAll(s, "\"", "\\\"")
|
|
||||||
return "\"" + s + "\""
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user