package responses import ( "encoding/base64" "encoding/json" "strings" sigcompat "github.com/router-for-me/CLIProxyAPI/v7/internal/signature" "github.com/tidwall/gjson" "github.com/tidwall/sjson" ) const ( geminiResponsesCarrierPrefix = "cpa-gemini-responses-carrier-v1:" geminiResponsesCarrierNext = "next" geminiResponsesCarrierPrevious = "previous" geminiResponsesCarrierStandalone = "standalone" geminiResponsesCarrierText = "text" geminiResponsesCarrierFunction = "function" geminiResponsesCarrierAny = "any" geminiResponsesCarrierDirectionField = "_cpa_reasoning_direction" geminiResponsesCarrierTargetField = "_cpa_reasoning_target" geminiResponsesCarrierSignatureField = "_cpa_reasoning_signature" geminiResponsesCarrierSummaryField = "_cpa_reasoning_summary" ) func encodeGeminiResponsesCarrier(rawSignature, direction, targetKind string) string { rawSignature = strings.TrimSpace(rawSignature) if rawSignature == "" { return "" } return geminiResponsesCarrierPrefix + direction + ":" + targetKind + ":" + base64.RawStdEncoding.EncodeToString([]byte(rawSignature)) } func decodeGeminiResponsesCarrier(rawSignature string) (signatureValue, direction, targetKind string, marked, ok bool) { rawSignature = strings.TrimSpace(rawSignature) if !strings.HasPrefix(rawSignature, geminiResponsesCarrierPrefix) { return rawSignature, "", "", false, true } marked = true if len(rawSignature) > (sigcompat.MaxGeminiThoughtSignatureLen*4/3)+1024 { return "", "", "", true, false } fields := strings.SplitN(strings.TrimPrefix(rawSignature, geminiResponsesCarrierPrefix), ":", 3) if len(fields) != 3 { return "", "", "", true, false } direction, targetKind = fields[0], fields[1] switch direction { case geminiResponsesCarrierNext, geminiResponsesCarrierPrevious, geminiResponsesCarrierStandalone: default: return "", "", "", true, false } switch targetKind { case geminiResponsesCarrierText, geminiResponsesCarrierFunction, geminiResponsesCarrierAny: default: return "", "", "", true, false } decoded, errDecode := base64.RawStdEncoding.DecodeString(fields[2]) if errDecode != nil || len(decoded) == 0 || strings.HasPrefix(string(decoded), geminiResponsesCarrierPrefix) { return "", "", "", true, false } return string(decoded), direction, targetKind, true, true } func compatibleGeminiResponsesCarrierSignature(rawSignature, targetKind string) (string, bool) { blockKind := sigcompat.SignatureBlockKindGeminiModelPart if targetKind == geminiResponsesCarrierFunction { blockKind = sigcompat.SignatureBlockKindGeminiFunctionCall } normalized, compatible := sigcompat.CompatibleSignatureForProviderBlock(sigcompat.SignatureProviderGemini, rawSignature, blockKind) if !compatible || sigcompat.IsGeminiThoughtSignatureBypass(sigcompat.SignaturePayloadWithoutProviderPrefix(normalized)) { return "", false } return normalized, true } func geminiResponsesCarrierSemanticTarget(item gjson.Result) string { switch item.Get("type").String() { case "function_call", "custom_tool_call": return geminiResponsesCarrierFunction case "reasoning": if strings.TrimSpace(item.Get("summary.0.text").String()) != "" { return geminiResponsesCarrierText } } if _, ok := openAIResponsesAssistantVisibleText(item); ok { return geminiResponsesCarrierText } return "" } func geminiResponsesCarrierMatchesAdjacent(items []gjson.Result, index int, direction, targetKind string) bool { step := 1 if direction == geminiResponsesCarrierPrevious { step = -1 } for adjacent := index + step; adjacent >= 0 && adjacent < len(items); adjacent += step { if kind := geminiResponsesCarrierSemanticTarget(items[adjacent]); kind != "" { return targetKind == geminiResponsesCarrierAny || targetKind == kind } if !isOpenAIResponsesDetachedCarrier(items[adjacent]) { return false } } return false } func hasInternalCarrierFields(item gjson.Result) bool { return item.Get(geminiResponsesCarrierDirectionField).Exists() || item.Get(geminiResponsesCarrierTargetField).Exists() || item.Get(geminiResponsesCarrierSignatureField).Exists() || item.Get(geminiResponsesCarrierSummaryField).Exists() } func stripGeminiResponsesCarrierMetadata(rawJSON string) ([]byte, bool) { var fields map[string]json.RawMessage if err := json.Unmarshal([]byte(rawJSON), &fields); err != nil { return []byte(rawJSON), false } delete(fields, geminiResponsesCarrierDirectionField) delete(fields, geminiResponsesCarrierTargetField) delete(fields, geminiResponsesCarrierSignatureField) delete(fields, geminiResponsesCarrierSummaryField) stripped, errMarshal := json.Marshal(fields) if errMarshal != nil { return []byte(rawJSON), false } return stripped, true } func normalizeGeminiResponsesCarriers(items []gjson.Result) ([]gjson.Result, bool) { normalized := make([]gjson.Result, 0, len(items)) hasValidCarrier := false for itemIndex, originalItem := range items { item := originalItem var itemJSON []byte if hasInternalCarrierFields(originalItem) { stripped, ok := stripGeminiResponsesCarrierMetadata(originalItem.Raw) if ok { itemJSON = stripped item = gjson.ParseBytes(itemJSON) } } if item.Get("type").String() != "reasoning" { normalized = append(normalized, item) continue } if len(itemJSON) == 0 { itemJSON = []byte(item.Raw) } rawSignature := strings.TrimSpace(item.Get("encrypted_content").String()) signature, direction, targetKind, marked, ok := decodeGeminiResponsesCarrier(rawSignature) if !marked { if rawSignature != "" { _, hasCompatibleRawCarrier := compatibleGeminiResponsesCarrierSignature(rawSignature, geminiResponsesCarrierAny) hasValidCarrier = hasValidCarrier || hasCompatibleRawCarrier } normalized = append(normalized, item) continue } if ok { signature, ok = compatibleGeminiResponsesCarrierSignature(signature, targetKind) } if ok && direction != geminiResponsesCarrierStandalone { ok = geminiResponsesCarrierMatchesAdjacent(items, itemIndex, direction, targetKind) } isDetached := isOpenAIResponsesDetachedCarrier(item) hasSummary := strings.TrimSpace(item.Get("summary.0.text").String()) != "" validSummaryCarrier := hasSummary && ((direction == geminiResponsesCarrierStandalone && (targetKind == geminiResponsesCarrierText || targetKind == geminiResponsesCarrierAny)) || direction == geminiResponsesCarrierNext) if !ok || (!isDetached && !validSummaryCarrier) { if strings.TrimSpace(item.Get("summary.0.text").String()) == "" { continue } itemJSON, _ = sjson.DeleteBytes(itemJSON, "encrypted_content") normalized = append(normalized, gjson.ParseBytes(itemJSON)) continue } hasValidCarrier = true itemJSON, _ = sjson.SetBytes(itemJSON, "encrypted_content", signature) itemJSON, _ = sjson.SetBytes(itemJSON, geminiResponsesCarrierDirectionField, direction) itemJSON, _ = sjson.SetBytes(itemJSON, geminiResponsesCarrierTargetField, targetKind) normalized = append(normalized, gjson.ParseBytes(itemJSON)) } return normalized, hasValidCarrier } func geminiResponsesCarrierDirection(item gjson.Result) string { return item.Get(geminiResponsesCarrierDirectionField).String() } func geminiResponsesCarrierTarget(item gjson.Result) string { return item.Get(geminiResponsesCarrierTargetField).String() } func isOpenAIResponsesDetachedCarrier(item gjson.Result) bool { return item.Get("type").String() == "reasoning" && strings.TrimSpace(item.Get("encrypted_content").String()) != "" && strings.TrimSpace(item.Get("summary.0.text").String()) == "" }