Something went wrong. Try again.
sloproxy
Something went wrong. Try again.
13 kB · 418 lines
Go
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419package util
import ( "crypto/sha256" "encoding/hex" "fmt" "sort" "strings"
"github.com/tidwall/gjson" "github.com/tidwall/sjson")
// ResponsesToolIdentity represents the resolved identity of a tool in OpenAI Responses format.type ResponsesToolIdentity struct { Name string Namespace string Custom bool}
// ResponsesToolDescriptor is an internal representation of a tool declaration in a Responses request.type ResponsesToolDescriptor struct { Name string // Qualified name (e.g. "functions__exec" or "exec") LocalName string // Local name without namespace (e.g. "exec") Namespace string // Namespace if any (e.g. "functions") ToolType string // "function", "custom", etc. Tool gjson.Result SourcePriority int // 0 for top-level tools, 1 for additional_tools Direct bool // true if declared directly, false if declared as namespace child Order int // original discovery order}
// QualifyResponsesNamespaceToolName qualifies a child tool name with its namespace.func QualifyResponsesNamespaceToolName(namespaceName, childName string) string { childName = strings.TrimSpace(childName) namespaceName = strings.TrimSpace(namespaceName) if childName == "" || namespaceName == "" || strings.HasPrefix(childName, "mcp__") { return childName } if childName == namespaceName || strings.HasPrefix(childName, namespaceName+"__") { return childName } if strings.HasSuffix(namespaceName, "__") { return namespaceName + childName } return namespaceName + "__" + childName}
func responsesToolSources(root gjson.Result) []struct { tools gjson.Result priority int} { var sources []struct { tools gjson.Result priority int } appendSource := func(tools gjson.Result, priority int) { if tools.Exists() && tools.IsArray() { sources = append(sources, struct { tools gjson.Result priority int }{tools: tools, priority: priority}) } } appendSource(root.Get("tools"), 0) if input := root.Get("input"); input.Exists() && input.IsArray() { input.ForEach(func(_, item gjson.Result) bool { if item.Get("type").String() == "additional_tools" { appendSource(item.Get("tools"), 1) } return true }) } return sources}
func responsesToolName(tool gjson.Result) string { if name := strings.TrimSpace(tool.Get("name").String()); name != "" { return name } return strings.TrimSpace(tool.Get("function.name").String())}
func responsesToolDescription(tool gjson.Result) string { if description := tool.Get("description").String(); description != "" { return description } return tool.Get("function.description").String()}
func responsesToolParameters(tool gjson.Result) gjson.Result { for _, path := range []string{ "parameters", "parametersJsonSchema", "input_schema", "function.parameters", "function.parametersJsonSchema", } { if parameters := tool.Get(path); parameters.Exists() { return parameters } } return gjson.Result{}}
// CollectResponsesToolDescriptors extracts all tool descriptors from a Responses request root.func CollectResponsesToolDescriptors(root gjson.Result) []ResponsesToolDescriptor { var descriptors []ResponsesToolDescriptor appendDescriptor := func(tool gjson.Result, name, localName, namespace string, toolType string, sourcePriority int, direct bool) { if name == "" { return } descriptors = append(descriptors, ResponsesToolDescriptor{ Name: name, LocalName: localName, Namespace: namespace, ToolType: toolType, Tool: tool, SourcePriority: sourcePriority, Direct: direct, Order: len(descriptors), }) } appendNamespaceChildren := func(namespaceTool gjson.Result, sourcePriority int) { namespaceName := strings.TrimSpace(namespaceTool.Get("name").String()) children := namespaceTool.Get("tools") if !children.Exists() || !children.IsArray() { return } children.ForEach(func(_, child gjson.Result) bool { childName := responsesToolName(child) if childName == "" { return true } qualifiedName := QualifyResponsesNamespaceToolName(namespaceName, childName) switch strings.TrimSpace(child.Get("type").String()) { case "", "function": appendDescriptor(child, qualifiedName, childName, namespaceName, "function", sourcePriority, false) case "custom": appendDescriptor(child, qualifiedName, childName, namespaceName, "custom", sourcePriority, false) } return true }) } for _, source := range responsesToolSources(root) { source.tools.ForEach(func(_, tool gjson.Result) bool { toolType := strings.TrimSpace(tool.Get("type").String()) switch toolType { case "", "function": name := responsesToolName(tool) appendDescriptor(tool, name, name, "", "function", source.priority, true) case "custom": name := responsesToolName(tool) appendDescriptor(tool, name, name, "", "custom", source.priority, true) case "namespace": appendNamespaceChildren(tool, source.priority) } return true }) } return descriptors}
func responsesToolDescriptorPrecedes(left, right ResponsesToolDescriptor) bool { if left.SourcePriority != right.SourcePriority { return left.SourcePriority < right.SourcePriority } if left.Direct != right.Direct { return left.Direct } return left.Order < right.Order}
// CollectResponsesToolWinners collects deduplicated winning descriptors for each qualified tool name.func CollectResponsesToolWinners(root gjson.Result) map[string]ResponsesToolDescriptor { winners := map[string]ResponsesToolDescriptor{} for _, descriptor := range CollectResponsesToolDescriptors(root) { current, exists := winners[descriptor.Name] if !exists || responsesToolDescriptorPrecedes(descriptor, current) { winners[descriptor.Name] = descriptor } } return winners}
func sanitizeResponsesToolNames(names []string) map[string]string { if len(names) == 0 { return nil } uniqueNames := make(map[string]struct{}, len(names)) baseCounts := make(map[string]int, len(names)) for _, name := range names { if name == "" { continue } if _, exists := uniqueNames[name]; exists { continue } uniqueNames[name] = struct{}{} baseCounts[SanitizeFunctionName(name)]++ }
sortedNames := make([]string, 0, len(uniqueNames)) for name := range uniqueNames { sortedNames = append(sortedNames, name) } sort.Strings(sortedNames)
out := make(map[string]string, len(sortedNames)) used := make(map[string]string, len(sortedNames)) for _, name := range sortedNames { base := SanitizeFunctionName(name) mapped := base _, baseUsed := used[base] if baseCounts[base] > 1 || baseUsed { mapped = disambiguateResponsesSanitizedName(base, name, used) } out[name] = mapped used[mapped] = name } return out}
func disambiguateResponsesSanitizedName(base, original string, used map[string]string) string { for attempt := 0; ; attempt++ { digest := sha256.Sum256([]byte(fmt.Sprintf("%s\x00%d", original, attempt))) suffix := "_" + hex.EncodeToString(digest[:6]) prefix := base if maxPrefix := 64 - len(suffix); len(prefix) > maxPrefix { prefix = prefix[:maxPrefix] } candidate := prefix + suffix if _, exists := used[candidate]; !exists { return candidate } }}
// BuildGeminiFunctionDeclarations builds Gemini function declarations, forward name mapping, and reverse identity mapping.func BuildGeminiFunctionDeclarations(root gjson.Result) ([][]byte, map[string]string, map[string]ResponsesToolIdentity) { descriptors := CollectResponsesToolDescriptors(root) winners := CollectResponsesToolWinners(root)
seenNames := make(map[string]struct{}) var winningList []ResponsesToolDescriptor for _, descriptor := range descriptors { winner, ok := winners[descriptor.Name] if !ok || winner.Order != descriptor.Order { continue } if _, seen := seenNames[descriptor.Name]; seen { continue } seenNames[descriptor.Name] = struct{}{} winningList = append(winningList, descriptor) }
if len(winningList) == 0 { return nil, nil, nil }
qualifiedNames := make([]string, 0, len(winningList)) for _, desc := range winningList { qualifiedNames = append(qualifiedNames, desc.Name) } sanitizedMap := sanitizeResponsesToolNames(qualifiedNames)
forwardMap := make(map[string]string, len(winningList)*2) reverseMap := make(map[string]ResponsesToolIdentity, len(winningList)*2) var declarations [][]byte
for _, desc := range winningList { geminiName := desc.Name if mapped, ok := sanitizedMap[desc.Name]; ok && mapped != "" { geminiName = mapped } else { geminiName = SanitizeFunctionName(desc.Name) }
forwardMap[desc.Name] = geminiName if desc.LocalName != "" && desc.LocalName != desc.Name { if _, exists := forwardMap[desc.LocalName]; !exists { forwardMap[desc.LocalName] = geminiName } }
identity := ResponsesToolIdentity{ Name: desc.LocalName, Namespace: desc.Namespace, Custom: desc.ToolType == "custom", } reverseMap[geminiName] = identity if desc.Name != geminiName { reverseMap[desc.Name] = identity }
funcDecl := []byte(`{"name":"","description":"","parametersJsonSchema":{}}`) funcDecl, _ = sjson.SetBytes(funcDecl, "name", geminiName) if descStr := responsesToolDescription(desc.Tool); descStr != "" { funcDecl, _ = sjson.SetBytes(funcDecl, "description", descStr) }
if desc.ToolType == "custom" { funcDecl, _ = sjson.SetRawBytes(funcDecl, "parametersJsonSchema", []byte(`{"type":"object","properties":{"input":{"type":"string"}},"required":["input"]}`)) } else { params := responsesToolParameters(desc.Tool) if params.Exists() { funcDecl, _ = sjson.SetRawBytes(funcDecl, "parametersJsonSchema", []byte(CleanJSONSchemaForGemini(params.Raw))) } } declarations = append(declarations, funcDecl) }
return declarations, forwardMap, reverseMap}
// ResponsesToolReverseIdentityMap builds a Gemini function name -> ResponsesToolIdentity map from a Responses request raw JSON.func ResponsesToolReverseIdentityMap(rawJSON []byte) map[string]ResponsesToolIdentity { if len(rawJSON) == 0 || !gjson.ValidBytes(rawJSON) { return nil } root := gjson.ParseBytes(rawJSON) if req := root.Get("request"); req.Exists() && (req.Get("model").Exists() || req.Get("input").Exists() || req.Get("tools").Exists()) { root = req } _, _, reverseMap := BuildGeminiFunctionDeclarations(root) return reverseMap}
// MapResponsesToolName returns the mapped Gemini function name if present in forwardMap, else sanitized name.func MapResponsesToolName(forwardMap map[string]string, name string) string { if mapped, ok := forwardMap[name]; ok && mapped != "" { return mapped } return SanitizeFunctionName(name)}
// ConvertResponsesToolChoiceToGemini translates Responses tool_choice into Gemini functionCallingConfig JSON.func ConvertResponsesToolChoiceToGemini(toolChoice gjson.Result, forwardMap map[string]string) ([]byte, bool) { if !toolChoice.Exists() { return nil, false } mode := "" var allowedNames []string if toolChoice.Type == gjson.String { switch strings.ToLower(strings.TrimSpace(toolChoice.String())) { case "none": mode = "NONE" case "auto": mode = "AUTO" case "required", "any": mode = "ANY" } } else if toolChoice.IsObject() { toolType := strings.ToLower(strings.TrimSpace(toolChoice.Get("type").String())) switch toolType { case "none": mode = "NONE" case "auto": mode = "AUTO" case "required", "any": mode = "ANY" case "function", "custom", "tool", "": mode = "ANY" name := strings.TrimSpace(toolChoice.Get("name").String()) if name == "" { name = strings.TrimSpace(toolChoice.Get("function.name").String()) } if name == "" { name = strings.TrimSpace(toolChoice.Get("custom.name").String()) } namespace := strings.TrimSpace(toolChoice.Get("namespace").String()) if namespace == "" { namespace = strings.TrimSpace(toolChoice.Get("function.namespace").String()) } if namespace == "" { namespace = strings.TrimSpace(toolChoice.Get("custom.namespace").String()) } if namespace != "" { name = QualifyResponsesNamespaceToolName(namespace, name) } if name != "" { geminiName := MapResponsesToolName(forwardMap, name) allowedNames = append(allowedNames, geminiName) } } } if mode == "" { return nil, false } cfg := []byte(`{"mode":""}`) cfg, _ = sjson.SetBytes(cfg, "mode", mode) if len(allowedNames) > 0 { cfg, _ = sjson.SetBytes(cfg, "allowedFunctionNames", allowedNames) } return cfg, true}
// UnwrapResponsesCustomToolInput extracts the raw input string from custom tool arguments JSON or plain string.func UnwrapResponsesCustomToolInput(arguments string) string { arguments = strings.TrimSpace(arguments) if arguments == "" || arguments == "{}" { return "" } if gjson.Valid(arguments) { parsed := gjson.Parse(arguments) if v := parsed.Get("input"); v.Exists() { if v.Type == gjson.String { return v.String() } return v.Raw } if parsed.Type == gjson.String { return parsed.String() } } return arguments}