|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
package gemini |
|
|
|
|
|
import ( |
|
|
"bytes" |
|
|
"crypto/rand" |
|
|
"fmt" |
|
|
"math/big" |
|
|
"strconv" |
|
|
"strings" |
|
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc" |
|
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util" |
|
|
"github.com/tidwall/gjson" |
|
|
"github.com/tidwall/sjson" |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func ConvertGeminiRequestToCodex(modelName string, inputRawJSON []byte, _ bool) []byte { |
|
|
rawJSON := bytes.Clone(inputRawJSON) |
|
|
|
|
|
out := `{"model":"","instructions":"","input":[]}` |
|
|
|
|
|
|
|
|
_, instructions := misc.CodexInstructionsForModel(modelName, "") |
|
|
out, _ = sjson.Set(out, "instructions", instructions) |
|
|
|
|
|
root := gjson.ParseBytes(rawJSON) |
|
|
|
|
|
|
|
|
shortMap := map[string]string{} |
|
|
if tools := root.Get("tools"); tools.IsArray() { |
|
|
var names []string |
|
|
tarr := tools.Array() |
|
|
for i := 0; i < len(tarr); i++ { |
|
|
fns := tarr[i].Get("functionDeclarations") |
|
|
if !fns.IsArray() { |
|
|
continue |
|
|
} |
|
|
for _, fn := range fns.Array() { |
|
|
if v := fn.Get("name"); v.Exists() { |
|
|
names = append(names, v.String()) |
|
|
} |
|
|
} |
|
|
} |
|
|
if len(names) > 0 { |
|
|
shortMap = buildShortNameMap(names) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var pendingCallIDs []string |
|
|
|
|
|
|
|
|
genCallID := func() string { |
|
|
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" |
|
|
var b strings.Builder |
|
|
|
|
|
for i := 0; i < 24; i++ { |
|
|
n, _ := rand.Int(rand.Reader, big.NewInt(int64(len(letters)))) |
|
|
b.WriteByte(letters[n.Int64()]) |
|
|
} |
|
|
return "call_" + b.String() |
|
|
} |
|
|
|
|
|
|
|
|
out, _ = sjson.Set(out, "model", modelName) |
|
|
|
|
|
|
|
|
sysParts := root.Get("system_instruction.parts") |
|
|
if sysParts.IsArray() { |
|
|
msg := `{"type":"message","role":"user","content":[]}` |
|
|
arr := sysParts.Array() |
|
|
for i := 0; i < len(arr); i++ { |
|
|
p := arr[i] |
|
|
if t := p.Get("text"); t.Exists() { |
|
|
part := `{}` |
|
|
part, _ = sjson.Set(part, "type", "input_text") |
|
|
part, _ = sjson.Set(part, "text", t.String()) |
|
|
msg, _ = sjson.SetRaw(msg, "content.-1", part) |
|
|
} |
|
|
} |
|
|
if len(gjson.Get(msg, "content").Array()) > 0 { |
|
|
out, _ = sjson.SetRaw(out, "input.-1", msg) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
contents := root.Get("contents") |
|
|
if contents.IsArray() { |
|
|
items := contents.Array() |
|
|
for i := 0; i < len(items); i++ { |
|
|
item := items[i] |
|
|
role := item.Get("role").String() |
|
|
if role == "model" { |
|
|
role = "assistant" |
|
|
} |
|
|
|
|
|
parts := item.Get("parts") |
|
|
if !parts.IsArray() { |
|
|
continue |
|
|
} |
|
|
parr := parts.Array() |
|
|
for j := 0; j < len(parr); j++ { |
|
|
p := parr[j] |
|
|
|
|
|
if t := p.Get("text"); t.Exists() { |
|
|
msg := `{"type":"message","role":"","content":[]}` |
|
|
msg, _ = sjson.Set(msg, "role", role) |
|
|
partType := "input_text" |
|
|
if role == "assistant" { |
|
|
partType = "output_text" |
|
|
} |
|
|
part := `{}` |
|
|
part, _ = sjson.Set(part, "type", partType) |
|
|
part, _ = sjson.Set(part, "text", t.String()) |
|
|
msg, _ = sjson.SetRaw(msg, "content.-1", part) |
|
|
out, _ = sjson.SetRaw(out, "input.-1", msg) |
|
|
continue |
|
|
} |
|
|
|
|
|
|
|
|
if fc := p.Get("functionCall"); fc.Exists() { |
|
|
fn := `{"type":"function_call"}` |
|
|
if name := fc.Get("name"); name.Exists() { |
|
|
n := name.String() |
|
|
if short, ok := shortMap[n]; ok { |
|
|
n = short |
|
|
} else { |
|
|
n = shortenNameIfNeeded(n) |
|
|
} |
|
|
fn, _ = sjson.Set(fn, "name", n) |
|
|
} |
|
|
if args := fc.Get("args"); args.Exists() { |
|
|
fn, _ = sjson.Set(fn, "arguments", args.Raw) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
id := genCallID() |
|
|
fn, _ = sjson.Set(fn, "call_id", id) |
|
|
pendingCallIDs = append(pendingCallIDs, id) |
|
|
out, _ = sjson.SetRaw(out, "input.-1", fn) |
|
|
continue |
|
|
} |
|
|
|
|
|
|
|
|
if fr := p.Get("functionResponse"); fr.Exists() { |
|
|
fno := `{"type":"function_call_output"}` |
|
|
|
|
|
if res := fr.Get("response.result"); res.Exists() { |
|
|
fno, _ = sjson.Set(fno, "output", res.String()) |
|
|
} else if resp := fr.Get("response"); resp.Exists() { |
|
|
fno, _ = sjson.Set(fno, "output", resp.Raw) |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var id string |
|
|
if len(pendingCallIDs) > 0 { |
|
|
id = pendingCallIDs[0] |
|
|
|
|
|
pendingCallIDs = pendingCallIDs[1:] |
|
|
} else { |
|
|
id = genCallID() |
|
|
} |
|
|
fno, _ = sjson.Set(fno, "call_id", id) |
|
|
out, _ = sjson.SetRaw(out, "input.-1", fno) |
|
|
continue |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
tools := root.Get("tools") |
|
|
if tools.IsArray() { |
|
|
out, _ = sjson.SetRaw(out, "tools", `[]`) |
|
|
out, _ = sjson.Set(out, "tool_choice", "auto") |
|
|
tarr := tools.Array() |
|
|
for i := 0; i < len(tarr); i++ { |
|
|
td := tarr[i] |
|
|
fns := td.Get("functionDeclarations") |
|
|
if !fns.IsArray() { |
|
|
continue |
|
|
} |
|
|
farr := fns.Array() |
|
|
for j := 0; j < len(farr); j++ { |
|
|
fn := farr[j] |
|
|
tool := `{}` |
|
|
tool, _ = sjson.Set(tool, "type", "function") |
|
|
if v := fn.Get("name"); v.Exists() { |
|
|
name := v.String() |
|
|
if short, ok := shortMap[name]; ok { |
|
|
name = short |
|
|
} else { |
|
|
name = shortenNameIfNeeded(name) |
|
|
} |
|
|
tool, _ = sjson.Set(tool, "name", name) |
|
|
} |
|
|
if v := fn.Get("description"); v.Exists() { |
|
|
tool, _ = sjson.Set(tool, "description", v.String()) |
|
|
} |
|
|
if prm := fn.Get("parameters"); prm.Exists() { |
|
|
|
|
|
cleaned := prm.Raw |
|
|
cleaned, _ = sjson.Delete(cleaned, "$schema") |
|
|
cleaned, _ = sjson.Set(cleaned, "additionalProperties", false) |
|
|
tool, _ = sjson.SetRaw(tool, "parameters", cleaned) |
|
|
} else if prm = fn.Get("parametersJsonSchema"); prm.Exists() { |
|
|
|
|
|
cleaned := prm.Raw |
|
|
cleaned, _ = sjson.Delete(cleaned, "$schema") |
|
|
cleaned, _ = sjson.Set(cleaned, "additionalProperties", false) |
|
|
tool, _ = sjson.SetRaw(tool, "parameters", cleaned) |
|
|
} |
|
|
tool, _ = sjson.Set(tool, "strict", false) |
|
|
out, _ = sjson.SetRaw(out, "tools.-1", tool) |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
out, _ = sjson.Set(out, "parallel_tool_calls", true) |
|
|
|
|
|
|
|
|
reasoningEffort := "medium" |
|
|
if genConfig := root.Get("generationConfig"); genConfig.Exists() { |
|
|
if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { |
|
|
if util.ModelUsesThinkingLevels(modelName) { |
|
|
if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { |
|
|
budget := int(thinkingBudget.Int()) |
|
|
if effort, ok := util.ThinkingBudgetToEffort(modelName, budget); ok && effort != "" { |
|
|
reasoningEffort = effort |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
out, _ = sjson.Set(out, "reasoning.effort", reasoningEffort) |
|
|
out, _ = sjson.Set(out, "reasoning.summary", "auto") |
|
|
out, _ = sjson.Set(out, "stream", true) |
|
|
out, _ = sjson.Set(out, "store", false) |
|
|
out, _ = sjson.Set(out, "include", []string{"reasoning.encrypted_content"}) |
|
|
|
|
|
var pathsToLower []string |
|
|
toolsResult := gjson.Get(out, "tools") |
|
|
util.Walk(toolsResult, "", "type", &pathsToLower) |
|
|
for _, p := range pathsToLower { |
|
|
fullPath := fmt.Sprintf("tools.%s", p) |
|
|
out, _ = sjson.Set(out, fullPath, strings.ToLower(gjson.Get(out, fullPath).String())) |
|
|
} |
|
|
|
|
|
return []byte(out) |
|
|
} |
|
|
|
|
|
|
|
|
func shortenNameIfNeeded(name string) string { |
|
|
const limit = 64 |
|
|
if len(name) <= limit { |
|
|
return name |
|
|
} |
|
|
if strings.HasPrefix(name, "mcp__") { |
|
|
idx := strings.LastIndex(name, "__") |
|
|
if idx > 0 { |
|
|
cand := "mcp__" + name[idx+2:] |
|
|
if len(cand) > limit { |
|
|
return cand[:limit] |
|
|
} |
|
|
return cand |
|
|
} |
|
|
} |
|
|
return name[:limit] |
|
|
} |
|
|
|
|
|
|
|
|
func buildShortNameMap(names []string) map[string]string { |
|
|
const limit = 64 |
|
|
used := map[string]struct{}{} |
|
|
m := map[string]string{} |
|
|
|
|
|
baseCandidate := func(n string) string { |
|
|
if len(n) <= limit { |
|
|
return n |
|
|
} |
|
|
if strings.HasPrefix(n, "mcp__") { |
|
|
idx := strings.LastIndex(n, "__") |
|
|
if idx > 0 { |
|
|
cand := "mcp__" + n[idx+2:] |
|
|
if len(cand) > limit { |
|
|
cand = cand[:limit] |
|
|
} |
|
|
return cand |
|
|
} |
|
|
} |
|
|
return n[:limit] |
|
|
} |
|
|
|
|
|
makeUnique := func(cand string) string { |
|
|
if _, ok := used[cand]; !ok { |
|
|
return cand |
|
|
} |
|
|
base := cand |
|
|
for i := 1; ; i++ { |
|
|
suffix := "_" + strconv.Itoa(i) |
|
|
allowed := limit - len(suffix) |
|
|
if allowed < 0 { |
|
|
allowed = 0 |
|
|
} |
|
|
tmp := base |
|
|
if len(tmp) > allowed { |
|
|
tmp = tmp[:allowed] |
|
|
} |
|
|
tmp = tmp + suffix |
|
|
if _, ok := used[tmp]; !ok { |
|
|
return tmp |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
for _, n := range names { |
|
|
cand := baseCandidate(n) |
|
|
uniq := makeUnique(cand) |
|
|
used[uniq] = struct{}{} |
|
|
m[n] = uniq |
|
|
} |
|
|
return m |
|
|
} |
|
|
|