vibe-proxy/backend/internal/translator/antigravity/interactions/interactions_antigravity_request.go
2026-08-24 00:10:41 +02:00

793 lines
28 KiB
Go

package interactions
import (
"encoding/json"
"fmt"
"strings"
translatorcommon "github.com/router-for-me/CLIProxyAPI/v7/internal/translator/common"
"github.com/router-for-me/CLIProxyAPI/v7/internal/util"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
func ConvertInteractionsRequestToAntigravity(modelName string, inputRawJSON []byte, stream bool) []byte {
root := gjson.ParseBytes(inputRawJSON)
functionNameMap := util.SanitizedFunctionNameMap(inputRawJSON)
out := []byte(`{"project":"","request":{"contents":[]},"model":""}`)
out, _ = sjson.SetBytes(out, "model", modelName)
if stream || root.Get("stream").Bool() {
out, _ = sjson.SetBytes(out, "request.stream", true)
}
out = copyInteractionsSystemToAntigravity(out, root)
out = copyInteractionsGenerationConfigToAntigravity(out, root)
contentItems := translatorcommon.NewRawArrayItems(root.Get("input.#").Int())
appendInteractionsInputToAntigravity(&contentItems, root.Get("input"))
out = translatorcommon.SetRawArrayItems(out, "request.contents", contentItems)
out = copyInteractionsToolsToAntigravity(out, root, functionNameMap)
out = rewriteInteractionsFunctionNames(out, functionNameMap)
out = attachDefaultAntigravitySafetySettings(out)
return out
}
func rewriteInteractionsFunctionNames(out []byte, functionNameMap map[string]string) []byte {
contents := gjson.GetBytes(out, "request.contents")
canBatchContents := contents.IsArray()
if canBatchContents {
contents.ForEach(func(_, content gjson.Result) bool {
parts := content.Get("parts")
if parts.Exists() && !parts.IsArray() {
canBatchContents = false
return false
}
return true
})
}
if canBatchContents {
contentsChanged := false
contentItems := translatorcommon.NewRawArrayItems(contents.Get("#").Int())
contents.ForEach(func(_, content gjson.Result) bool {
contentJSON := []byte(content.Raw)
partsChanged := false
partItems := make([][]byte, 0, 4)
content.Get("parts").ForEach(func(_, part gjson.Result) bool {
partJSON := []byte(part.Raw)
for _, field := range []string{"functionCall", "functionResponse"} {
nameResult := part.Get(field + ".name")
name := nameResult.String()
if name == "" {
continue
}
mappedName := util.MapSanitizedFunctionName(functionNameMap, name)
if nameResult.Type == gjson.String && mappedName == name {
continue
}
partJSON, _ = sjson.SetBytes(partJSON, field+".name", mappedName)
partsChanged = true
}
partItems = append(partItems, partJSON)
return true
})
if partsChanged {
contentJSON, _ = sjson.SetRawBytes(contentJSON, "parts", translatorcommon.JoinRawArray(partItems))
contentsChanged = true
}
contentItems = append(contentItems, contentJSON)
return true
})
if contentsChanged {
out, _ = sjson.SetRawBytes(out, "request.contents", translatorcommon.JoinRawArray(contentItems))
}
} else {
for contentIndex, content := range contents.Array() {
for partIndex, part := range content.Get("parts").Array() {
for _, field := range []string{"functionCall", "functionResponse"} {
nameResult := part.Get(field + ".name")
name := nameResult.String()
if name == "" {
continue
}
mappedName := util.MapSanitizedFunctionName(functionNameMap, name)
if nameResult.Type == gjson.String && mappedName == name {
continue
}
path := fmt.Sprintf("request.contents.%d.parts.%d.%s.name", contentIndex, partIndex, field)
out, _ = sjson.SetBytes(out, path, mappedName)
}
}
}
}
allowedPath := "request.toolConfig.functionCallingConfig.allowedFunctionNames"
allowedNames := gjson.GetBytes(out, allowedPath)
if allowedNames.IsArray() {
namesChanged := false
nameItems := make([][]byte, 0, 4)
allowedNames.ForEach(func(_, name gjson.Result) bool {
mappedName := util.MapSanitizedFunctionName(functionNameMap, name.String())
namesChanged = namesChanged || name.Type != gjson.String || mappedName != name.String()
mappedNameJSON, _ := json.Marshal(mappedName)
nameItems = append(nameItems, mappedNameJSON)
return true
})
if namesChanged {
out, _ = sjson.SetRawBytes(out, allowedPath, translatorcommon.JoinRawArray(nameItems))
}
} else {
for index, name := range allowedNames.Array() {
mappedName := util.MapSanitizedFunctionName(functionNameMap, name.String())
if name.Type == gjson.String && mappedName == name.String() {
continue
}
path := fmt.Sprintf("%s.%d", allowedPath, index)
out, _ = sjson.SetBytes(out, path, mappedName)
}
}
return out
}
func copyInteractionsSystemToAntigravity(out []byte, root gjson.Result) []byte {
sys := root.Get("system_instruction")
if !sys.Exists() {
return out
}
if sys.Type == gjson.String {
instr := []byte(`{"parts":[{"text":""}]}`)
instr, _ = sjson.SetBytes(instr, "parts.0.text", sys.String())
out, _ = sjson.SetRawBytes(out, "request.systemInstruction", instr)
return out
}
if text := sys.Get("text"); text.Exists() && !sys.Get("parts").Exists() {
instr := []byte(`{"parts":[{"text":""}]}`)
instr, _ = sjson.SetBytes(instr, "parts.0.text", text.String())
out, _ = sjson.SetRawBytes(out, "request.systemInstruction", instr)
return out
}
out, _ = sjson.SetRawBytes(out, "request.systemInstruction", []byte(sys.Raw))
return out
}
func copyInteractionsGenerationConfigToAntigravity(out []byte, root gjson.Result) []byte {
if cfg := root.Get("generation_config"); cfg.Exists() {
out, _ = sjson.SetRawBytes(out, "request.generationConfig", convertSnakeCaseKeysToCamelCaseForAntigravity([]byte(cfg.Raw)))
} else if cfg := root.Get("generationConfig"); cfg.Exists() {
out, _ = sjson.SetRawBytes(out, "request.generationConfig", []byte(cfg.Raw))
}
out = normalizeInteractionsGenerationConfigForAntigravity(out)
out = copyInteractionsReasoningToAntigravity(out, root)
out = copyInteractionsResponseModalitiesToAntigravity(out, root)
out = copyInteractionsToolChoiceToAntigravity(out, root)
return out
}
func normalizeInteractionsGenerationConfigForAntigravity(out []byte) []byte {
if thinkingLevel := gjson.GetBytes(out, "request.generationConfig.thinkingLevel"); thinkingLevel.Exists() {
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.thinkingLevel", []byte(thinkingLevel.Raw))
out, _ = sjson.DeleteBytes(out, "request.generationConfig.thinkingLevel")
}
if thinkingBudget := gjson.GetBytes(out, "request.generationConfig.thinkingBudget"); thinkingBudget.Exists() {
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.thinkingBudget", []byte(thinkingBudget.Raw))
out, _ = sjson.DeleteBytes(out, "request.generationConfig.thinkingBudget")
}
if includeThoughts := gjson.GetBytes(out, "request.generationConfig.includeThoughts"); includeThoughts.Exists() {
out, _ = sjson.SetRawBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", []byte(includeThoughts.Raw))
out, _ = sjson.DeleteBytes(out, "request.generationConfig.includeThoughts")
}
if summaries := gjson.GetBytes(out, "request.generationConfig.thinkingSummaries"); summaries.Exists() {
if includeThoughts, ok := antigravityThinkingSummariesIncludeThoughts(summaries); ok {
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)
}
out, _ = sjson.DeleteBytes(out, "request.generationConfig.thinkingSummaries")
}
if toolChoice := gjson.GetBytes(out, "request.generationConfig.toolChoice"); toolChoice.Exists() {
out, _ = sjson.DeleteBytes(out, "request.generationConfig.toolChoice")
}
return out
}
func copyInteractionsReasoningToAntigravity(out []byte, root gjson.Result) []byte {
reasoning := root.Get("reasoning")
if !reasoning.Exists() {
return out
}
effort := strings.ToLower(strings.TrimSpace(reasoning.Get("effort").String()))
if effort == "" {
effort = strings.ToLower(strings.TrimSpace(reasoning.Get("thinking_level").String()))
}
if effort != "" {
// Thinking amount and summary visibility are independent. This OpenAI-style
// compatibility alias controls only the amount; includeThoughts is written
// below only for an explicit Interactions summary selector.
if effort == "auto" {
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.thinkingBudget", -1)
} else {
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.thinkingLevel", effort)
}
}
if summary := reasoning.Get("summary"); summary.Exists() {
if includeThoughts, ok := antigravityThinkingSummariesIncludeThoughts(summary); ok {
out, _ = sjson.SetBytes(out, "request.generationConfig.thinkingConfig.includeThoughts", includeThoughts)
}
}
return out
}
func copyInteractionsResponseModalitiesToAntigravity(out []byte, root gjson.Result) []byte {
mods := root.Get("response_modalities")
if !mods.Exists() {
mods = root.Get("responseModalities")
}
if !mods.Exists() || !mods.IsArray() {
return out
}
var responseMods []string
mods.ForEach(func(_, mod gjson.Result) bool {
switch strings.ToLower(strings.TrimSpace(mod.String())) {
case "text":
responseMods = append(responseMods, "TEXT")
case "image":
responseMods = append(responseMods, "IMAGE")
case "audio":
responseMods = append(responseMods, "AUDIO")
}
return true
})
if len(responseMods) > 0 {
out, _ = sjson.SetBytes(out, "request.generationConfig.responseModalities", responseMods)
}
return out
}
func copyInteractionsToolChoiceToAntigravity(out []byte, root gjson.Result) []byte {
toolChoice := root.Get("tool_choice")
if !toolChoice.Exists() {
toolChoice = root.Get("generation_config.tool_choice")
}
if !toolChoice.Exists() {
toolChoice = root.Get("generationConfig.toolChoice")
}
if !toolChoice.Exists() {
return out
}
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() {
switch strings.ToLower(strings.TrimSpace(toolChoice.Get("type").String())) {
case "none":
mode = "NONE"
case "auto":
mode = "AUTO"
case "required", "any":
mode = "ANY"
case "function":
mode = "ANY"
if name := toolChoice.Get("function.name").String(); strings.TrimSpace(name) != "" {
allowedNames = append(allowedNames, name)
}
case "tool":
mode = "ANY"
if name := toolChoice.Get("name").String(); strings.TrimSpace(name) != "" {
allowedNames = append(allowedNames, name)
}
}
}
if mode == "" {
return out
}
out, _ = sjson.SetBytes(out, "request.toolConfig.functionCallingConfig.mode", mode)
if len(allowedNames) > 0 {
out, _ = sjson.SetBytes(out, "request.toolConfig.functionCallingConfig.allowedFunctionNames", allowedNames)
}
return out
}
func appendInteractionsInputToAntigravity(items *[][]byte, input gjson.Result) {
if !input.Exists() {
return
}
if input.Type == gjson.String {
appendAntigravityTextContent(items, "user", input.String())
return
}
if input.IsArray() {
input.ForEach(func(_, item gjson.Result) bool {
appendInteractionsStepToAntigravity(items, item, "user")
return true
})
return
}
if steps := input.Get("steps"); steps.Exists() && steps.IsArray() {
defaultRole := "user"
if role := input.Get("role").String(); role == "model" || role == "assistant" {
defaultRole = "model"
}
steps.ForEach(func(_, step gjson.Result) bool {
appendInteractionsStepToAntigravity(items, step, defaultRole)
return true
})
return
}
appendInteractionsStepToAntigravity(items, input, "user")
}
func appendInteractionsStepToAntigravity(items *[][]byte, step gjson.Result, defaultRole string) {
if step.Type == gjson.String {
appendAntigravityTextContent(items, defaultRole, step.String())
return
}
if steps := step.Get("steps"); steps.Exists() && steps.IsArray() {
role := defaultRole
if itemRole := step.Get("role").String(); itemRole == "model" || itemRole == "assistant" {
role = "model"
} else if itemRole == "user" {
role = "user"
}
steps.ForEach(func(_, child gjson.Result) bool {
appendInteractionsStepToAntigravity(items, child, role)
return true
})
return
}
switch step.Get("type").String() {
case "model_output":
appendInteractionsStepContentToAntigravity(items, "model", step, false)
case "thought":
appendInteractionsStepContentToAntigravity(items, "model", step, true)
case "function_call":
appendInteractionsFunctionCallToAntigravity(items, step)
case "function_result":
appendInteractionsFunctionResultToAntigravity(items, step)
case "user_input", "":
if step.Get("parts").Exists() {
appendInteractionsNativeContentToAntigravity(items, step, defaultRole)
} else {
appendInteractionsContentListToAntigravity(items, defaultRole, step.Get("content"))
}
default:
if step.Get("parts").Exists() {
appendInteractionsNativeContentToAntigravity(items, step, defaultRole)
} else if step.Get("content").Exists() {
appendInteractionsContentListToAntigravity(items, defaultRole, step.Get("content"))
} else if text := step.Get("text"); text.Exists() {
appendAntigravityTextContent(items, defaultRole, text.String())
}
}
}
func appendInteractionsNativeContentToAntigravity(items *[][]byte, step gjson.Result, defaultRole string) {
parts := step.Get("parts")
if !parts.Exists() || !parts.IsArray() {
return
}
partItems := make([][]byte, 0, 4)
parts.ForEach(func(_, part gjson.Result) bool {
if partJSON := interactionsNativeAntigravityPart(part); len(partJSON) > 0 {
partItems = append(partItems, partJSON)
}
return true
})
if len(partItems) > 0 {
role := antigravityContentRole(step.Get("role").String(), defaultRole)
*items = append(*items, antigravityContent(role, partItems))
}
}
func appendInteractionsStepContentToAntigravity(items *[][]byte, role string, step gjson.Result, thought bool) {
content := step.Get("content")
if !content.Exists() {
return
}
partItems := make([][]byte, 0, 4)
if content.IsArray() {
content.ForEach(func(_, part gjson.Result) bool {
if partJSON := appendInteractionsContentToAntigravityPart(nil, part, thought); len(partJSON) > 0 {
partItems = append(partItems, partJSON)
}
return true
})
} else if content.IsObject() {
if partJSON := appendInteractionsContentToAntigravityPart(nil, content, thought); len(partJSON) > 0 {
partItems = append(partItems, partJSON)
}
} else if content.Type == gjson.String {
partItems = append(partItems, antigravityTextPartJSON(content.String(), thought))
}
if len(partItems) > 0 {
*items = append(*items, antigravityContent(role, partItems))
}
}
func appendInteractionsContentListToAntigravity(items *[][]byte, role string, content gjson.Result) {
if !content.Exists() {
return
}
if content.IsArray() {
content.ForEach(func(_, part gjson.Result) bool {
appendInteractionsContentPartToAntigravity(items, role, part)
return true
})
return
}
if content.IsObject() {
appendInteractionsContentPartToAntigravity(items, role, content)
} else if content.Type == gjson.String {
appendAntigravityTextContent(items, role, content.String())
}
}
func appendInteractionsContentPartToAntigravity(items *[][]byte, role string, part gjson.Result) {
partJSON := appendInteractionsContentToAntigravityPart(nil, part, false)
if len(partJSON) > 0 {
*items = append(*items, antigravityContent(role, [][]byte{partJSON}))
}
}
func appendInteractionsContentToAntigravityPart(_ []byte, content gjson.Result, thought bool) []byte {
if text := content.Get("text"); text.Exists() {
return antigravityTextPartJSON(text.String(), thought)
}
if inline := content.Get("inline_data"); inline.Exists() {
return antigravityInlineDataPartJSON(inline)
}
if inline := content.Get("inlineData"); inline.Exists() {
return antigravityInlineDataPartJSON(inline)
}
switch strings.ToLower(strings.TrimSpace(content.Get("type").String())) {
case "text":
if text := content.Get("text"); text.Exists() {
return antigravityTextPartJSON(text.String(), thought)
}
case "image", "audio", "video", "document":
if mime := content.Get("mime_type"); mime.Exists() || content.Get("mimeType").Exists() {
mimeType := mime.String()
if mimeType == "" {
mimeType = content.Get("mimeType").String()
}
if data := content.Get("data").String(); data != "" {
return antigravityInlineDataPartJSON(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, mimeType, data)))
}
}
if uri := content.Get("file_uri"); uri.Exists() || content.Get("fileUri").Exists() {
fileURI := uri.String()
if fileURI == "" {
fileURI = content.Get("fileUri").String()
}
mimeType := content.Get("mime_type").String()
if mimeType == "" {
mimeType = content.Get("mimeType").String()
}
return antigravityFileDataPartJSON(gjson.Parse(fmt.Sprintf(`{"mimeType":%q,"fileUri":%q}`, mimeType, fileURI)))
}
if url := content.Get("url"); url.Exists() {
return antigravityInlineDataPartFromDataURL(url.String())
}
case "image_url":
return antigravityInlineDataPartFromDataURL(content.Get("image_url.url").String())
case "input_audio":
mimeType := antigravityInputAudioMimeType(content.Get("input_audio.format").String())
return antigravityInlineDataPartJSON(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, mimeType, content.Get("input_audio.data").String())))
case "file":
filename := content.Get("file.filename").String()
fileData := content.Get("file.file_data").String()
if mimeType, data, ok := translatorcommon.NormalizeOpenAIFileData(filename, "", fileData); ok {
return antigravityInlineDataPartJSON(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, mimeType, data)))
}
}
return nil
}
func appendInteractionsFunctionCallToAntigravity(items *[][]byte, step gjson.Result) {
part := []byte(`{"functionCall":{"name":"","args":{}}}`)
part, _ = sjson.SetBytes(part, "functionCall.name", step.Get("name").String())
if callID := step.Get("call_id"); callID.Exists() {
part, _ = sjson.SetBytes(part, "functionCall.id", callID.String())
} else if id := step.Get("id"); id.Exists() {
part, _ = sjson.SetBytes(part, "functionCall.id", id.String())
}
if args := step.Get("arguments"); args.Exists() {
part, _ = sjson.SetRawBytes(part, "functionCall.args", []byte(args.Raw))
}
*items = append(*items, antigravityContent("model", [][]byte{part}))
}
func appendInteractionsFunctionResultToAntigravity(items *[][]byte, step gjson.Result) {
part := []byte(`{"functionResponse":{"name":"","response":{}}}`)
part, _ = sjson.SetBytes(part, "functionResponse.name", step.Get("name").String())
if callID := step.Get("call_id"); callID.Exists() {
part, _ = sjson.SetBytes(part, "functionResponse.id", callID.String())
} else if id := step.Get("id"); id.Exists() {
part, _ = sjson.SetBytes(part, "functionResponse.id", id.String())
}
if result := step.Get("result"); result.Exists() {
part, _ = sjson.SetRawBytes(part, "functionResponse.response", []byte(result.Raw))
}
*items = append(*items, antigravityContent("user", [][]byte{part}))
}
func copyInteractionsToolsToAntigravity(out []byte, root gjson.Result, functionNameMap map[string]string) []byte {
tools := root.Get("tools")
if !tools.Exists() {
return out
}
if !tools.IsArray() {
out, _ = sjson.SetRawBytes(out, "request.tools", []byte(tools.Raw))
return out
}
var functionDeclarations [][]byte
var otherTools [][]byte
tools.ForEach(func(_, tool gjson.Result) bool {
if decls := tool.Get("functionDeclarations"); decls.Exists() && decls.IsArray() {
decls.ForEach(func(_, decl gjson.Result) bool {
if converted := antigravityFunctionDeclarationJSON(decl, functionNameMap); len(converted) > 0 {
functionDeclarations = append(functionDeclarations, converted)
}
return true
})
return true
}
if decls := tool.Get("function_declarations"); decls.Exists() && decls.IsArray() {
decls.ForEach(func(_, decl gjson.Result) bool {
if converted := antigravityFunctionDeclarationJSON(decl, functionNameMap); len(converted) > 0 {
functionDeclarations = append(functionDeclarations, converted)
}
return true
})
return true
}
if tool.Get("type").String() == "function" || tool.Get("name").Exists() {
if converted := antigravityFunctionDeclarationJSON(tool, functionNameMap); len(converted) > 0 {
functionDeclarations = append(functionDeclarations, converted)
}
return true
}
otherTools = append(otherTools, []byte(tool.Raw))
return true
})
deduplicated := util.DeduplicateFunctionDeclarations(translatorcommon.JoinRawArray(functionDeclarations))
hasFunction := len(deduplicated) > 2
if hasFunction || len(otherTools) > 0 {
toolItems := make([][]byte, 0, 1+len(otherTools))
if hasFunction {
functionToolNode := []byte(`{"functionDeclarations":[]}`)
functionToolNode, _ = sjson.SetRawBytes(functionToolNode, "functionDeclarations", deduplicated)
toolItems = append(toolItems, functionToolNode)
}
toolItems = append(toolItems, otherTools...)
out, _ = sjson.SetRawBytes(out, "request.tools", translatorcommon.JoinRawArray(toolItems))
}
return out
}
func antigravityFunctionDeclarationJSON(decl gjson.Result, functionNameMap map[string]string) []byte {
fn := decl
if nested := decl.Get("function"); nested.Exists() && nested.IsObject() {
fn = nested
}
name := fn.Get("name").String()
if strings.TrimSpace(name) == "" {
return nil
}
out := []byte(`{"name":"","parametersJsonSchema":{"type":"object","properties":{}}}`)
out, _ = sjson.SetBytes(out, "name", util.MapSanitizedFunctionName(functionNameMap, name))
if desc := fn.Get("description"); desc.Exists() {
out, _ = sjson.SetBytes(out, "description", desc.String())
}
if params := fn.Get("parametersJsonSchema"); params.Exists() {
out, _ = sjson.SetRawBytes(out, "parametersJsonSchema", []byte(params.Raw))
} else if params := fn.Get("parameters"); params.Exists() {
out, _ = sjson.SetRawBytes(out, "parametersJsonSchema", []byte(params.Raw))
}
if response := fn.Get("response"); response.Exists() {
out, _ = sjson.SetRawBytes(out, "response", []byte(response.Raw))
}
if responseSchema := fn.Get("responseJsonSchema"); responseSchema.Exists() {
out, _ = sjson.SetRawBytes(out, "responseJsonSchema", []byte(responseSchema.Raw))
}
return out
}
func interactionsNativeAntigravityPart(part gjson.Result) []byte {
switch {
case part.Get("text").Exists(), part.Get("functionCall").Exists(), part.Get("functionResponse").Exists():
return []byte(part.Raw)
case part.Get("inlineData").Exists():
return antigravityInlineDataPartJSON(part.Get("inlineData"))
case part.Get("fileData").Exists():
return antigravityFileDataPartJSON(part.Get("fileData"))
case part.Get("inline_data").Exists():
return antigravityInlineDataPartJSON(part.Get("inline_data"))
case part.Get("file_data").Exists():
return antigravityFileDataPartJSON(part.Get("file_data"))
}
return nil
}
func antigravityTextPartJSON(text string, thought bool) []byte {
partJSON := []byte(`{"text":""}`)
partJSON, _ = sjson.SetBytes(partJSON, "text", text)
if thought {
partJSON, _ = sjson.SetBytes(partJSON, "thought", true)
}
return partJSON
}
func antigravityInlineDataPartJSON(inline gjson.Result) []byte {
mimeType := inline.Get("mimeType").String()
if mimeType == "" {
mimeType = inline.Get("mime_type").String()
}
data := inline.Get("data").String()
if mimeType == "" || data == "" {
return nil
}
partJSON := []byte(`{"inlineData":{"mimeType":"","data":""}}`)
partJSON, _ = sjson.SetBytes(partJSON, "inlineData.mimeType", mimeType)
partJSON, _ = sjson.SetBytes(partJSON, "inlineData.data", data)
return partJSON
}
func antigravityFileDataPartJSON(fileData gjson.Result) []byte {
mimeType := fileData.Get("mimeType").String()
if mimeType == "" {
mimeType = fileData.Get("mime_type").String()
}
fileURI := fileData.Get("fileUri").String()
if fileURI == "" {
fileURI = fileData.Get("file_uri").String()
}
if mimeType == "" || fileURI == "" {
return nil
}
partJSON := []byte(`{"fileData":{"mimeType":"","fileUri":""}}`)
partJSON, _ = sjson.SetBytes(partJSON, "fileData.mimeType", mimeType)
partJSON, _ = sjson.SetBytes(partJSON, "fileData.fileUri", fileURI)
return partJSON
}
func antigravityInlineDataPartFromDataURL(dataURL string) []byte {
if !strings.HasPrefix(dataURL, "data:") {
return nil
}
payload := dataURL[5:]
pieces := strings.SplitN(payload, ";", 2)
if len(pieces) != 2 || !strings.HasPrefix(pieces[1], "base64,") {
return nil
}
return antigravityInlineDataPartJSON(gjson.Parse(fmt.Sprintf(`{"mime_type":%q,"data":%q}`, pieces[0], pieces[1][7:])))
}
func appendAntigravityTextContent(items *[][]byte, role, text string) {
part := antigravityTextPartJSON(text, false)
*items = append(*items, antigravityContent(antigravityContentRole(role, "user"), [][]byte{part}))
}
func antigravityContent(role string, parts [][]byte) []byte {
content := []byte(`{"role":"","parts":[]}`)
content, _ = sjson.SetBytes(content, "role", role)
content, _ = sjson.SetRawBytes(content, "parts", translatorcommon.JoinRawArray(parts))
return content
}
func antigravityContentRole(role, defaultRole string) string {
switch strings.ToLower(strings.TrimSpace(role)) {
case "model", "assistant":
return "model"
case "user":
return "user"
}
if defaultRole == "model" {
return "model"
}
return "user"
}
func antigravityInputAudioMimeType(format string) string {
switch strings.ToLower(strings.TrimSpace(format)) {
case "wav":
return "audio/wav"
case "mp3":
return "audio/mpeg"
case "flac":
return "audio/flac"
case "opus":
return "audio/opus"
case "pcm16":
return "audio/pcm"
default:
return "audio/mpeg"
}
}
func antigravityThinkingSummariesIncludeThoughts(summary gjson.Result) (bool, bool) {
if summary.Type != gjson.String {
return false, false
}
switch strings.ToLower(strings.TrimSpace(summary.String())) {
case "auto":
return true, true
case "none":
return false, true
default:
return false, false
}
}
func convertSnakeCaseKeysToCamelCaseForAntigravity(raw []byte) []byte {
root := gjson.ParseBytes(raw)
if !root.Exists() {
return raw
}
out := []byte(`{}`)
out = copySnakeCaseValueToCamelCaseForAntigravity(out, "", root)
return out
}
func copySnakeCaseValueToCamelCaseForAntigravity(out []byte, path string, node gjson.Result) []byte {
if node.IsObject() {
node.ForEach(func(key, value gjson.Result) bool {
childPath := joinAntigravityJSONPath(path, toAntigravityCamelCase(key.String()))
out = copySnakeCaseValueToCamelCaseForAntigravity(out, childPath, value)
return true
})
return out
}
if node.IsArray() {
node.ForEach(func(_, value gjson.Result) bool {
out = copySnakeCaseValueToCamelCaseForAntigravity(out, path+".-1", value)
return true
})
return out
}
out, _ = sjson.SetRawBytes(out, path, []byte(node.Raw))
return out
}
func joinAntigravityJSONPath(path, key string) string {
if path == "" {
return key
}
return path + "." + key
}
func toAntigravityCamelCase(s string) string {
parts := strings.Split(s, "_")
if len(parts) == 0 {
return s
}
out := parts[0]
for _, part := range parts[1:] {
if part == "" {
continue
}
out += strings.ToUpper(part[:1]) + part[1:]
}
return out
}
func attachDefaultAntigravitySafetySettings(out []byte) []byte {
if gjson.GetBytes(out, "request.safetySettings").Exists() {
return out
}
settings := []map[string]string{
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF"},
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "OFF"},
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "OFF"},
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF"},
{"category": "HARM_CATEGORY_CIVIC_INTEGRITY", "threshold": "BLOCK_NONE"},
}
raw, errMarshal := json.Marshal(settings)
if errMarshal != nil {
return out
}
out, _ = sjson.SetRawBytes(out, "request.safetySettings", raw)
return out
}