mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 20:30:51 +08:00
Add Gemini-to-Gemini request normalization and passthrough support
- Introduced a `ConvertGeminiRequestToGemini` function to normalize Gemini v1beta requests by ensuring valid or default roles. - Added passthrough response handlers for both streamed and non-streamed Gemini responses. - Registered translators for Gemini-to-Gemini traffic in the initialization process. - Updated `gemini-cli` request normalization to align with the new Gemini translator logic.
This commit is contained in:
@@ -49,6 +49,33 @@ func ConvertGeminiRequestToGeminiCLI(_ string, rawJSON []byte, _ bool) []byte {
|
|||||||
}
|
}
|
||||||
rawJSON = []byte(template)
|
rawJSON = []byte(template)
|
||||||
|
|
||||||
|
// Normalize roles in request.contents: default to valid values if missing/invalid
|
||||||
|
contents := gjson.GetBytes(rawJSON, "request.contents")
|
||||||
|
if contents.Exists() {
|
||||||
|
prevRole := ""
|
||||||
|
idx := 0
|
||||||
|
contents.ForEach(func(_ gjson.Result, value gjson.Result) bool {
|
||||||
|
role := value.Get("role").String()
|
||||||
|
valid := role == "user" || role == "model"
|
||||||
|
if role == "" || !valid {
|
||||||
|
var newRole string
|
||||||
|
if prevRole == "" {
|
||||||
|
newRole = "user"
|
||||||
|
} else if prevRole == "user" {
|
||||||
|
newRole = "model"
|
||||||
|
} else {
|
||||||
|
newRole = "user"
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("request.contents.%d.role", idx)
|
||||||
|
rawJSON, _ = sjson.SetBytes(rawJSON, path, newRole)
|
||||||
|
role = newRole
|
||||||
|
}
|
||||||
|
prevRole = role
|
||||||
|
idx++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return rawJSON
|
return rawJSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
54
internal/translator/gemini/gemini/gemini_gemini_request.go
Normal file
54
internal/translator/gemini/gemini/gemini_gemini_request.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// Package gemini provides in-provider request normalization for Gemini API.
|
||||||
|
// It ensures incoming v1beta requests meet minimal schema requirements
|
||||||
|
// expected by Google's Generative Language API.
|
||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/tidwall/gjson"
|
||||||
|
"github.com/tidwall/sjson"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConvertGeminiRequestToGemini normalizes Gemini v1beta requests.
|
||||||
|
// - Adds a default role for each content if missing or invalid.
|
||||||
|
// The first message defaults to "user", then alternates user/model when needed.
|
||||||
|
//
|
||||||
|
// It keeps the payload otherwise unchanged.
|
||||||
|
func ConvertGeminiRequestToGemini(_ string, rawJSON []byte, _ bool) []byte {
|
||||||
|
// Fast path: if no contents field, return as-is
|
||||||
|
contents := gjson.GetBytes(rawJSON, "contents")
|
||||||
|
if !contents.Exists() {
|
||||||
|
return rawJSON
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk contents and fix roles
|
||||||
|
out := rawJSON
|
||||||
|
prevRole := ""
|
||||||
|
idx := 0
|
||||||
|
contents.ForEach(func(_ gjson.Result, value gjson.Result) bool {
|
||||||
|
role := value.Get("role").String()
|
||||||
|
|
||||||
|
// Only user/model are valid for Gemini v1beta requests
|
||||||
|
valid := role == "user" || role == "model"
|
||||||
|
if role == "" || !valid {
|
||||||
|
var newRole string
|
||||||
|
if prevRole == "" {
|
||||||
|
newRole = "user"
|
||||||
|
} else if prevRole == "user" {
|
||||||
|
newRole = "model"
|
||||||
|
} else {
|
||||||
|
newRole = "user"
|
||||||
|
}
|
||||||
|
path := fmt.Sprintf("contents.%d.role", idx)
|
||||||
|
out, _ = sjson.SetBytes(out, path, newRole)
|
||||||
|
role = newRole
|
||||||
|
}
|
||||||
|
|
||||||
|
prevRole = role
|
||||||
|
idx++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return out
|
||||||
|
}
|
||||||
15
internal/translator/gemini/gemini/gemini_gemini_response.go
Normal file
15
internal/translator/gemini/gemini/gemini_gemini_response.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PassthroughGeminiResponseStream forwards Gemini responses unchanged.
|
||||||
|
func PassthroughGeminiResponseStream(_ context.Context, _ string, rawJSON []byte, _ *any) []string {
|
||||||
|
return []string{string(rawJSON)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PassthroughGeminiResponseNonStream forwards Gemini responses unchanged.
|
||||||
|
func PassthroughGeminiResponseNonStream(_ context.Context, _ string, rawJSON []byte, _ *any) string {
|
||||||
|
return string(rawJSON)
|
||||||
|
}
|
||||||
21
internal/translator/gemini/gemini/init.go
Normal file
21
internal/translator/gemini/gemini/init.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package gemini
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/luispater/CLIProxyAPI/internal/constant"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/interfaces"
|
||||||
|
"github.com/luispater/CLIProxyAPI/internal/translator/translator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register a no-op response translator and a request normalizer for Gemini→Gemini.
|
||||||
|
// The request converter ensures missing or invalid roles are normalized to valid values.
|
||||||
|
func init() {
|
||||||
|
translator.Register(
|
||||||
|
GEMINI,
|
||||||
|
GEMINI,
|
||||||
|
ConvertGeminiRequestToGemini,
|
||||||
|
interfaces.TranslateResponse{
|
||||||
|
Stream: PassthroughGeminiResponseStream,
|
||||||
|
NonStream: PassthroughGeminiResponseNonStream,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/gemini"
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/gemini"
|
||||||
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/openai"
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini-cli/openai"
|
||||||
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/claude"
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/claude"
|
||||||
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini"
|
||||||
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini-cli"
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/gemini-cli"
|
||||||
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai"
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/gemini/openai"
|
||||||
_ "github.com/luispater/CLIProxyAPI/internal/translator/openai/claude"
|
_ "github.com/luispater/CLIProxyAPI/internal/translator/openai/claude"
|
||||||
|
|||||||
Reference in New Issue
Block a user