Fix Antigravity Gemini thought signatures
This commit is contained in:
@@ -99,35 +99,19 @@ func ConvertGeminiRequestToAntigravity(modelName string, inputRawJSON []byte, _
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Gemini-specific handling for non-Claude models:
|
// Gemini-specific handling for non-Claude models:
|
||||||
// - Add skip_thought_signature_validator to functionCall parts so upstream can bypass signature validation.
|
// - Replace client-provided thoughtSignature values with the skip sentinel.
|
||||||
// - Also mark thinking parts with the same sentinel when present (we keep the parts; we only annotate them).
|
// - Add the same sentinel to functionCall and thinking parts so upstream can bypass signature validation.
|
||||||
if !strings.Contains(modelName, "claude") {
|
if !strings.Contains(strings.ToLower(modelName), "claude") {
|
||||||
const skipSentinel = "skip_thought_signature_validator"
|
const skipSentinel = "skip_thought_signature_validator"
|
||||||
|
|
||||||
gjson.GetBytes(rawJSON, "request.contents").ForEach(func(contentIdx, content gjson.Result) bool {
|
gjson.GetBytes(rawJSON, "request.contents").ForEach(func(contentIdx, content gjson.Result) bool {
|
||||||
if content.Get("role").String() == "model" {
|
if content.Get("role").String() == "model" {
|
||||||
// First pass: collect indices of thinking parts to mark with skip sentinel
|
|
||||||
var thinkingIndicesToSkipSignature []int64
|
|
||||||
content.Get("parts").ForEach(func(partIdx, part gjson.Result) bool {
|
content.Get("parts").ForEach(func(partIdx, part gjson.Result) bool {
|
||||||
// Collect indices of thinking blocks to mark with skip sentinel
|
if part.Get("functionCall").Exists() || part.Get("thought").Exists() || part.Get("thoughtSignature").Exists() {
|
||||||
if part.Get("thought").Bool() {
|
rawJSON, _ = sjson.SetBytes(rawJSON, fmt.Sprintf("request.contents.%d.parts.%d.thoughtSignature", contentIdx.Int(), partIdx.Int()), skipSentinel)
|
||||||
thinkingIndicesToSkipSignature = append(thinkingIndicesToSkipSignature, partIdx.Int())
|
|
||||||
}
|
|
||||||
// Add skip sentinel to functionCall parts
|
|
||||||
if part.Get("functionCall").Exists() {
|
|
||||||
existingSig := part.Get("thoughtSignature").String()
|
|
||||||
if existingSig == "" || len(existingSig) < 50 {
|
|
||||||
rawJSON, _ = sjson.SetBytes(rawJSON, fmt.Sprintf("request.contents.%d.parts.%d.thoughtSignature", contentIdx.Int(), partIdx.Int()), skipSentinel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
// Add skip_thought_signature_validator sentinel to thinking blocks in reverse order to preserve indices
|
|
||||||
for i := len(thinkingIndicesToSkipSignature) - 1; i >= 0; i-- {
|
|
||||||
idx := thinkingIndicesToSkipSignature[i]
|
|
||||||
rawJSON, _ = sjson.SetBytes(rawJSON, fmt.Sprintf("request.contents.%d.parts.%d.thoughtSignature", contentIdx.Int(), idx), skipSentinel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConvertGeminiRequestToAntigravity_PreserveValidSignature(t *testing.T) {
|
func TestConvertGeminiRequestToAntigravity_ReplacesClientSignatureOnFunctionCall(t *testing.T) {
|
||||||
// Valid signature on functionCall should be preserved
|
// Client signatures on Gemini function calls are not portable to Antigravity.
|
||||||
validSignature := "abc123validSignature1234567890123456789012345678901234567890"
|
validSignature := "abc123validSignature1234567890123456789012345678901234567890"
|
||||||
inputJSON := []byte(fmt.Sprintf(`{
|
inputJSON := []byte(fmt.Sprintf(`{
|
||||||
"model": "gemini-3-pro-preview",
|
"model": "gemini-3-pro-preview",
|
||||||
@@ -25,15 +25,83 @@ func TestConvertGeminiRequestToAntigravity_PreserveValidSignature(t *testing.T)
|
|||||||
output := ConvertGeminiRequestToAntigravity("gemini-3-pro-preview", inputJSON, false)
|
output := ConvertGeminiRequestToAntigravity("gemini-3-pro-preview", inputJSON, false)
|
||||||
outputStr := string(output)
|
outputStr := string(output)
|
||||||
|
|
||||||
// Check that valid thoughtSignature is preserved
|
|
||||||
parts := gjson.Get(outputStr, "request.contents.0.parts").Array()
|
parts := gjson.Get(outputStr, "request.contents.0.parts").Array()
|
||||||
if len(parts) != 1 {
|
if len(parts) != 1 {
|
||||||
t.Fatalf("Expected 1 part, got %d", len(parts))
|
t.Fatalf("Expected 1 part, got %d", len(parts))
|
||||||
}
|
}
|
||||||
|
|
||||||
sig := parts[0].Get("thoughtSignature").String()
|
sig := parts[0].Get("thoughtSignature").String()
|
||||||
if sig != validSignature {
|
expectedSig := "skip_thought_signature_validator"
|
||||||
t.Errorf("Expected thoughtSignature '%s', got '%s'", validSignature, sig)
|
if sig != expectedSig {
|
||||||
|
t.Errorf("Expected thoughtSignature '%s', got '%s'", expectedSig, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertGeminiRequestToAntigravity_ReplacesClientSignatureOnTextPart(t *testing.T) {
|
||||||
|
validSignature := "abc123validSignature1234567890123456789012345678901234567890"
|
||||||
|
inputJSON := []byte(fmt.Sprintf(`{
|
||||||
|
"model": "gemini-3-pro-preview",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"role": "model",
|
||||||
|
"parts": [
|
||||||
|
{"text": "previous answer", "thoughtSignature": "%s"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, validSignature))
|
||||||
|
|
||||||
|
output := ConvertGeminiRequestToAntigravity("gemini-3-pro-preview", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
sig := gjson.Get(outputStr, "request.contents.0.parts.0.thoughtSignature").String()
|
||||||
|
expectedSig := "skip_thought_signature_validator"
|
||||||
|
if sig != expectedSig {
|
||||||
|
t.Errorf("Expected thoughtSignature '%s', got '%s'", expectedSig, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertGeminiRequestToAntigravity_AddsSkipSentinelToStringThoughtPart(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "gemini-3-pro-preview",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"role": "model",
|
||||||
|
"parts": [
|
||||||
|
{"thought": "internal reasoning"}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertGeminiRequestToAntigravity("gemini-3-pro-preview", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
sig := gjson.Get(outputStr, "request.contents.0.parts.0.thoughtSignature").String()
|
||||||
|
expectedSig := "skip_thought_signature_validator"
|
||||||
|
if sig != expectedSig {
|
||||||
|
t.Errorf("Expected thoughtSignature '%s', got '%s'", expectedSig, sig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertGeminiRequestToAntigravity_SkipsUppercaseClaudeModel(t *testing.T) {
|
||||||
|
inputJSON := []byte(`{
|
||||||
|
"model": "Claude-Test",
|
||||||
|
"contents": [
|
||||||
|
{
|
||||||
|
"role": "model",
|
||||||
|
"parts": [
|
||||||
|
{"functionCall": {"name": "test_tool", "args": {}}}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`)
|
||||||
|
|
||||||
|
output := ConvertGeminiRequestToAntigravity("Claude-Test", inputJSON, false)
|
||||||
|
outputStr := string(output)
|
||||||
|
|
||||||
|
if sig := gjson.Get(outputStr, "request.contents.0.parts.0.thoughtSignature"); sig.Exists() {
|
||||||
|
t.Fatalf("Expected no thoughtSignature for Claude model, got %s", sig.Raw)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user