mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-16 07:33:06 +08:00
fix(antigravity): preserve tool_result images, native Gemini functionCall/thoughtSignature, and composite /v1beta routing
This commit is contained in:
@@ -39,9 +39,11 @@ func (h *GatewayHandler) GeminiV1BetaListModels(c *gin.Context) {
|
||||
googleError(c, http.StatusUnauthorized, "Invalid API key")
|
||||
return
|
||||
}
|
||||
// 检查平台:优先使用强制平台(/antigravity 路由),否则要求 gemini 分组
|
||||
// 检查平台:优先使用强制平台(/antigravity 路由),否则要求 gemini 分组(composite 分组允许路由到 antigravity)
|
||||
forcePlatform, hasForcePlatform := middleware.GetForcePlatformFromContext(c)
|
||||
if !hasForcePlatform && effectiveAPIKeyPlatform(c, apiKey) != service.PlatformGemini {
|
||||
effectivePlatform := effectiveAPIKeyPlatform(c, apiKey)
|
||||
if !hasForcePlatform && effectivePlatform != service.PlatformGemini &&
|
||||
!servesCompositeAntigravityGemini(apiKey, effectivePlatform) {
|
||||
googleError(c, http.StatusBadRequest, "API key group platform is not gemini")
|
||||
return
|
||||
}
|
||||
@@ -223,9 +225,11 @@ func (h *GatewayHandler) GeminiV1BetaGetModel(c *gin.Context) {
|
||||
googleError(c, http.StatusUnauthorized, "Invalid API key")
|
||||
return
|
||||
}
|
||||
// 检查平台:优先使用强制平台(/antigravity 路由),否则要求 gemini 分组
|
||||
// 检查平台:优先使用强制平台(/antigravity 路由),否则要求 gemini 分组(composite 分组允许路由到 antigravity)
|
||||
forcePlatform, hasForcePlatform := middleware.GetForcePlatformFromContext(c)
|
||||
if !hasForcePlatform && effectiveAPIKeyPlatform(c, apiKey) != service.PlatformGemini {
|
||||
effectivePlatform := effectiveAPIKeyPlatform(c, apiKey)
|
||||
if !hasForcePlatform && effectivePlatform != service.PlatformGemini &&
|
||||
!servesCompositeAntigravityGemini(apiKey, effectivePlatform) {
|
||||
googleError(c, http.StatusBadRequest, "API key group platform is not gemini")
|
||||
return
|
||||
}
|
||||
@@ -244,9 +248,13 @@ func (h *GatewayHandler) GeminiV1BetaGetModel(c *gin.Context) {
|
||||
if resolvedModel, ok := service.ResolvedUpstreamModelFromContext(c.Request.Context()); ok && strings.TrimSpace(resolvedModel) != "" {
|
||||
modelName = strings.TrimSpace(resolvedModel)
|
||||
}
|
||||
if !hasForcePlatform && servesCompositeAntigravityGemini(apiKey, effectivePlatform) && !isAntigravityGeminiModel(modelName) {
|
||||
googleError(c, http.StatusBadRequest, "API key group platform is not gemini")
|
||||
return
|
||||
}
|
||||
|
||||
// 强制 antigravity 模式:返回 antigravity 模型信息
|
||||
if forcePlatform == service.PlatformAntigravity {
|
||||
// 强制 antigravity 模式或 composite 路由到 antigravity:返回 antigravity 模型信息
|
||||
if forcePlatform == service.PlatformAntigravity || effectivePlatform == service.PlatformAntigravity {
|
||||
c.JSON(http.StatusOK, antigravity.FallbackGeminiModel(modelName))
|
||||
return
|
||||
}
|
||||
@@ -299,9 +307,11 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
|
||||
zap.Any("group_id", apiKey.GroupID),
|
||||
)
|
||||
|
||||
// 检查平台:优先使用强制平台(/antigravity 路由,中间件已设置 request.Context),否则要求 gemini 分组
|
||||
// 检查平台:优先使用强制平台(/antigravity 路由,中间件已设置 request.Context),否则要求 gemini 分组(composite 分组允许路由到 antigravity)
|
||||
effectivePlatform := effectiveAPIKeyPlatform(c, apiKey)
|
||||
if !middleware.HasForcePlatform(c) {
|
||||
if effectiveAPIKeyPlatform(c, apiKey) != service.PlatformGemini {
|
||||
if effectivePlatform != service.PlatformGemini &&
|
||||
!servesCompositeAntigravityGemini(apiKey, effectivePlatform) {
|
||||
googleError(c, http.StatusBadRequest, "API key group platform is not gemini")
|
||||
return
|
||||
}
|
||||
@@ -321,6 +331,10 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
|
||||
if resolvedModel, ok := service.ResolvedUpstreamModelFromContext(c.Request.Context()); ok && strings.TrimSpace(resolvedModel) != "" {
|
||||
modelName = strings.TrimSpace(resolvedModel)
|
||||
}
|
||||
if !middleware.HasForcePlatform(c) && servesCompositeAntigravityGemini(apiKey, effectivePlatform) && !isAntigravityGeminiModel(modelName) {
|
||||
googleError(c, http.StatusBadRequest, "API key group platform is not gemini")
|
||||
return
|
||||
}
|
||||
|
||||
stream := action == "streamGenerateContent"
|
||||
reqLog = reqLog.With(zap.String("model", modelName), zap.String("action", action), zap.Bool("stream", stream))
|
||||
@@ -567,7 +581,11 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
if len(fs.FailedAccountIDs) == 0 {
|
||||
cls := classifyNoAccountErrorFromGin(c, h.gatewayService, apiKey, modelName, modelName, service.PlatformGemini)
|
||||
diagPlatform := service.PlatformGemini
|
||||
if p := effectiveAPIKeyPlatform(c, apiKey); p == service.PlatformAntigravity {
|
||||
diagPlatform = service.PlatformAntigravity
|
||||
}
|
||||
cls := classifyNoAccountErrorFromGin(c, h.gatewayService, apiKey, modelName, modelName, diagPlatform)
|
||||
if !cls.ModelNotFound {
|
||||
markOpsRoutingCapacityLimitedIfNoAvailable(c, err)
|
||||
}
|
||||
@@ -1053,3 +1071,16 @@ func derefGroupID(groupID *int64) int64 {
|
||||
}
|
||||
return *groupID
|
||||
}
|
||||
|
||||
func servesCompositeAntigravityGemini(apiKey *service.APIKey, effectivePlatform string) bool {
|
||||
return apiKey.Group != nil && apiKey.Group.Platform == service.PlatformComposite && effectivePlatform == service.PlatformAntigravity
|
||||
}
|
||||
|
||||
func isAntigravityGeminiModel(model string) bool {
|
||||
trimmed := strings.ToLower(strings.TrimSpace(strings.TrimPrefix(model, "models/")))
|
||||
if trimmed == "" || strings.HasPrefix(trimmed, "claude-") {
|
||||
return false
|
||||
}
|
||||
detected, ok := service.DetectModelPlatform(trimmed)
|
||||
return ok && detected == service.PlatformGemini
|
||||
}
|
||||
|
||||
@@ -216,3 +216,75 @@ func TestShouldFallbackGeminiModel_DelegatesScopeFallback(t *testing.T) {
|
||||
}
|
||||
require.True(t, shouldFallbackGeminiModel("gemini-future-model", res))
|
||||
}
|
||||
|
||||
func TestGeminiV1BetaGetModel_CompositeAntigravityRoute(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("composite route to antigravity gemini model succeeds", func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1beta/models/gemini-4-argon", nil)
|
||||
req = req.WithContext(service.WithCompositeRouteDecision(req.Context(), service.CompositeRouteDecision{
|
||||
Matched: true,
|
||||
TargetPlatform: service.PlatformAntigravity,
|
||||
UpstreamModel: "gemini-4-argon-high",
|
||||
}))
|
||||
c.Request = req
|
||||
c.Params = gin.Params{{Key: "model", Value: "gemini-4-argon"}}
|
||||
groupID := int64(4)
|
||||
c.Set(string(middleware.ContextKeyAPIKey), &service.APIKey{
|
||||
ID: 1,
|
||||
GroupID: &groupID,
|
||||
Group: &service.Group{ID: groupID, Platform: service.PlatformComposite},
|
||||
})
|
||||
|
||||
(&GatewayHandler{}).GeminiV1BetaGetModel(c)
|
||||
require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
|
||||
require.Contains(t, rec.Body.String(), "models/gemini-4-argon-high")
|
||||
})
|
||||
|
||||
t.Run("composite route to antigravity claude model is rejected on v1beta", func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
req := httptest.NewRequest(http.MethodGet, "/v1beta/models/claude-sonnet-5-5-high", nil)
|
||||
req = req.WithContext(service.WithCompositeRouteDecision(req.Context(), service.CompositeRouteDecision{
|
||||
Matched: true,
|
||||
TargetPlatform: service.PlatformAntigravity,
|
||||
UpstreamModel: "claude-sonnet-5-5-high",
|
||||
}))
|
||||
c.Request = req
|
||||
c.Params = gin.Params{{Key: "model", Value: "claude-sonnet-5-5-high"}}
|
||||
groupID := int64(4)
|
||||
c.Set(string(middleware.ContextKeyAPIKey), &service.APIKey{
|
||||
ID: 1,
|
||||
GroupID: &groupID,
|
||||
Group: &service.Group{ID: groupID, Platform: service.PlatformComposite},
|
||||
})
|
||||
|
||||
(&GatewayHandler{}).GeminiV1BetaGetModel(c)
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String())
|
||||
})
|
||||
|
||||
t.Run("composite route to antigravity claude model is rejected on generateContent", func(t *testing.T) {
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
req := httptest.NewRequest(http.MethodPost, "/v1beta/models/claude-sonnet-5-5-high:generateContent", nil)
|
||||
req = req.WithContext(service.WithCompositeRouteDecision(req.Context(), service.CompositeRouteDecision{
|
||||
Matched: true,
|
||||
TargetPlatform: service.PlatformAntigravity,
|
||||
UpstreamModel: "claude-sonnet-5-5-high",
|
||||
}))
|
||||
c.Request = req
|
||||
c.Params = gin.Params{{Key: "modelAction", Value: "/claude-sonnet-5-5-high:generateContent"}}
|
||||
groupID := int64(4)
|
||||
c.Set(string(middleware.ContextKeyAPIKey), &service.APIKey{
|
||||
ID: 1,
|
||||
GroupID: &groupID,
|
||||
Group: &service.Group{ID: groupID, Platform: service.PlatformComposite},
|
||||
})
|
||||
c.Set(string(middleware.ContextKeyUser), middleware.AuthSubject{UserID: 1, Concurrency: 1})
|
||||
|
||||
(&GatewayHandler{}).GeminiV1BetaModels(c)
|
||||
require.Equal(t, http.StatusBadRequest, rec.Code, rec.Body.String())
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user