diff --git a/backend/internal/pkg/antigravity/request_transformer.go b/backend/internal/pkg/antigravity/request_transformer.go index 1b21bd58..63f6ee7c 100644 --- a/backend/internal/pkg/antigravity/request_transformer.go +++ b/backend/internal/pkg/antigravity/request_transformer.go @@ -367,8 +367,10 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu Text: block.Thinking, Thought: true, } - // 保留原有 signature(Claude 模型需要有效的 signature) - if block.Signature != "" { + // signature 处理: + // - Claude 模型(allowDummyThought=false):必须是上游返回的真实 signature(dummy 视为缺失) + // - Gemini 模型(allowDummyThought=true):优先透传真实 signature,缺失时使用 dummy signature + if block.Signature != "" && (allowDummyThought || block.Signature != dummyThoughtSignature) { part.ThoughtSignature = block.Signature } else if !allowDummyThought { // Claude 模型需要有效 signature;在缺失时降级为普通文本,并在上层禁用 thinking mode。 @@ -407,12 +409,12 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu }, } // tool_use 的 signature 处理: - // - Gemini 模型:使用 dummy signature(跳过 thought_signature 校验) - // - Claude 模型:透传上游返回的真实 signature(Vertex/Google 需要完整签名链路) - if allowDummyThought { - part.ThoughtSignature = dummyThoughtSignature - } else if block.Signature != "" && block.Signature != dummyThoughtSignature { + // - Claude 模型(allowDummyThought=false):必须是上游返回的真实 signature(dummy 视为缺失) + // - Gemini 模型(allowDummyThought=true):优先透传真实 signature,缺失时使用 dummy signature + if block.Signature != "" && (allowDummyThought || block.Signature != dummyThoughtSignature) { part.ThoughtSignature = block.Signature + } else if allowDummyThought { + part.ThoughtSignature = dummyThoughtSignature } parts = append(parts, part) diff --git a/backend/internal/pkg/antigravity/request_transformer_test.go b/backend/internal/pkg/antigravity/request_transformer_test.go index 60ee6f63..9d62a4a1 100644 --- a/backend/internal/pkg/antigravity/request_transformer_test.go +++ b/backend/internal/pkg/antigravity/request_transformer_test.go @@ -100,7 +100,7 @@ func TestBuildParts_ToolUseSignatureHandling(t *testing.T) { {"type": "tool_use", "id": "t1", "name": "Bash", "input": {"command": "ls"}, "signature": "sig_tool_abc"} ]` - t.Run("Gemini uses dummy tool_use signature", func(t *testing.T) { + t.Run("Gemini preserves provided tool_use signature", func(t *testing.T) { toolIDToName := make(map[string]string) parts, _, err := buildParts(json.RawMessage(content), toolIDToName, true) if err != nil { @@ -109,6 +109,23 @@ func TestBuildParts_ToolUseSignatureHandling(t *testing.T) { if len(parts) != 1 || parts[0].FunctionCall == nil { t.Fatalf("expected 1 functionCall part, got %+v", parts) } + if parts[0].ThoughtSignature != "sig_tool_abc" { + t.Fatalf("expected preserved tool signature %q, got %q", "sig_tool_abc", parts[0].ThoughtSignature) + } + }) + + t.Run("Gemini falls back to dummy tool_use signature when missing", func(t *testing.T) { + contentNoSig := `[ + {"type": "tool_use", "id": "t1", "name": "Bash", "input": {"command": "ls"}} + ]` + toolIDToName := make(map[string]string) + parts, _, err := buildParts(json.RawMessage(contentNoSig), toolIDToName, true) + if err != nil { + t.Fatalf("buildParts() error = %v", err) + } + if len(parts) != 1 || parts[0].FunctionCall == nil { + t.Fatalf("expected 1 functionCall part, got %+v", parts) + } if parts[0].ThoughtSignature != dummyThoughtSignature { t.Fatalf("expected dummy tool signature %q, got %q", dummyThoughtSignature, parts[0].ThoughtSignature) }