| |
| |
| |
| |
| |
| 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/thinking" |
| "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) |
| userAgent := misc.ExtractCodexUserAgent(rawJSON) |
| |
| out := `{"model":"","instructions":"","input":[]}` |
|
|
| |
| _, instructions := misc.CodexInstructionsForModel(modelName, "", userAgent) |
| 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":"developer","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) |
|
|
| |
| effortSet := false |
| if genConfig := root.Get("generationConfig"); genConfig.Exists() { |
| if thinkingConfig := genConfig.Get("thinkingConfig"); thinkingConfig.Exists() && thinkingConfig.IsObject() { |
| if thinkingLevel := thinkingConfig.Get("thinkingLevel"); thinkingLevel.Exists() { |
| effort := strings.ToLower(strings.TrimSpace(thinkingLevel.String())) |
| if effort != "" { |
| out, _ = sjson.Set(out, "reasoning.effort", effort) |
| effortSet = true |
| } |
| } else if thinkingBudget := thinkingConfig.Get("thinkingBudget"); thinkingBudget.Exists() { |
| if effort, ok := thinking.ConvertBudgetToLevel(int(thinkingBudget.Int())); ok { |
| out, _ = sjson.Set(out, "reasoning.effort", effort) |
| effortSet = true |
| } |
| } |
| } |
| } |
| if !effortSet { |
| |
| out, _ = sjson.Set(out, "reasoning.effort", "medium") |
| } |
| 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 |
| } |
|
|