• ianshaw's avatar
    feat(gemini): 支持 Gemini CLI 粘性会话与跨账号 thoughtSignature 清理 · 839975b0
    ianshaw authored
    ## 问题背景
    
    1. Gemini CLI 没有明确的会话标识(如 Claude Code 的 metadata.user_id)
    2. thoughtSignature 与具体上游账号强绑定,跨账号使用会导致 400 错误
    3. 粘性会话切换账号或 cache 丢失时,旧签名会导致请求失败
    
    ## 解决方案
    
    ### 1. Gemini CLI 会话标识提取
    
    - 从 `x-gemini-api-privileged-user-id` header 和请求体中的 tmp 目录哈希生成会话标识
    - 组合策略:SHA256(privileged-user-id + ":" + tmp_dir_hash)
    - 正则提取:`/\.gemini/tmp/([A-Fa-f0-9]{64})`
    
    ### 2. 跨账号 thoughtSignature 清理
    
    实现三种场景的智能清理:
    
    1. **Cache 命中 + 账号切换**
       - 粘性会话绑定的账号与当前选择的账号不同时清理
    
    2. **同一请求内 failover 切换**
       - 通过 sessionBoundAccountID 跟踪,检测重试时的账号切换
    
    3. **Gemini CLI + Cache 未命中 + 含签名**
       - 预防性清理,避免 cache 丢失后首次转发就 400
       - 仅对 Gemini CLI 请求且请求体包含 thoughtSignature 时触发
    
    ## 修改内容
    
    ### backend/internal/handler/gemini_v1beta_handler.go
    - 添加 `extractGeminiCLISessionHash` 函数提取 Gemini CLI 会话标识
    - 添加 `isGeminiCLIRequest` 函数识别 Gemini CLI 请求
    - 实现账号切换检测与 thoughtSignature 清理逻辑
    - 添加 `geminiCLITmpDirRegex` 正则表达式
    
    ### backend/internal/service/gateway_service.go
    - 添加 `GetCachedSessionAccountID` 方法查询粘性会话绑定的账号 ID
    
    ### backend/internal/service/gemini_native_signature_cleaner.go (新增)
    - 实现 `CleanGeminiNativeThoughtSignatures` 函数
    - 递归清理 JSON 中的所有 thoughtSignature 字段
    - 支持任意 JSON 顶层类型(object/array)
    
    ### backend/internal/handler/gemini_cli_session_test.go (新增)
    - 测试 Gemini CLI 会话哈希提取逻辑
    - 测试 tmp 目录正则匹配
    - 覆盖有/无 privileged-user-id 的场景
    
    ## 影响范围
    
    - 修复 Gemini CLI 多轮对话时账号切换导致的 400 错误
    - 提高粘性会话的稳定性和容错能力
    - 不影响其他客户端(Claude Code 等)的会话标识生成
    
    ## 测试
    
    - 单元测试:go test -tags=unit ./internal/handler -run TestExtractGeminiCLISessionHash
    - 单元测试:go test -tags=unit ./internal/handler -run TestGeminiCLITmpDirRegex
    - 编译验证:go build ./cmd/server
    839975b0
gemini_v1beta_handler.go 18.1 KB