Merge branch 'main' into test
This commit is contained in:
@@ -40,17 +40,48 @@ const (
|
||||
|
||||
// URL 可用性 TTL(不可用 URL 的恢复时间)
|
||||
URLAvailabilityTTL = 5 * time.Minute
|
||||
|
||||
// Antigravity API 端点
|
||||
antigravityProdBaseURL = "https://cloudcode-pa.googleapis.com"
|
||||
antigravityDailyBaseURL = "https://daily-cloudcode-pa.sandbox.googleapis.com"
|
||||
)
|
||||
|
||||
// BaseURLs 定义 Antigravity API 端点(与 Antigravity-Manager 保持一致)
|
||||
var BaseURLs = []string{
|
||||
"https://cloudcode-pa.googleapis.com", // prod (优先)
|
||||
"https://daily-cloudcode-pa.sandbox.googleapis.com", // daily sandbox (备用)
|
||||
antigravityProdBaseURL, // prod (优先)
|
||||
antigravityDailyBaseURL, // daily sandbox (备用)
|
||||
}
|
||||
|
||||
// BaseURL 默认 URL(保持向后兼容)
|
||||
var BaseURL = BaseURLs[0]
|
||||
|
||||
// ForwardBaseURLs 返回 API 转发用的 URL 顺序(daily 优先)
|
||||
func ForwardBaseURLs() []string {
|
||||
if len(BaseURLs) == 0 {
|
||||
return nil
|
||||
}
|
||||
urls := append([]string(nil), BaseURLs...)
|
||||
dailyIndex := -1
|
||||
for i, url := range urls {
|
||||
if url == antigravityDailyBaseURL {
|
||||
dailyIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if dailyIndex <= 0 {
|
||||
return urls
|
||||
}
|
||||
reordered := make([]string, 0, len(urls))
|
||||
reordered = append(reordered, urls[dailyIndex])
|
||||
for i, url := range urls {
|
||||
if i == dailyIndex {
|
||||
continue
|
||||
}
|
||||
reordered = append(reordered, url)
|
||||
}
|
||||
return reordered
|
||||
}
|
||||
|
||||
// URLAvailability 管理 URL 可用性状态(带 TTL 自动恢复和动态优先级)
|
||||
type URLAvailability struct {
|
||||
mu sync.RWMutex
|
||||
@@ -100,22 +131,37 @@ func (u *URLAvailability) IsAvailable(url string) bool {
|
||||
// GetAvailableURLs 返回可用的 URL 列表
|
||||
// 最近成功的 URL 优先,其他按默认顺序
|
||||
func (u *URLAvailability) GetAvailableURLs() []string {
|
||||
return u.GetAvailableURLsWithBase(BaseURLs)
|
||||
}
|
||||
|
||||
// GetAvailableURLsWithBase 返回可用的 URL 列表(使用自定义顺序)
|
||||
// 最近成功的 URL 优先,其他按传入顺序
|
||||
func (u *URLAvailability) GetAvailableURLsWithBase(baseURLs []string) []string {
|
||||
u.mu.RLock()
|
||||
defer u.mu.RUnlock()
|
||||
|
||||
now := time.Now()
|
||||
result := make([]string, 0, len(BaseURLs))
|
||||
result := make([]string, 0, len(baseURLs))
|
||||
|
||||
// 如果有最近成功的 URL 且可用,放在最前面
|
||||
if u.lastSuccess != "" {
|
||||
expiry, exists := u.unavailable[u.lastSuccess]
|
||||
if !exists || now.After(expiry) {
|
||||
result = append(result, u.lastSuccess)
|
||||
found := false
|
||||
for _, url := range baseURLs {
|
||||
if url == u.lastSuccess {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if found {
|
||||
expiry, exists := u.unavailable[u.lastSuccess]
|
||||
if !exists || now.After(expiry) {
|
||||
result = append(result, u.lastSuccess)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 添加其他可用的 URL(按默认顺序)
|
||||
for _, url := range BaseURLs {
|
||||
// 添加其他可用的 URL(按传入顺序)
|
||||
for _, url := range baseURLs {
|
||||
// 跳过已添加的 lastSuccess
|
||||
if url == u.lastSuccess {
|
||||
continue
|
||||
|
||||
@@ -44,11 +44,13 @@ type TransformOptions struct {
|
||||
// IdentityPatch 可选:自定义注入到 systemInstruction 开头的身份防护提示词;
|
||||
// 为空时使用默认模板(包含 [IDENTITY_PATCH] 及 SYSTEM_PROMPT_BEGIN 标记)。
|
||||
IdentityPatch string
|
||||
EnableMCPXML bool
|
||||
}
|
||||
|
||||
func DefaultTransformOptions() TransformOptions {
|
||||
return TransformOptions{
|
||||
EnableIdentityPatch: true,
|
||||
EnableMCPXML: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,8 +259,8 @@ func buildSystemInstruction(system json.RawMessage, modelName string, opts Trans
|
||||
// 添加用户的 system prompt
|
||||
parts = append(parts, userSystemParts...)
|
||||
|
||||
// 检测是否有 MCP 工具,如有则注入 XML 调用协议
|
||||
if hasMCPTools(tools) {
|
||||
// 检测是否有 MCP 工具,如有且启用了 MCP XML 注入则注入 XML 调用协议
|
||||
if opts.EnableMCPXML && hasMCPTools(tools) {
|
||||
parts = append(parts, GeminiPart{Text: mcpXMLProtocol})
|
||||
}
|
||||
|
||||
@@ -312,7 +314,7 @@ func buildContents(messages []ClaudeMessage, toolIDToName map[string]string, isT
|
||||
parts = append([]GeminiPart{{
|
||||
Text: "Thinking...",
|
||||
Thought: true,
|
||||
ThoughtSignature: dummyThoughtSignature,
|
||||
ThoughtSignature: DummyThoughtSignature,
|
||||
}}, parts...)
|
||||
}
|
||||
}
|
||||
@@ -330,9 +332,10 @@ func buildContents(messages []ClaudeMessage, toolIDToName map[string]string, isT
|
||||
return contents, strippedThinking, nil
|
||||
}
|
||||
|
||||
// dummyThoughtSignature 用于跳过 Gemini 3 thought_signature 验证
|
||||
// DummyThoughtSignature 用于跳过 Gemini 3 thought_signature 验证
|
||||
// 参考: 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
|
||||
// allowDummyThought: 只有 Gemini 模型支持 dummy thought signature
|
||||
@@ -370,7 +373,7 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
|
||||
// signature 处理:
|
||||
// - Claude 模型(allowDummyThought=false):必须是上游返回的真实 signature(dummy 视为缺失)
|
||||
// - Gemini 模型(allowDummyThought=true):优先透传真实 signature,缺失时使用 dummy signature
|
||||
if block.Signature != "" && (allowDummyThought || block.Signature != dummyThoughtSignature) {
|
||||
if block.Signature != "" && (allowDummyThought || block.Signature != DummyThoughtSignature) {
|
||||
part.ThoughtSignature = block.Signature
|
||||
} else if !allowDummyThought {
|
||||
// Claude 模型需要有效 signature;在缺失时降级为普通文本,并在上层禁用 thinking mode。
|
||||
@@ -381,7 +384,7 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
|
||||
continue
|
||||
} else {
|
||||
// Gemini 模型使用 dummy signature
|
||||
part.ThoughtSignature = dummyThoughtSignature
|
||||
part.ThoughtSignature = DummyThoughtSignature
|
||||
}
|
||||
parts = append(parts, part)
|
||||
|
||||
@@ -411,10 +414,10 @@ func buildParts(content json.RawMessage, toolIDToName map[string]string, allowDu
|
||||
// tool_use 的 signature 处理:
|
||||
// - Claude 模型(allowDummyThought=false):必须是上游返回的真实 signature(dummy 视为缺失)
|
||||
// - Gemini 模型(allowDummyThought=true):优先透传真实 signature,缺失时使用 dummy signature
|
||||
if block.Signature != "" && (allowDummyThought || block.Signature != dummyThoughtSignature) {
|
||||
if block.Signature != "" && (allowDummyThought || block.Signature != DummyThoughtSignature) {
|
||||
part.ThoughtSignature = block.Signature
|
||||
} else if allowDummyThought {
|
||||
part.ThoughtSignature = dummyThoughtSignature
|
||||
part.ThoughtSignature = DummyThoughtSignature
|
||||
}
|
||||
parts = append(parts, part)
|
||||
|
||||
@@ -492,9 +495,23 @@ func parseToolResultContent(content json.RawMessage, isError bool) string {
|
||||
}
|
||||
|
||||
// buildGenerationConfig 构建 generationConfig
|
||||
const (
|
||||
defaultMaxOutputTokens = 64000
|
||||
maxOutputTokensUpperBound = 65000
|
||||
maxOutputTokensClaude = 64000
|
||||
)
|
||||
|
||||
func maxOutputTokensLimit(model string) int {
|
||||
if strings.HasPrefix(model, "claude-") {
|
||||
return maxOutputTokensClaude
|
||||
}
|
||||
return maxOutputTokensUpperBound
|
||||
}
|
||||
|
||||
func buildGenerationConfig(req *ClaudeRequest) *GeminiGenerationConfig {
|
||||
maxLimit := maxOutputTokensLimit(req.Model)
|
||||
config := &GeminiGenerationConfig{
|
||||
MaxOutputTokens: 64000, // 默认最大输出
|
||||
MaxOutputTokens: defaultMaxOutputTokens, // 默认最大输出
|
||||
StopSequences: DefaultStopSequences,
|
||||
}
|
||||
|
||||
@@ -518,6 +535,10 @@ func buildGenerationConfig(req *ClaudeRequest) *GeminiGenerationConfig {
|
||||
}
|
||||
}
|
||||
|
||||
if config.MaxOutputTokens > maxLimit {
|
||||
config.MaxOutputTokens = maxLimit
|
||||
}
|
||||
|
||||
// 其他参数
|
||||
if req.Temperature != nil {
|
||||
config.Temperature = req.Temperature
|
||||
|
||||
@@ -86,7 +86,7 @@ func TestBuildParts_ThinkingBlockWithoutSignature(t *testing.T) {
|
||||
if len(parts) != 3 {
|
||||
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",
|
||||
parts[1].Thought, parts[1].ThoughtSignature)
|
||||
}
|
||||
@@ -126,8 +126,8 @@ 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 != dummyThoughtSignature {
|
||||
t.Fatalf("expected dummy tool signature %q, got %q", dummyThoughtSignature, parts[0].ThoughtSignature)
|
||||
if parts[0].ThoughtSignature != DummyThoughtSignature {
|
||||
t.Fatalf("expected dummy tool signature %q, got %q", DummyThoughtSignature, parts[0].ThoughtSignature)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -14,6 +14,9 @@ const (
|
||||
// RetryCount 表示当前请求在网关层的重试次数(用于 Ops 记录与排障)。
|
||||
RetryCount Key = "ctx_retry_count"
|
||||
|
||||
// AccountSwitchCount 表示请求过程中发生的账号切换次数
|
||||
AccountSwitchCount Key = "ctx_account_switch_count"
|
||||
|
||||
// IsClaudeCodeClient 标识当前请求是否来自 Claude Code 客户端
|
||||
IsClaudeCodeClient Key = "ctx_is_claude_code_client"
|
||||
// Group 认证后的分组信息,由 API Key 认证中间件设置
|
||||
|
||||
Reference in New Issue
Block a user