Merge pull request #476 from touwaeriol/fix/gemini-thoughtsignature-v2

fix(gemini): 导出 DummyThoughtSignature 常量并修复跨账号签名验证
This commit is contained in:
Wesley Liddick
2026-02-03 21:56:34 +08:00
committed by GitHub
3 changed files with 28 additions and 24 deletions

View File

@@ -314,7 +314,7 @@ func buildContents(messages []ClaudeMessage, toolIDToName map[string]string, isT
parts = append([]GeminiPart{{ parts = append([]GeminiPart{{
Text: "Thinking...", Text: "Thinking...",
Thought: true, Thought: true,
ThoughtSignature: dummyThoughtSignature, ThoughtSignature: DummyThoughtSignature,
}}, parts...) }}, parts...)
} }
} }
@@ -332,9 +332,10 @@ func buildContents(messages []ClaudeMessage, toolIDToName map[string]string, isT
return contents, strippedThinking, nil return contents, strippedThinking, nil
} }
// dummyThoughtSignature 用于跳过 Gemini 3 thought_signature 验证 // DummyThoughtSignature 用于跳过 Gemini 3 thought_signature 验证
// 参考: https://ai.google.dev/gemini-api/docs/thought-signatures // 参考: https://ai.google.dev/gemini-api/docs/thought-signatures
const dummyThoughtSignature = "skip_thought_signature_validator" // 导出供跨包使用(如 gemini_native_signature_cleaner 跨账号修复)
const DummyThoughtSignature = "skip_thought_signature_validator"
// buildParts 构建消息的 parts // buildParts 构建消息的 parts
// allowDummyThought: 只有 Gemini 模型支持 dummy thought signature // allowDummyThought: 只有 Gemini 模型支持 dummy thought signature
@@ -372,7 +373,7 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
// signature 处理: // signature 处理:
// - Claude 模型allowDummyThought=false必须是上游返回的真实 signaturedummy 视为缺失) // - Claude 模型allowDummyThought=false必须是上游返回的真实 signaturedummy 视为缺失)
// - Gemini 模型allowDummyThought=true优先透传真实 signature缺失时使用 dummy signature // - Gemini 模型allowDummyThought=true优先透传真实 signature缺失时使用 dummy signature
if block.Signature != "" && (allowDummyThought || block.Signature != dummyThoughtSignature) { if block.Signature != "" && (allowDummyThought || block.Signature != DummyThoughtSignature) {
part.ThoughtSignature = block.Signature part.ThoughtSignature = block.Signature
} else if !allowDummyThought { } else if !allowDummyThought {
// Claude 模型需要有效 signature在缺失时降级为普通文本并在上层禁用 thinking mode。 // Claude 模型需要有效 signature在缺失时降级为普通文本并在上层禁用 thinking mode。
@@ -383,7 +384,7 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
continue continue
} else { } else {
// Gemini 模型使用 dummy signature // Gemini 模型使用 dummy signature
part.ThoughtSignature = dummyThoughtSignature part.ThoughtSignature = DummyThoughtSignature
} }
parts = append(parts, part) parts = append(parts, part)
@@ -413,10 +414,10 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
// tool_use 的 signature 处理: // tool_use 的 signature 处理:
// - Claude 模型allowDummyThought=false必须是上游返回的真实 signaturedummy 视为缺失) // - Claude 模型allowDummyThought=false必须是上游返回的真实 signaturedummy 视为缺失)
// - Gemini 模型allowDummyThought=true优先透传真实 signature缺失时使用 dummy signature // - Gemini 模型allowDummyThought=true优先透传真实 signature缺失时使用 dummy signature
if block.Signature != "" && (allowDummyThought || block.Signature != dummyThoughtSignature) { if block.Signature != "" && (allowDummyThought || block.Signature != DummyThoughtSignature) {
part.ThoughtSignature = block.Signature part.ThoughtSignature = block.Signature
} else if allowDummyThought { } else if allowDummyThought {
part.ThoughtSignature = dummyThoughtSignature part.ThoughtSignature = DummyThoughtSignature
} }
parts = append(parts, part) parts = append(parts, part)

View File

@@ -86,7 +86,7 @@ func TestBuildParts_ThinkingBlockWithoutSignature(t *testing.T) {
if len(parts) != 3 { if len(parts) != 3 {
t.Fatalf("expected 3 parts, got %d", len(parts)) t.Fatalf("expected 3 parts, got %d", len(parts))
} }
if !parts[1].Thought || parts[1].ThoughtSignature != dummyThoughtSignature { if !parts[1].Thought || parts[1].ThoughtSignature != DummyThoughtSignature {
t.Fatalf("expected dummy thought signature, got thought=%v signature=%q", t.Fatalf("expected dummy thought signature, got thought=%v signature=%q",
parts[1].Thought, parts[1].ThoughtSignature) parts[1].Thought, parts[1].ThoughtSignature)
} }
@@ -126,8 +126,8 @@ func TestBuildParts_ToolUseSignatureHandling(t *testing.T) {
if len(parts) != 1 || parts[0].FunctionCall == nil { if len(parts) != 1 || parts[0].FunctionCall == nil {
t.Fatalf("expected 1 functionCall part, got %+v", parts) t.Fatalf("expected 1 functionCall part, got %+v", parts)
} }
if parts[0].ThoughtSignature != dummyThoughtSignature { if parts[0].ThoughtSignature != DummyThoughtSignature {
t.Fatalf("expected dummy tool signature %q, got %q", dummyThoughtSignature, parts[0].ThoughtSignature) t.Fatalf("expected dummy tool signature %q, got %q", DummyThoughtSignature, parts[0].ThoughtSignature)
} }
}) })

View File

@@ -2,20 +2,22 @@ package service
import ( import (
"encoding/json" "encoding/json"
"github.com/Wei-Shaw/sub2api/internal/pkg/antigravity"
) )
// CleanGeminiNativeThoughtSignatures 从 Gemini 原生 API 请求中移除 thoughtSignature 字段, // CleanGeminiNativeThoughtSignatures 从 Gemini 原生 API 请求中替换 thoughtSignature 字段为 dummy 签名
// 以避免跨账号签名验证错误。 // 以避免跨账号签名验证错误。
// //
// 当粘性会话切换账号时(例如原账号异常、不可调度等),旧账号返回的 thoughtSignature // 当粘性会话切换账号时(例如原账号异常、不可调度等),旧账号返回的 thoughtSignature
// 会导致新账号的签名验证失败。通过移除这些签名,让新账号重新生成有效的签名 // 会导致新账号的签名验证失败。通过替换为 dummy 签名,跳过签名验证
// //
// CleanGeminiNativeThoughtSignatures removes thoughtSignature fields from Gemini native API requests // CleanGeminiNativeThoughtSignatures replaces thoughtSignature fields with dummy signature
// to avoid cross-account signature validation errors. // in Gemini native API requests to avoid cross-account signature validation errors.
// //
// When sticky session switches accounts (e.g., original account becomes unavailable), // When sticky session switches accounts (e.g., original account becomes unavailable),
// thoughtSignatures from the old account will cause validation failures on the new account. // thoughtSignatures from the old account will cause validation failures on the new account.
// By removing these signatures, we allow the new account to generate valid signatures. // By replacing with dummy signature, we skip signature validation.
func CleanGeminiNativeThoughtSignatures(body []byte) []byte { func CleanGeminiNativeThoughtSignatures(body []byte) []byte {
if len(body) == 0 { if len(body) == 0 {
return body return body
@@ -28,11 +30,11 @@ func CleanGeminiNativeThoughtSignatures(body []byte) []byte {
return body return body
} }
// 递归清理 thoughtSignature // 递归替换 thoughtSignature 为 dummy 签名
cleaned := cleanThoughtSignaturesRecursive(data) replaced := replaceThoughtSignaturesRecursive(data)
// 重新序列化 // 重新序列化
result, err := json.Marshal(cleaned) result, err := json.Marshal(replaced)
if err != nil { if err != nil {
// 如果序列化失败,返回原始 body // 如果序列化失败,返回原始 body
return body return body
@@ -41,19 +43,20 @@ func CleanGeminiNativeThoughtSignatures(body []byte) []byte {
return result return result
} }
// cleanThoughtSignaturesRecursive 递归遍历数据结构,移除所有 thoughtSignature 字段 // replaceThoughtSignaturesRecursive 递归遍历数据结构,所有 thoughtSignature 字段替换为 dummy 签名
func cleanThoughtSignaturesRecursive(data any) any { func replaceThoughtSignaturesRecursive(data any) any {
switch v := data.(type) { switch v := data.(type) {
case map[string]any: case map[string]any:
// 创建新的 map移除 thoughtSignature // 创建新的 map替换 thoughtSignature 为 dummy 签名
result := make(map[string]any, len(v)) result := make(map[string]any, len(v))
for key, value := range v { for key, value := range v {
// 跳过 thoughtSignature 字段 // 替换 thoughtSignature 字段为 dummy 签名
if key == "thoughtSignature" { if key == "thoughtSignature" {
result[key] = antigravity.DummyThoughtSignature
continue continue
} }
// 递归处理嵌套结构 // 递归处理嵌套结构
result[key] = cleanThoughtSignaturesRecursive(value) result[key] = replaceThoughtSignaturesRecursive(value)
} }
return result return result
@@ -61,7 +64,7 @@ func cleanThoughtSignaturesRecursive(data any) any {
// 递归处理数组中的每个元素 // 递归处理数组中的每个元素
result := make([]any, len(v)) result := make([]any, len(v))
for i, item := range v { for i, item := range v {
result[i] = cleanThoughtSignaturesRecursive(item) result[i] = replaceThoughtSignaturesRecursive(item)
} }
return result return result