494 lines
19 KiB
Go
494 lines
19 KiB
Go
package interactions
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
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"
|
|
)
|
|
|
|
type antigravityToInteractionsStreamState struct {
|
|
Started bool
|
|
Finished bool
|
|
Completed bool
|
|
Done bool
|
|
ActiveStepOpen bool
|
|
ID string
|
|
StepID string
|
|
ActiveStepType string
|
|
ActiveStepIndex int
|
|
StepIndex int
|
|
ToolNameMap map[string]string
|
|
}
|
|
|
|
func ConvertAntigravityResponseToInteractions(ctx context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, param *any) [][]byte {
|
|
_ = ctx
|
|
_ = originalRequestRawJSON
|
|
_ = requestRawJSON
|
|
if param == nil {
|
|
var local any
|
|
param = &local
|
|
}
|
|
if *param == nil {
|
|
*param = &antigravityToInteractionsStreamState{
|
|
ID: fmt.Sprintf("interaction_%d", time.Now().UnixNano()),
|
|
ToolNameMap: util.DisambiguatedToolNameMap(originalRequestRawJSON),
|
|
}
|
|
}
|
|
st := (*param).(*antigravityToInteractionsStreamState)
|
|
payloads := antigravityStreamPayloads(rawJSON)
|
|
out := make([][]byte, 0)
|
|
for _, payload := range payloads {
|
|
if bytes.Equal(bytes.TrimSpace(payload), []byte("[DONE]")) {
|
|
if !st.Completed {
|
|
out = appendAntigravityInteractionsStepStop(out, st)
|
|
out = appendAntigravityInteractionsCompleted(out, st, modelName, gjson.Result{})
|
|
}
|
|
out = appendAntigravityInteractionsDone(out, st)
|
|
continue
|
|
}
|
|
root := unwrapAntigravityResponse(gjson.ParseBytes(payload))
|
|
root = restoreInteractionsFunctionNames(root, st.ToolNameMap)
|
|
if !root.Exists() {
|
|
continue
|
|
}
|
|
if !st.Started {
|
|
out = appendAntigravityInteractionsCreated(out, st, modelName)
|
|
out = appendAntigravityInteractionsStatusUpdate(out, st)
|
|
st.Started = true
|
|
}
|
|
root.Get("candidates.0.content.parts").ForEach(func(_, part gjson.Result) bool {
|
|
out = appendAntigravityPartToInteractionsStream(out, st, part)
|
|
return true
|
|
})
|
|
hasFinish := root.Get("candidates.0.finishReason").Exists()
|
|
hasUsage := hasAntigravityStreamUsage(root)
|
|
if hasFinish && !st.Finished {
|
|
out = appendAntigravityInteractionsStepStop(out, st)
|
|
st.Finished = true
|
|
}
|
|
if hasUsage && st.Finished && !st.Completed {
|
|
out = appendAntigravityInteractionsCompleted(out, st, modelName, root)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func ConvertAntigravityResponseToInteractionsNonStream(ctx context.Context, modelName string, originalRequestRawJSON, requestRawJSON, rawJSON []byte, _ *any) []byte {
|
|
_ = ctx
|
|
_ = originalRequestRawJSON
|
|
_ = requestRawJSON
|
|
root := unwrapAntigravityResponse(gjson.ParseBytes(rawJSON))
|
|
root = restoreInteractionsFunctionNames(root, util.DisambiguatedToolNameMap(originalRequestRawJSON))
|
|
out := []byte(`{"id":"","object":"interaction","status":"completed","model":"","steps":[]}`)
|
|
id := root.Get("responseId").String()
|
|
if id == "" {
|
|
id = fmt.Sprintf("interaction_%d", time.Now().UnixNano())
|
|
}
|
|
out, _ = sjson.SetBytes(out, "id", id)
|
|
out, _ = sjson.SetBytes(out, "model", modelName)
|
|
var steps [][]byte
|
|
root.Get("candidates.0.content.parts").ForEach(func(_, part gjson.Result) bool {
|
|
if step := antigravityPartToInteractionsStep(part); len(step) > 0 {
|
|
steps = append(steps, step)
|
|
}
|
|
return true
|
|
})
|
|
if len(steps) > 0 {
|
|
out = translatorcommon.SetRawArrayItems(out, "steps", steps)
|
|
}
|
|
out = setInteractionsUsageFromAntigravity(out, "usage", root)
|
|
return out
|
|
}
|
|
|
|
func antigravityStreamPayloads(rawJSON []byte) [][]byte {
|
|
trimmed := bytes.TrimSpace(rawJSON)
|
|
if bytes.HasPrefix(trimmed, []byte("data:")) {
|
|
return [][]byte{bytes.TrimSpace(trimmed[5:])}
|
|
}
|
|
root := gjson.ParseBytes(trimmed)
|
|
if root.IsArray() {
|
|
payloads := make([][]byte, 0)
|
|
root.ForEach(func(_, item gjson.Result) bool {
|
|
if response := item.Get("response"); response.Exists() {
|
|
payloads = append(payloads, []byte(response.Raw))
|
|
} else if item.Exists() {
|
|
payloads = append(payloads, []byte(item.Raw))
|
|
}
|
|
return true
|
|
})
|
|
if len(payloads) > 0 {
|
|
return payloads
|
|
}
|
|
}
|
|
return [][]byte{trimmed}
|
|
}
|
|
|
|
func unwrapAntigravityResponse(root gjson.Result) gjson.Result {
|
|
if response := root.Get("response"); response.Exists() {
|
|
response = restoreAntigravityUsageMetadata(response)
|
|
return response
|
|
}
|
|
return restoreAntigravityUsageMetadata(root)
|
|
}
|
|
|
|
func restoreInteractionsFunctionNames(root gjson.Result, nameMap map[string]string) gjson.Result {
|
|
if !root.Exists() || len(nameMap) == 0 {
|
|
return root
|
|
}
|
|
raw := []byte(root.Raw)
|
|
candidates := root.Get("candidates")
|
|
for candidateIndex, candidate := range candidates.Array() {
|
|
for partIndex, part := range candidate.Get("content.parts").Array() {
|
|
for _, field := range []string{"functionCall", "functionResponse"} {
|
|
nameResult := part.Get(field + ".name")
|
|
name := nameResult.String()
|
|
if name == "" {
|
|
continue
|
|
}
|
|
restoredName := util.RestoreSanitizedToolName(nameMap, name)
|
|
if nameResult.Type == gjson.String && restoredName == name {
|
|
continue
|
|
}
|
|
path := fmt.Sprintf("candidates.%d.content.parts.%d.%s.name", candidateIndex, partIndex, field)
|
|
raw, _ = sjson.SetBytes(raw, path, restoredName)
|
|
}
|
|
}
|
|
}
|
|
return gjson.ParseBytes(raw)
|
|
}
|
|
|
|
func restoreAntigravityUsageMetadata(root gjson.Result) gjson.Result {
|
|
if !root.Get("usageMetadata").Exists() {
|
|
if cpaUsage := root.Get("cpaUsageMetadata"); cpaUsage.Exists() {
|
|
raw, _ := sjson.SetRawBytes([]byte(root.Raw), "usageMetadata", []byte(cpaUsage.Raw))
|
|
raw, _ = sjson.DeleteBytes(raw, "cpaUsageMetadata")
|
|
return gjson.ParseBytes(raw)
|
|
}
|
|
}
|
|
return root
|
|
}
|
|
|
|
func appendAntigravityInteractionsCreated(out [][]byte, st *antigravityToInteractionsStreamState, modelName string) [][]byte {
|
|
created := []byte(`{"interaction":{"id":"","status":"in_progress","object":"interaction","model":""},"event_type":"interaction.created"}`)
|
|
created, _ = sjson.SetBytes(created, "interaction.id", st.ID)
|
|
created, _ = sjson.SetBytes(created, "interaction.model", modelName)
|
|
return append(out, translatorcommon.SSEEventData("interaction.created", created))
|
|
}
|
|
|
|
func appendAntigravityInteractionsStatusUpdate(out [][]byte, st *antigravityToInteractionsStreamState) [][]byte {
|
|
statusUpdate := []byte(`{"interaction_id":"","status":"in_progress","event_type":"interaction.status_update"}`)
|
|
statusUpdate, _ = sjson.SetBytes(statusUpdate, "interaction_id", st.ID)
|
|
return append(out, translatorcommon.SSEEventData("interaction.status_update", statusUpdate))
|
|
}
|
|
|
|
func appendAntigravityInteractionsCompleted(out [][]byte, st *antigravityToInteractionsStreamState, modelName string, root gjson.Result) [][]byte {
|
|
now := time.Now().UTC().Format(time.RFC3339)
|
|
completed := []byte(`{"interaction":{"id":"","status":"completed","usage":{},"created":"","updated":"","service_tier":"standard","object":"interaction","model":""},"event_type":"interaction.completed"}`)
|
|
completed, _ = sjson.SetBytes(completed, "interaction.id", st.ID)
|
|
completed, _ = sjson.SetBytes(completed, "interaction.created", now)
|
|
completed, _ = sjson.SetBytes(completed, "interaction.updated", now)
|
|
completed, _ = sjson.SetBytes(completed, "interaction.model", modelName)
|
|
if root.Exists() {
|
|
completed = setInteractionsStreamUsageFromAntigravity(completed, "interaction.usage", root)
|
|
}
|
|
out = append(out, translatorcommon.SSEEventData("interaction.completed", completed))
|
|
st.Completed = true
|
|
return out
|
|
}
|
|
|
|
func appendAntigravityInteractionsDone(out [][]byte, st *antigravityToInteractionsStreamState) [][]byte {
|
|
if st.Done {
|
|
return out
|
|
}
|
|
out = append(out, translatorcommon.SSEEventData("done", []byte("[DONE]")))
|
|
st.Done = true
|
|
return out
|
|
}
|
|
|
|
func appendAntigravityInteractionsStepStart(out [][]byte, st *antigravityToInteractionsStreamState, stepType string, part gjson.Result) [][]byte {
|
|
st.StepID = fmt.Sprintf("step_%d", time.Now().UnixNano())
|
|
st.ActiveStepIndex = st.StepIndex
|
|
st.StepIndex++
|
|
st.ActiveStepType = stepType
|
|
st.ActiveStepOpen = true
|
|
stepStart := []byte(`{"index":0,"step":{"type":""},"event_type":"step.start"}`)
|
|
stepStart, _ = sjson.SetBytes(stepStart, "index", st.ActiveStepIndex)
|
|
stepStart, _ = sjson.SetBytes(stepStart, "step.type", stepType)
|
|
if stepType == "function_call" {
|
|
id := antigravityFunctionPartID(part)
|
|
if id == "" {
|
|
id = st.StepID
|
|
}
|
|
stepStart, _ = sjson.SetBytes(stepStart, "step.id", id)
|
|
stepStart, _ = sjson.SetBytes(stepStart, "step.call_id", id)
|
|
stepStart, _ = sjson.SetBytes(stepStart, "step.name", part.Get("name").String())
|
|
stepStart, _ = sjson.SetRawBytes(stepStart, "step.arguments", []byte(`{}`))
|
|
}
|
|
return append(out, translatorcommon.SSEEventData("step.start", stepStart))
|
|
}
|
|
|
|
func appendAntigravityInteractionsStepStop(out [][]byte, st *antigravityToInteractionsStreamState) [][]byte {
|
|
if !st.ActiveStepOpen {
|
|
return out
|
|
}
|
|
stepStop := []byte(`{"index":0,"event_type":"step.stop"}`)
|
|
stepStop, _ = sjson.SetBytes(stepStop, "index", st.ActiveStepIndex)
|
|
out = append(out, translatorcommon.SSEEventData("step.stop", stepStop))
|
|
st.ActiveStepOpen = false
|
|
st.ActiveStepType = ""
|
|
return out
|
|
}
|
|
|
|
func ensureAntigravityInteractionsStep(out [][]byte, st *antigravityToInteractionsStreamState, stepType string, part gjson.Result) [][]byte {
|
|
if st.ActiveStepOpen && st.ActiveStepType == stepType {
|
|
return out
|
|
}
|
|
out = appendAntigravityInteractionsStepStop(out, st)
|
|
return appendAntigravityInteractionsStepStart(out, st, stepType, part)
|
|
}
|
|
|
|
func appendAntigravityPartToInteractionsStream(out [][]byte, st *antigravityToInteractionsStreamState, part gjson.Result) [][]byte {
|
|
if text := part.Get("text"); text.Exists() && text.String() != "" {
|
|
if part.Get("thought").Bool() {
|
|
out = ensureAntigravityInteractionsStep(out, st, "thought", gjson.Result{})
|
|
delta := []byte(`{"index":0,"delta":{"content":{"text":"","type":"text"},"type":"thought_summary"},"event_type":"step.delta"}`)
|
|
delta, _ = sjson.SetBytes(delta, "index", st.ActiveStepIndex)
|
|
delta, _ = sjson.SetBytes(delta, "delta.content.text", text.String())
|
|
out = append(out, translatorcommon.SSEEventData("step.delta", delta))
|
|
return appendAntigravityThoughtSignature(out, st, part)
|
|
}
|
|
out = ensureAntigravityInteractionsStep(out, st, "model_output", gjson.Result{})
|
|
delta := []byte(`{"index":0,"delta":{"text":"","type":"text"},"event_type":"step.delta"}`)
|
|
delta, _ = sjson.SetBytes(delta, "index", st.ActiveStepIndex)
|
|
delta, _ = sjson.SetBytes(delta, "delta.text", text.String())
|
|
return append(out, translatorcommon.SSEEventData("step.delta", delta))
|
|
}
|
|
if fc := part.Get("functionCall"); fc.Exists() {
|
|
out = appendAntigravityThoughtSignature(out, st, part)
|
|
out = ensureAntigravityInteractionsStep(out, st, "function_call", fc)
|
|
delta := []byte(`{"index":0,"delta":{"arguments":"","type":"arguments_delta"},"event_type":"step.delta"}`)
|
|
delta, _ = sjson.SetBytes(delta, "index", st.ActiveStepIndex)
|
|
arguments := `{}`
|
|
if args := fc.Get("args"); args.Exists() {
|
|
arguments = args.Raw
|
|
}
|
|
delta, _ = sjson.SetBytes(delta, "delta.arguments", arguments)
|
|
out = append(out, translatorcommon.SSEEventData("step.delta", delta))
|
|
return appendAntigravityInteractionsStepStop(out, st)
|
|
}
|
|
if fr := part.Get("functionResponse"); fr.Exists() {
|
|
out = ensureAntigravityInteractionsStep(out, st, "function_result", fr)
|
|
delta := []byte(`{"index":0,"delta":{"type":"function_result","name":"","result":{}},"event_type":"step.delta"}`)
|
|
delta, _ = sjson.SetBytes(delta, "index", st.ActiveStepIndex)
|
|
delta, _ = sjson.SetBytes(delta, "delta.name", fr.Get("name").String())
|
|
if response := fr.Get("response"); response.Exists() {
|
|
delta, _ = sjson.SetRawBytes(delta, "delta.result", []byte(response.Raw))
|
|
}
|
|
out = append(out, translatorcommon.SSEEventData("step.delta", delta))
|
|
return appendAntigravityInteractionsStepStop(out, st)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func appendAntigravityThoughtSignature(out [][]byte, st *antigravityToInteractionsStreamState, part gjson.Result) [][]byte {
|
|
if signature := antigravityThoughtSignature(part); signature != "" {
|
|
out = ensureAntigravityInteractionsStep(out, st, "thought", gjson.Result{})
|
|
signatureDelta := []byte(`{"index":0,"delta":{"signature":"","type":"thought_signature"},"event_type":"step.delta"}`)
|
|
signatureDelta, _ = sjson.SetBytes(signatureDelta, "index", st.ActiveStepIndex)
|
|
signatureDelta, _ = sjson.SetBytes(signatureDelta, "delta.signature", signature)
|
|
return append(out, translatorcommon.SSEEventData("step.delta", signatureDelta))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func antigravityPartToInteractionsStep(part gjson.Result) []byte {
|
|
if fc := part.Get("functionCall"); fc.Exists() {
|
|
step := []byte(`{"type":"function_call","name":"","arguments":{}}`)
|
|
step, _ = sjson.SetBytes(step, "name", fc.Get("name").String())
|
|
if id := fc.Get("id"); id.Exists() {
|
|
step, _ = sjson.SetBytes(step, "call_id", id.String())
|
|
} else if callID := fc.Get("call_id"); callID.Exists() {
|
|
step, _ = sjson.SetBytes(step, "call_id", callID.String())
|
|
}
|
|
if args := fc.Get("args"); args.Exists() {
|
|
step, _ = sjson.SetRawBytes(step, "arguments", []byte(args.Raw))
|
|
}
|
|
return step
|
|
}
|
|
if fr := part.Get("functionResponse"); fr.Exists() {
|
|
step := []byte(`{"type":"function_result","name":"","result":{}}`)
|
|
step, _ = sjson.SetBytes(step, "name", fr.Get("name").String())
|
|
if id := fr.Get("id"); id.Exists() {
|
|
step, _ = sjson.SetBytes(step, "call_id", id.String())
|
|
} else if callID := fr.Get("call_id"); callID.Exists() {
|
|
step, _ = sjson.SetBytes(step, "call_id", callID.String())
|
|
}
|
|
if response := fr.Get("response"); response.Exists() {
|
|
step, _ = sjson.SetRawBytes(step, "result", []byte(response.Raw))
|
|
}
|
|
return step
|
|
}
|
|
if text := part.Get("text"); text.Exists() {
|
|
step := []byte(`{"type":"model_output","content":[]}`)
|
|
if part.Get("thought").Bool() {
|
|
step, _ = sjson.SetBytes(step, "type", "thought")
|
|
}
|
|
item := []byte(`{"type":"text","text":""}`)
|
|
item, _ = sjson.SetBytes(item, "text", text.String())
|
|
step = translatorcommon.SetRawArrayItems(step, "content", [][]byte{item})
|
|
return step
|
|
}
|
|
if inline := part.Get("inlineData"); inline.Exists() {
|
|
return antigravityInlineDataToInteractionsStep(inline)
|
|
}
|
|
if inline := part.Get("inline_data"); inline.Exists() {
|
|
return antigravityInlineDataToInteractionsStep(inline)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func antigravityInlineDataToInteractionsStep(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
|
|
}
|
|
contentType := "document"
|
|
lower := strings.ToLower(mimeType)
|
|
switch {
|
|
case strings.HasPrefix(lower, "image/"):
|
|
contentType = "image"
|
|
case strings.HasPrefix(lower, "audio/"):
|
|
contentType = "audio"
|
|
case strings.HasPrefix(lower, "video/"):
|
|
contentType = "video"
|
|
}
|
|
item := []byte(`{"type":"","mime_type":"","data":""}`)
|
|
item, _ = sjson.SetBytes(item, "type", contentType)
|
|
item, _ = sjson.SetBytes(item, "mime_type", mimeType)
|
|
item, _ = sjson.SetBytes(item, "data", data)
|
|
step := []byte(`{"type":"model_output","content":[]}`)
|
|
step, _ = sjson.SetRawBytes(step, "content.-1", item)
|
|
return step
|
|
}
|
|
|
|
func hasAntigravityStreamUsage(root gjson.Result) bool {
|
|
usage := antigravityUsageNode(root)
|
|
if !usage.Exists() {
|
|
return false
|
|
}
|
|
for _, path := range []string{
|
|
"promptTokenCount",
|
|
"candidatesTokenCount",
|
|
"totalTokenCount",
|
|
"thoughtsTokenCount",
|
|
"cachedContentTokenCount",
|
|
"prompt_token_count",
|
|
"candidates_token_count",
|
|
"total_token_count",
|
|
"thoughts_token_count",
|
|
"cached_content_token_count",
|
|
} {
|
|
if usage.Get(path).Exists() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func setInteractionsUsageFromAntigravity(out []byte, path string, root gjson.Result) []byte {
|
|
usage := antigravityUsageNode(root)
|
|
if !usage.Exists() {
|
|
return out
|
|
}
|
|
out, _ = sjson.SetBytes(out, path+".input_tokens", firstAntigravityUsageInt(usage, "promptTokenCount", "prompt_token_count"))
|
|
out, _ = sjson.SetBytes(out, path+".output_tokens", firstAntigravityUsageInt(usage, "candidatesTokenCount", "candidates_token_count"))
|
|
if antigravityUsagePathExists(usage, "thoughtsTokenCount", "thoughts_token_count") {
|
|
out, _ = sjson.SetBytes(out, path+".reasoning_tokens", firstAntigravityUsageInt(usage, "thoughtsTokenCount", "thoughts_token_count"))
|
|
}
|
|
out, _ = sjson.SetBytes(out, path+".total_tokens", firstAntigravityUsageInt(usage, "totalTokenCount", "total_token_count"))
|
|
if antigravityUsagePathExists(usage, "cachedContentTokenCount", "cached_content_token_count") {
|
|
out, _ = sjson.SetBytes(out, path+".cached_tokens", firstAntigravityUsageInt(usage, "cachedContentTokenCount", "cached_content_token_count"))
|
|
}
|
|
return out
|
|
}
|
|
|
|
func setInteractionsStreamUsageFromAntigravity(out []byte, path string, root gjson.Result) []byte {
|
|
usage := antigravityUsageNode(root)
|
|
if !usage.Exists() {
|
|
return out
|
|
}
|
|
inputTokens := firstAntigravityUsageInt(usage, "promptTokenCount", "prompt_token_count")
|
|
outputTokens := firstAntigravityUsageInt(usage, "candidatesTokenCount", "candidates_token_count")
|
|
totalTokens := firstAntigravityUsageInt(usage, "totalTokenCount", "total_token_count")
|
|
thoughtTokens := firstAntigravityUsageInt(usage, "thoughtsTokenCount", "thoughts_token_count")
|
|
cachedTokens := firstAntigravityUsageInt(usage, "cachedContentTokenCount", "cached_content_token_count")
|
|
out, _ = sjson.SetBytes(out, path+".total_tokens", totalTokens)
|
|
out, _ = sjson.SetBytes(out, path+".total_input_tokens", inputTokens)
|
|
out, _ = sjson.SetRawBytes(out, path+".input_tokens_by_modality", []byte(fmt.Sprintf(`[{"modality":"text","tokens":%d}]`, inputTokens)))
|
|
out, _ = sjson.SetBytes(out, path+".total_cached_tokens", cachedTokens)
|
|
out, _ = sjson.SetBytes(out, path+".total_output_tokens", outputTokens)
|
|
out, _ = sjson.SetBytes(out, path+".total_tool_use_tokens", 0)
|
|
out, _ = sjson.SetBytes(out, path+".total_thought_tokens", thoughtTokens)
|
|
return out
|
|
}
|
|
|
|
func antigravityUsageNode(root gjson.Result) gjson.Result {
|
|
if usage := root.Get("usageMetadata"); usage.Exists() {
|
|
return usage
|
|
}
|
|
if usage := root.Get("usage_metadata"); usage.Exists() {
|
|
return usage
|
|
}
|
|
if usage := root.Get("cpaUsageMetadata"); usage.Exists() {
|
|
return usage
|
|
}
|
|
return gjson.Result{}
|
|
}
|
|
|
|
func firstAntigravityUsageInt(usage gjson.Result, paths ...string) int64 {
|
|
for _, path := range paths {
|
|
if value := usage.Get(path); value.Exists() {
|
|
return value.Int()
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func antigravityUsagePathExists(usage gjson.Result, paths ...string) bool {
|
|
for _, path := range paths {
|
|
if usage.Get(path).Exists() {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func antigravityFunctionPartID(part gjson.Result) string {
|
|
if id := part.Get("id"); id.Exists() {
|
|
return id.String()
|
|
}
|
|
if callID := part.Get("call_id"); callID.Exists() {
|
|
return callID.String()
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func antigravityThoughtSignature(part gjson.Result) string {
|
|
for _, path := range []string{"thoughtSignature", "thought_signature", "extra_content.google.thought_signature"} {
|
|
if signature := strings.TrimSpace(part.Get(path).String()); signature != "" {
|
|
return signature
|
|
}
|
|
}
|
|
return ""
|
|
}
|