From 856d7ea9056b08b9f52831d085741370e1863959 Mon Sep 17 00:00:00 2001 From: yJader <76905040+yJader@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:24:38 +0800 Subject: [PATCH 1/2] fix: expose mapped antigravity test models --- README.md | 4 ++ README_CN.md | 4 ++ .../internal/handler/admin/account_handler.go | 47 +++++++++++++++++-- .../account_handler_available_models_test.go | 44 +++++++++++++++++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8ec0b7f59c..eefd1ea8bf 100644 --- a/README.md +++ b/README.md @@ -876,6 +876,10 @@ Antigravity accounts support optional **hybrid scheduling**. When enabled, the g > **⚠️ Warning**: Anthropic Claude and Antigravity Claude **cannot be mixed within the same conversation context**. Use groups to isolate them properly. +### Admin Account Test Models + +For Antigravity accounts, the connectivity test model selector uses the request-side keys of an explicitly configured account `model_mapping`. If the mapping is missing or empty, it uses the built-in model catalog. The selected key is still mapped to its upstream value when the test runs. + --- ## Project Structure diff --git a/README_CN.md b/README_CN.md index 70eef1990d..dda415d183 100644 --- a/README_CN.md +++ b/README_CN.md @@ -792,6 +792,10 @@ Antigravity 账户支持可选的**混合调度**功能。开启后,通用端 > **⚠️ 注意**:Anthropic Claude 和 Antigravity Claude **不能在同一上下文中混合使用**,请通过分组功能做好隔离。 +### 管理端账号测试模型 + +在管理端打开“测试账号连接”时,Antigravity 账户的可选测试模型遵循账号映射配置:配置了账号级 `model_mapping` 时,列表使用映射的请求侧 key;未配置或映射为空时,列表使用内置默认模型目录。选择映射 key 后,测试请求仍会按映射 value 转换为实际发送给上游的模型。 + --- ## 项目结构 diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 6a0ef4fdf4..97ebd9ed35 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -2894,10 +2894,9 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) { return } - // Handle Antigravity accounts: return Claude + Gemini models + // Explicit account mappings expose their request-side names to connectivity tests. if account.Platform == service.PlatformAntigravity { - // 直接复用 antigravity.DefaultModels(),与 /v1/models 端点保持同步 - response.Success(c, antigravity.DefaultModels()) + response.Success(c, antigravityAccountTestModels(account.Credentials["model_mapping"])) return } @@ -2998,6 +2997,48 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) { response.Success(c, models) } +// antigravityAccountTestModels uses the stored mapping rather than GetModelMapping, +// which supplies defaults and compatibility aliases that the administrator did not configure. +func antigravityAccountTestModels(rawMapping any) []antigravity.ClaudeModel { + var mappedIDs []string + switch mapping := rawMapping.(type) { + case map[string]any: + for id := range mapping { + if strings.TrimSpace(id) != "" { + mappedIDs = append(mappedIDs, id) + } + } + case map[string]string: + for id := range mapping { + if strings.TrimSpace(id) != "" { + mappedIDs = append(mappedIDs, id) + } + } + } + if len(mappedIDs) == 0 { + return antigravity.DefaultModels() + } + + sort.Strings(mappedIDs) + defaultByID := make(map[string]antigravity.ClaudeModel) + for _, model := range antigravity.DefaultModels() { + defaultByID[model.ID] = model + } + models := make([]antigravity.ClaudeModel, 0, len(mappedIDs)) + for _, id := range mappedIDs { + if model, ok := defaultByID[id]; ok { + models = append(models, model) + continue + } + models = append(models, antigravity.ClaudeModel{ + ID: id, + Type: "model", + DisplayName: id, + }) + } + return models +} + // SyncUpstreamModels handles syncing live supported models from an account's upstream. // POST /api/v1/admin/accounts/:id/models/sync-upstream func (h *AccountHandler) SyncUpstreamModels(c *gin.Context) { diff --git a/backend/internal/handler/admin/account_handler_available_models_test.go b/backend/internal/handler/admin/account_handler_available_models_test.go index 3bcfce11d0..b8f091bccc 100644 --- a/backend/internal/handler/admin/account_handler_available_models_test.go +++ b/backend/internal/handler/admin/account_handler_available_models_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/Wei-Shaw/sub2api/internal/config" + "github.com/Wei-Shaw/sub2api/internal/pkg/antigravity" "github.com/Wei-Shaw/sub2api/internal/pkg/tlsfingerprint" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" @@ -330,6 +331,49 @@ func TestAccountHandlerGetAvailableModels_GeminiGoogleOneUsesConservativeCatalog require.NotContains(t, ids, "gemini-2.5-flash-image") } +func TestAccountHandlerGetAvailableModels_Antigravity(t *testing.T) { + defaults := antigravity.DefaultModels() + known := defaults[0] + custom := antigravity.ClaudeModel{ID: "claude-custom-alias", Type: "model", DisplayName: "claude-custom-alias"} + cases := []struct { + name string + credentials map[string]any + want []antigravity.ClaudeModel + }{ + {name: "nil credentials", want: defaults}, + {name: "missing mapping", credentials: map[string]any{}, want: defaults}, + {name: "empty JSON mapping", credentials: map[string]any{"model_mapping": map[string]any{}}, want: defaults}, + {name: "empty string mapping", credentials: map[string]any{"model_mapping": map[string]string{}}, want: defaults}, + {name: "blank key", credentials: map[string]any{"model_mapping": map[string]any{" ": "upstream-model"}}, want: defaults}, + {name: "JSON mapping keys and metadata", credentials: map[string]any{"model_mapping": map[string]any{ + known.ID: known.ID, custom.ID: "upstream-model", + }}, want: []antigravity.ClaudeModel{custom, known}}, + {name: "string mapping keys and metadata", credentials: map[string]any{"model_mapping": map[string]string{ + known.ID: known.ID, custom.ID: "upstream-model", + }}, want: []antigravity.ClaudeModel{custom, known}}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + for _, accountType := range []string{service.AccountTypeOAuth, service.AccountTypeAPIKey} { + t.Run(accountType, func(t *testing.T) { + svc := &availableModelsAdminService{ + stubAdminService: newStubAdminService(), + account: service.Account{ID: 51, Platform: service.PlatformAntigravity, Type: accountType, Credentials: tc.credentials}, + } + rec := httptest.NewRecorder() + setupAvailableModelsRouter(svc).ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/api/v1/admin/accounts/51/models", nil)) + require.Equal(t, http.StatusOK, rec.Code) + var resp struct { + Data []antigravity.ClaudeModel `json:"data"` + } + require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &resp)) + require.Equal(t, tc.want, resp.Data) + }) + } + }) + } +} + func TestAccountHandlerSyncUpstreamModels_ConfigErrorReturnsBadRequest(t *testing.T) { svc := &availableModelsAdminService{ stubAdminService: newStubAdminService(), From f385bba928706420de54beb8517a6c91a63c03d2 Mon Sep 17 00:00:00 2001 From: yJader <76905040+yJader@users.noreply.github.com> Date: Sat, 3 Oct 2026 21:31:46 +0800 Subject: [PATCH 2/2] docs: keep connectivity test fix out of README --- README.md | 4 ---- README_CN.md | 4 ---- 2 files changed, 8 deletions(-) diff --git a/README.md b/README.md index eefd1ea8bf..8ec0b7f59c 100644 --- a/README.md +++ b/README.md @@ -876,10 +876,6 @@ Antigravity accounts support optional **hybrid scheduling**. When enabled, the g > **⚠️ Warning**: Anthropic Claude and Antigravity Claude **cannot be mixed within the same conversation context**. Use groups to isolate them properly. -### Admin Account Test Models - -For Antigravity accounts, the connectivity test model selector uses the request-side keys of an explicitly configured account `model_mapping`. If the mapping is missing or empty, it uses the built-in model catalog. The selected key is still mapped to its upstream value when the test runs. - --- ## Project Structure diff --git a/README_CN.md b/README_CN.md index dda415d183..70eef1990d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -792,10 +792,6 @@ Antigravity 账户支持可选的**混合调度**功能。开启后,通用端 > **⚠️ 注意**:Anthropic Claude 和 Antigravity Claude **不能在同一上下文中混合使用**,请通过分组功能做好隔离。 -### 管理端账号测试模型 - -在管理端打开“测试账号连接”时,Antigravity 账户的可选测试模型遵循账号映射配置:配置了账号级 `model_mapping` 时,列表使用映射的请求侧 key;未配置或映射为空时,列表使用内置默认模型目录。选择映射 key 后,测试请求仍会按映射 value 转换为实际发送给上游的模型。 - --- ## 项目结构