From fdfc739b727b4e1e31c595e90e64f1c358562c5f Mon Sep 17 00:00:00 2001 From: yangjianbo Date: Sat, 21 Feb 2026 14:40:31 +0800 Subject: [PATCH] =?UTF-8?q?fix(anthropic):=20=E8=A1=A5=E9=BD=90=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=B4=A6=E5=8F=B7=E9=A1=B5=E8=87=AA=E5=8A=A8=E9=80=8F?= =?UTF-8?q?=E4=BC=A0=E5=BC=80=E5=85=B3=E5=B9=B6=E9=AA=8C=E8=AF=81=E5=90=8E?= =?UTF-8?q?=E7=AB=AF=E9=80=8F=E4=BC=A0=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在 CreateAccountModal 为 Anthropic API Key 增加自动透传开关 - 创建请求写入 extra.anthropic_passthrough 并补充状态重置 - 新增 AccountHandler 单测,验证 extra 字段从请求到 CreateAccountInput 的透传 --- .../admin/account_handler_passthrough_test.go | 67 +++++++++++++++++++ .../components/account/CreateAccountModal.vue | 55 ++++++++++++++- 2 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 backend/internal/handler/admin/account_handler_passthrough_test.go diff --git a/backend/internal/handler/admin/account_handler_passthrough_test.go b/backend/internal/handler/admin/account_handler_passthrough_test.go new file mode 100644 index 00000000..b6720451 --- /dev/null +++ b/backend/internal/handler/admin/account_handler_passthrough_test.go @@ -0,0 +1,67 @@ +package admin + +import ( + "bytes" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/require" +) + +func TestAccountHandler_Create_AnthropicAPIKeyPassthroughExtraForwarded(t *testing.T) { + gin.SetMode(gin.TestMode) + + adminSvc := newStubAdminService() + handler := NewAccountHandler( + adminSvc, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + nil, + ) + + router := gin.New() + router.POST("/api/v1/admin/accounts", handler.Create) + + body := map[string]any{ + "name": "anthropic-key-1", + "platform": "anthropic", + "type": "apikey", + "credentials": map[string]any{ + "api_key": "sk-ant-xxx", + "base_url": "https://api.anthropic.com", + }, + "extra": map[string]any{ + "anthropic_passthrough": true, + }, + "concurrency": 1, + "priority": 1, + } + raw, err := json.Marshal(body) + require.NoError(t, err) + + rec := httptest.NewRecorder() + req := httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts", bytes.NewReader(raw)) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(rec, req) + + require.Equal(t, http.StatusOK, rec.Code) + require.Len(t, adminSvc.createdAccounts, 1) + + created := adminSvc.createdAccounts[0] + require.Equal(t, "anthropic", created.Platform) + require.Equal(t, "apikey", created.Type) + require.NotNil(t, created.Extra) + require.Equal(t, true, created.Extra["anthropic_passthrough"]) +} + diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue index 8024dfb6..30da0767 100644 --- a/frontend/src/components/account/CreateAccountModal.vue +++ b/frontend/src/components/account/CreateAccountModal.vue @@ -1697,6 +1697,36 @@ + +
+
+
+ +

+ {{ t('admin.accounts.anthropic.apiKeyPassthroughDesc') }} +

+
+ +
+
+
('oauth') // For antigravity: oauth or upstream const upstreamBaseUrl = ref('') // For upstream type: base URL @@ -2526,6 +2557,9 @@ watch( openaiPassthroughEnabled.value = false codexCLIOnlyEnabled.value = false } + if (newPlatform !== 'anthropic') { + anthropicPassthroughEnabled.value = false + } // Reset OAuth states oauth.resetState() openaiOAuth.resetState() @@ -2542,6 +2576,9 @@ watch( if (platform === 'openai' && category !== 'oauth-based') { codexCLIOnlyEnabled.value = false } + if (platform !== 'anthropic' || category !== 'apikey') { + anthropicPassthroughEnabled.value = false + } } ) @@ -2791,6 +2828,7 @@ const resetForm = () => { autoPauseOnExpired.value = true openaiPassthroughEnabled.value = false codexCLIOnlyEnabled.value = false + anthropicPassthroughEnabled.value = false // Reset quota control state windowCostEnabled.value = false windowCostLimit.value = null @@ -2845,6 +2883,21 @@ const buildOpenAIExtra = (base?: Record): Record 0 ? extra : undefined } +const buildAnthropicExtra = (base?: Record): Record | undefined => { + if (form.platform !== 'anthropic' || accountCategory.value !== 'apikey') { + return base + } + + const extra: Record = { ...(base || {}) } + if (anthropicPassthroughEnabled.value) { + extra.anthropic_passthrough = true + } else { + delete extra.anthropic_passthrough + } + + return Object.keys(extra).length > 0 ? extra : undefined +} + const buildSoraExtra = ( base?: Record, linkedOpenAIAccountId?: string | number @@ -3015,7 +3068,7 @@ const handleSubmit = async () => { } form.credentials = credentials - const extra = buildOpenAIExtra() + const extra = buildAnthropicExtra(buildOpenAIExtra()) await doCreateAccount({ ...form,