refactor: replace sjson.Set usage with sjson.SetBytes to optimize mutable JSON transformations

This commit is contained in:
Luis Pater
2026-03-19 17:58:54 +08:00
parent 56073ded69
commit 2bd646ad70
73 changed files with 3008 additions and 2944 deletions

View File

@@ -34,23 +34,23 @@ import (
// - []byte: The transformed request data in Gemini API format
func ConvertGeminiRequestToGeminiCLI(_ string, inputRawJSON []byte, _ bool) []byte {
rawJSON := inputRawJSON
template := ""
template = `{"project":"","request":{},"model":""}`
template, _ = sjson.SetRaw(template, "request", string(rawJSON))
template, _ = sjson.Set(template, "model", gjson.Get(template, "request.model").String())
template, _ = sjson.Delete(template, "request.model")
template := []byte(`{"project":"","request":{},"model":""}`)
template, _ = sjson.SetRawBytes(template, "request", rawJSON)
template, _ = sjson.SetBytes(template, "model", gjson.GetBytes(template, "request.model").String())
template, _ = sjson.DeleteBytes(template, "request.model")
template, errFixCLIToolResponse := fixCLIToolResponse(template)
templateStr, errFixCLIToolResponse := fixCLIToolResponse(string(template))
if errFixCLIToolResponse != nil {
return []byte{}
}
template = []byte(templateStr)
systemInstructionResult := gjson.Get(template, "request.system_instruction")
systemInstructionResult := gjson.GetBytes(template, "request.system_instruction")
if systemInstructionResult.Exists() {
template, _ = sjson.SetRaw(template, "request.systemInstruction", systemInstructionResult.Raw)
template, _ = sjson.Delete(template, "request.system_instruction")
template, _ = sjson.SetRawBytes(template, "request.systemInstruction", []byte(systemInstructionResult.Raw))
template, _ = sjson.DeleteBytes(template, "request.system_instruction")
}
rawJSON = []byte(template)
rawJSON = template
// Normalize roles in request.contents: default to valid values if missing/invalid
contents := gjson.GetBytes(rawJSON, "request.contents")
@@ -113,7 +113,7 @@ func ConvertGeminiRequestToGeminiCLI(_ string, inputRawJSON []byte, _ bool) []by
// Filter out contents with empty parts to avoid Gemini API error:
// "required oneof field 'data' must have one initialized field"
filteredContents := "[]"
filteredContents := []byte(`[]`)
hasFiltered := false
gjson.GetBytes(rawJSON, "request.contents").ForEach(func(_, content gjson.Result) bool {
parts := content.Get("parts")
@@ -121,11 +121,11 @@ func ConvertGeminiRequestToGeminiCLI(_ string, inputRawJSON []byte, _ bool) []by
hasFiltered = true
return true
}
filteredContents, _ = sjson.SetRaw(filteredContents, "-1", content.Raw)
filteredContents, _ = sjson.SetRawBytes(filteredContents, "-1", []byte(content.Raw))
return true
})
if hasFiltered {
rawJSON, _ = sjson.SetRawBytes(rawJSON, "request.contents", []byte(filteredContents))
rawJSON, _ = sjson.SetRawBytes(rawJSON, "request.contents", filteredContents)
}
return common.AttachDefaultSafetySettings(rawJSON, "request.safetySettings")
@@ -142,7 +142,8 @@ type FunctionCallGroup struct {
func backfillFunctionResponseName(raw string, fallbackName string) string {
name := gjson.Get(raw, "functionResponse.name").String()
if strings.TrimSpace(name) == "" && fallbackName != "" {
raw, _ = sjson.Set(raw, "functionResponse.name", fallbackName)
rawBytes, _ := sjson.SetBytes([]byte(raw), "functionResponse.name", fallbackName)
raw = string(rawBytes)
}
return raw
}
@@ -171,7 +172,7 @@ func fixCLIToolResponse(input string) (string, error) {
}
// Initialize data structures for processing and grouping
contentsWrapper := `{"contents":[]}`
contentsWrapper := []byte(`{"contents":[]}`)
var pendingGroups []*FunctionCallGroup // Groups awaiting completion with responses
var collectedResponses []gjson.Result // Standalone responses to be matched
@@ -204,18 +205,18 @@ func fixCLIToolResponse(input string) (string, error) {
collectedResponses = collectedResponses[group.ResponsesNeeded:]
// Create merged function response content
functionResponseContent := `{"parts":[],"role":"function"}`
functionResponseContent := []byte(`{"parts":[],"role":"function"}`)
for ri, response := range groupResponses {
if !response.IsObject() {
log.Warnf("failed to parse function response")
continue
}
raw := backfillFunctionResponseName(response.Raw, group.CallNames[ri])
functionResponseContent, _ = sjson.SetRaw(functionResponseContent, "parts.-1", raw)
functionResponseContent, _ = sjson.SetRawBytes(functionResponseContent, "parts.-1", []byte(raw))
}
if gjson.Get(functionResponseContent, "parts.#").Int() > 0 {
contentsWrapper, _ = sjson.SetRaw(contentsWrapper, "contents.-1", functionResponseContent)
if gjson.GetBytes(functionResponseContent, "parts.#").Int() > 0 {
contentsWrapper, _ = sjson.SetRawBytes(contentsWrapper, "contents.-1", functionResponseContent)
}
}
@@ -238,7 +239,7 @@ func fixCLIToolResponse(input string) (string, error) {
log.Warnf("failed to parse model content")
return true
}
contentsWrapper, _ = sjson.SetRaw(contentsWrapper, "contents.-1", value.Raw)
contentsWrapper, _ = sjson.SetRawBytes(contentsWrapper, "contents.-1", []byte(value.Raw))
// Create a new group for tracking responses
group := &FunctionCallGroup{
@@ -252,7 +253,7 @@ func fixCLIToolResponse(input string) (string, error) {
log.Warnf("failed to parse content")
return true
}
contentsWrapper, _ = sjson.SetRaw(contentsWrapper, "contents.-1", value.Raw)
contentsWrapper, _ = sjson.SetRawBytes(contentsWrapper, "contents.-1", []byte(value.Raw))
}
} else {
// Non-model content (user, etc.)
@@ -260,7 +261,7 @@ func fixCLIToolResponse(input string) (string, error) {
log.Warnf("failed to parse content")
return true
}
contentsWrapper, _ = sjson.SetRaw(contentsWrapper, "contents.-1", value.Raw)
contentsWrapper, _ = sjson.SetRawBytes(contentsWrapper, "contents.-1", []byte(value.Raw))
}
return true
@@ -272,25 +273,25 @@ func fixCLIToolResponse(input string) (string, error) {
groupResponses := collectedResponses[:group.ResponsesNeeded]
collectedResponses = collectedResponses[group.ResponsesNeeded:]
functionResponseContent := `{"parts":[],"role":"function"}`
functionResponseContent := []byte(`{"parts":[],"role":"function"}`)
for ri, response := range groupResponses {
if !response.IsObject() {
log.Warnf("failed to parse function response")
continue
}
raw := backfillFunctionResponseName(response.Raw, group.CallNames[ri])
functionResponseContent, _ = sjson.SetRaw(functionResponseContent, "parts.-1", raw)
functionResponseContent, _ = sjson.SetRawBytes(functionResponseContent, "parts.-1", []byte(raw))
}
if gjson.Get(functionResponseContent, "parts.#").Int() > 0 {
contentsWrapper, _ = sjson.SetRaw(contentsWrapper, "contents.-1", functionResponseContent)
if gjson.GetBytes(functionResponseContent, "parts.#").Int() > 0 {
contentsWrapper, _ = sjson.SetRawBytes(contentsWrapper, "contents.-1", functionResponseContent)
}
}
}
// Update the original JSON with the new contents
result := input
result, _ = sjson.SetRaw(result, "request.contents", gjson.Get(contentsWrapper, "contents").Raw)
result := []byte(input)
result, _ = sjson.SetRawBytes(result, "request.contents", []byte(gjson.GetBytes(contentsWrapper, "contents").Raw))
return result, nil
return string(result), nil
}

View File

@@ -8,8 +8,8 @@ package gemini
import (
"bytes"
"context"
"fmt"
translatorcommon "github.com/router-for-me/CLIProxyAPI/v6/internal/translator/common"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
@@ -29,8 +29,8 @@ import (
// - param: A pointer to a parameter object for the conversion (unused in current implementation)
//
// Returns:
// - []string: The transformed request data in Gemini API format
func ConvertGeminiCliResponseToGemini(ctx context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []string {
// - [][]byte: The transformed request data in Gemini API format
func ConvertGeminiCliResponseToGemini(ctx context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) [][]byte {
if bytes.HasPrefix(rawJSON, []byte("data:")) {
rawJSON = bytes.TrimSpace(rawJSON[5:])
}
@@ -43,22 +43,22 @@ func ConvertGeminiCliResponseToGemini(ctx context.Context, _ string, originalReq
chunk = []byte(responseResult.Raw)
}
} else {
chunkTemplate := "[]"
chunkTemplate := []byte(`[]`)
responseResult := gjson.ParseBytes(chunk)
if responseResult.IsArray() {
responseResultItems := responseResult.Array()
for i := 0; i < len(responseResultItems); i++ {
responseResultItem := responseResultItems[i]
if responseResultItem.Get("response").Exists() {
chunkTemplate, _ = sjson.SetRaw(chunkTemplate, "-1", responseResultItem.Get("response").Raw)
chunkTemplate, _ = sjson.SetRawBytes(chunkTemplate, "-1", []byte(responseResultItem.Get("response").Raw))
}
}
}
chunk = []byte(chunkTemplate)
chunk = chunkTemplate
}
return []string{string(chunk)}
return [][]byte{chunk}
}
return []string{}
return [][]byte{}
}
// ConvertGeminiCliResponseToGeminiNonStream converts a non-streaming Gemini CLI request to a non-streaming Gemini response.
@@ -72,15 +72,15 @@ func ConvertGeminiCliResponseToGemini(ctx context.Context, _ string, originalReq
// - param: A pointer to a parameter object for the conversion (unused in current implementation)
//
// Returns:
// - string: A Gemini-compatible JSON response containing the response data
func ConvertGeminiCliResponseToGeminiNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) string {
// - []byte: A Gemini-compatible JSON response containing the response data
func ConvertGeminiCliResponseToGeminiNonStream(_ context.Context, _ string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []byte {
responseResult := gjson.GetBytes(rawJSON, "response")
if responseResult.Exists() {
return responseResult.Raw
return []byte(responseResult.Raw)
}
return string(rawJSON)
return rawJSON
}
func GeminiTokenCount(ctx context.Context, count int64) string {
return fmt.Sprintf(`{"totalTokens":%d,"promptTokensDetails":[{"modality":"TEXT","tokenCount":%d}]}`, count, count)
func GeminiTokenCount(ctx context.Context, count int64) []byte {
return translatorcommon.GeminiTokenCountJSON(count)
}