Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/backend-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ jobs:
- name: Integration tests
working-directory: backend
run: make test-integration
- name: Mixed protocol, cancellation and billing concurrency regression
- name: Native provider tool and streaming regression
working-directory: backend
run: go test -race -p 1 -tags=unit ./internal/service ./internal/handler ./migrations -run 'TestMixed' -count=1
- name: Key scope and user-rate revision concurrency regression
run: go test -race -p 1 -tags=unit ./internal/service ./internal/handler ./migrations -run 'TestNativeProvider' -count=1
- name: Legacy admission and user-rate revision concurrency regression
working-directory: backend
run: go test -race -p 1 -tags=unit ./internal/service ./internal/handler ./internal/server/middleware ./internal/repository -run 'Test(.*ModelScope|.*ScopeSelection|.*ScopeTransport|.*ScopeForward|.*ScopeWS|UserGroupRateRevision|UserGroupRateRepositoryRevision)' -count=1
run: go test -race -p 1 -tags=unit ./internal/service ./internal/handler ./internal/server/middleware ./internal/repository -run 'Test(LegacyPlatform|UserGroupRateRevision|UserGroupRateRepositoryRevision)' -count=1
- name: Codex ticket concurrency regression
working-directory: backend
run: go test -race -tags=unit ./internal/service -run 'Test.*CodexTicket' -count=1
Expand Down
4 changes: 4 additions & 0 deletions LOCAL_FORK.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ EPUSDT 支付接口不再预填第三方站点,需管理员明确填写自己
- 已导入的上游版本和完整提交记录在 `maintenance/upstream.lock.json`。同步前先审查该提交到目标提交的差异、迁移、依赖和部署边界。
- 审查通过后,将变更以维护者自己的单个提交同步到本仓库,保留本地补丁,并更新锁文件;不导入上游 Git 历史。
- VPS 只接受 `loLollipop/Sub2api` 发布的、已校验的 Linux amd64 制品。生产继续设置 `UPDATE_STRATEGY=disabled`,禁止应用直接替换自身。

## 原生平台恢复(2026-10-16)

Mixed 聚合平台及其协议探测回退、厂商倍率、API Key 四类互斥 scope 已退役。Kimi、智谱、DeepSeek、MiniMax 恢复独立账号与分组入口,Command Code、Cline、TypeSafe 保留既有原生链路。历史 mixed 对象和非 all scope Key 会明确拒绝,需要维护者按已有配置显式迁移;应用不会按域名猜测平台。已应用的 255–266 迁移及存量兼容列保持不变,无自动账务迁移。
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,7 @@ FRONTEND_CRITICAL_VITEST := \
src/components/admin/__tests__/ErrorPassthroughRulesModal.toggle.spec.ts \
src/components/admin/group/__tests__/GroupRateMultipliersModal.requests.spec.ts \
src/components/admin/user/__tests__/UserAllowedGroupsModal.rates.spec.ts \
src/components/common/__tests__/MixedProviderRates.spec.ts \
src/components/common/__tests__/PlatformIcon.spec.ts \
src/utils/__tests__/mixed-provider-rates.spec.ts \
src/components/modelPlaza/__tests__/PlazaGroupSection.spec.ts \
src/components/modelPlaza/__tests__/PlazaModelPricingTable.spec.ts \
src/components/modelPlaza/__tests__/ModelPlazaContent.spec.ts \
Expand Down
2 changes: 1 addition & 1 deletion backend/cmd/server/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
2.1.17
2.1.18
17 changes: 8 additions & 9 deletions backend/internal/domain/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,18 @@ const (
const (
PlatformAnthropic = "anthropic"
PlatformOpenAI = "openai"
PlatformMixed = "mixed"
PlatformCommandCode = "command_code"
PlatformCline = "cline"
PlatformTypeSafe = "typesafe"
PlatformGemini = "gemini"
PlatformAntigravity = "antigravity"
PlatformGrok = "grok"
// 国产 OpenAI 兼容供应商(经 OpenAI 网关转发,按 Chat Completions 协议)。
PlatformKimi = "kimi" // Kimi (月之暗面 / Moonshot)
PlatformZhipu = "zhipu" // 智谱 GLM (bigmodel)
PlatformDeepseek = "deepseek" // DeepSeek
PlatformMiniMax = "minimax" // MiniMax (M 系列)
PlatformComposite = "composite"
PlatformKimi = "kimi" // Kimi (月之暗面 / Moonshot)
PlatformZhipu = "zhipu" // 智谱 GLM (bigmodel)
PlatformDeepseek = "deepseek" // DeepSeek
PlatformMiniMax = "minimax" // MiniMax (M 系列)
PlatformComposite = "composite"
PlatformCommandCode = "command_code"
PlatformCline = "cline"
PlatformTypeSafe = "typesafe"
)

// Account mode constants 区分国产供应商的「按量付费(余额)」与「Coding Plan」两种接入方式。
Expand Down
1 change: 0 additions & 1 deletion backend/internal/domain/platforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ var platformList = []PlatformSpec{
{ID: PlatformZhipu, DisplayName: "Zhipu GLM", Gateway: PlatformGatewayOpenAI, CNProvider: true, LiteLLMProvider: "zhipu"},
{ID: PlatformDeepseek, DisplayName: "DeepSeek", Gateway: PlatformGatewayOpenAI, CNProvider: true, LiteLLMProvider: "deepseek"},
{ID: PlatformMiniMax, DisplayName: "MiniMax", Gateway: PlatformGatewayOpenAI, CNProvider: true, LiteLLMProvider: "minimax"},
{ID: PlatformMixed, DisplayName: "Mixed", Gateway: PlatformGatewayOpenAI},
{ID: PlatformCommandCode, DisplayName: "Command Code", Gateway: PlatformGatewayOpenAI},
{ID: PlatformCline, DisplayName: "Cline", Gateway: PlatformGatewayOpenAI},
{ID: PlatformTypeSafe, DisplayName: "TypeSafe", Gateway: PlatformGatewaySystemOne},
Expand Down
14 changes: 0 additions & 14 deletions backend/internal/handler/admin/account_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2831,20 +2831,6 @@ func (h *AccountHandler) GetAvailableModels(c *gin.Context) {
return
}

// Mixed uses the configured catalog. Opening the test picker must not probe
// models or invent provider defaults for a multi-model upstream.
if account.IsMixed() {
models := make([]openai.Model, 0)
for model := range account.GetModelMapping() {
if strings.Contains(model, "*") {
continue
}
models = append(models, openai.Model{ID: model, DisplayName: model})
}
sort.Slice(models, func(i, j int) bool { return models[i].ID < models[j].ID })
response.Success(c, models)
return
}
// Handle OpenAI accounts
if account.IsOpenAI() {
// Prefer the shared, account-keyed upstream catalog. If discovery fails,
Expand Down
54 changes: 25 additions & 29 deletions backend/internal/handler/admin/group_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -193,19 +193,18 @@ func sanitizeUpdateGroupRequestForSimpleMode(req *UpdateGroupRequest) {

// CreateGroupRequest represents create group request
type CreateGroupRequest struct {
I18n service.GroupI18n `json:"i18n"`
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Platform string `json:"platform" binding:"omitempty,group_platform"`
RateMultiplier float64 `json:"rate_multiplier"`
MixedProviderRateMultipliers service.MixedProviderRateMultipliers `json:"mixed_provider_rate_multipliers"`
IsExclusive bool `json:"is_exclusive"`
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
DailyLimitUSD optionalLimitField `json:"daily_limit_usd"`
WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"`
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
LongContextPricingEnabled bool `json:"long_context_pricing_enabled"`
ModelPricing []service.ChannelModelPricing `json:"model_pricing"`
I18n service.GroupI18n `json:"i18n"`
Name string `json:"name" binding:"required"`
Description string `json:"description"`
Platform string `json:"platform" binding:"omitempty,oneof=anthropic openai gemini antigravity grok kimi zhipu deepseek minimax cline command_code typesafe composite"`
RateMultiplier float64 `json:"rate_multiplier"`
IsExclusive bool `json:"is_exclusive"`
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
DailyLimitUSD optionalLimitField `json:"daily_limit_usd"`
WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"`
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
LongContextPricingEnabled bool `json:"long_context_pricing_enabled"`
ModelPricing []service.ChannelModelPricing `json:"model_pricing"`
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
AllowImageGeneration bool `json:"allow_image_generation"`
AllowBatchImageGeneration bool `json:"allow_batch_image_generation"`
Expand Down Expand Up @@ -270,20 +269,19 @@ type CreateGroupRequest struct {

// UpdateGroupRequest represents update group request
type UpdateGroupRequest struct {
Name string `json:"name"`
Description *string `json:"description"`
I18n *service.GroupI18n `json:"i18n"`
Platform string `json:"platform" binding:"omitempty,group_platform"`
RateMultiplier *float64 `json:"rate_multiplier"`
MixedProviderRateMultipliers service.MixedProviderRateMultipliers `json:"mixed_provider_rate_multipliers"`
IsExclusive *bool `json:"is_exclusive"`
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
DailyLimitUSD optionalLimitField `json:"daily_limit_usd"`
WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"`
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
LongContextPricingEnabled *bool `json:"long_context_pricing_enabled"`
ModelPricing *[]service.ChannelModelPricing `json:"model_pricing"`
I18n *service.GroupI18n `json:"i18n"`
Name string `json:"name"`
Description *string `json:"description"`
Platform string `json:"platform" binding:"omitempty,oneof=anthropic openai gemini antigravity grok kimi zhipu deepseek minimax cline command_code typesafe composite"`
RateMultiplier *float64 `json:"rate_multiplier"`
IsExclusive *bool `json:"is_exclusive"`
Status string `json:"status" binding:"omitempty,oneof=active inactive"`
SubscriptionType string `json:"subscription_type" binding:"omitempty,oneof=standard subscription"`
DailyLimitUSD optionalLimitField `json:"daily_limit_usd"`
WeeklyLimitUSD optionalLimitField `json:"weekly_limit_usd"`
MonthlyLimitUSD optionalLimitField `json:"monthly_limit_usd"`
LongContextPricingEnabled *bool `json:"long_context_pricing_enabled"`
ModelPricing *[]service.ChannelModelPricing `json:"model_pricing"`
// 图片生成计费配置(antigravity 和 gemini 平台使用,负数表示清除配置)
AllowImageGeneration *bool `json:"allow_image_generation"`
AllowBatchImageGeneration *bool `json:"allow_batch_image_generation"`
Expand Down Expand Up @@ -684,7 +682,6 @@ func (h *GroupHandler) Create(c *gin.Context) {
I18n: req.I18n,
Platform: req.Platform,
RateMultiplier: req.RateMultiplier,
MixedProviderRateMultipliers: req.MixedProviderRateMultipliers,
IsExclusive: req.IsExclusive,
SubscriptionType: req.SubscriptionType,
DailyLimitUSD: req.DailyLimitUSD.ToServiceInput(),
Expand Down Expand Up @@ -832,7 +829,6 @@ func (h *GroupHandler) Update(c *gin.Context) {
I18n: req.I18n,
Platform: req.Platform,
RateMultiplier: req.RateMultiplier,
MixedProviderRateMultipliers: req.MixedProviderRateMultipliers,
IsExclusive: req.IsExclusive,
Status: req.Status,
SubscriptionType: req.SubscriptionType,
Expand Down
11 changes: 8 additions & 3 deletions backend/internal/handler/admin/group_handler_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func bindGroupPlatformJSON(t *testing.T, target any, body string) error {
func TestGroupPlatformBinding_AllowedPlatforms(t *testing.T) {
allowed := []string{
"anthropic", "openai", "gemini", "antigravity", "grok",
"kimi", "zhipu", "deepseek", "minimax", "mixed", "command_code", "cline", "typesafe", "composite",
"kimi", "zhipu", "deepseek", "minimax", "command_code", "cline", "typesafe", "composite",
}
for _, platform := range allowed {
t.Run("create_"+platform, func(t *testing.T) {
Expand All @@ -54,7 +54,7 @@ func TestGroupPlatformBinding_RejectsInvalidPlatforms(t *testing.T) {
"openai ", // 尾随空格
"glm",
"bogus",
"opencode_go", "TypeSafe", "typesafe ",
"mixed", "opencode_go", "TypeSafe", "typesafe ",
}
for _, platform := range invalid {
t.Run("create_"+platform, func(t *testing.T) {
Expand All @@ -73,10 +73,15 @@ func TestGroupPlatformBinding_RejectsInvalidPlatforms(t *testing.T) {
}

func TestCompositeRouteTargetPlatform_AllowsCNProviders(t *testing.T) {
for _, platform := range []string{"kimi", "zhipu", "deepseek", "minimax", "mixed", "command_code", "cline", "typesafe"} {
for _, platform := range []string{"kimi", "zhipu", "deepseek", "minimax", "command_code", "cline", "typesafe"} {
var req CompositeRouteRequest
body := fmt.Sprintf(`{"public_model":"m","target_platform":%q}`, platform)
require.NoError(t, bindGroupPlatformJSON(t, &req, body))
require.Equal(t, platform, req.TargetPlatform)
}
}

func TestCompositeRouteTargetPlatform_RejectsRetiredMixed(t *testing.T) {
var req CompositeRouteRequest
require.Error(t, bindGroupPlatformJSON(t, &req, `{"public_model":"m","target_platform":"mixed"}`))
}
13 changes: 1 addition & 12 deletions backend/internal/handler/api_key_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,11 @@ func NewAPIKeyHandler(apiKeyService *service.APIKeyService) *APIKeyHandler {
}

func ProvideAPIKeyHandler(apiKeyService *service.APIKeyService, catalogue *service.GatewayService) *APIKeyHandler {
apiKeyService.SetGroupCatalogue(catalogue)
return NewAPIKeyHandler(apiKeyService)
}

// CreateAPIKeyRequest represents the create API key request payload
type CreateAPIKeyRequest struct {
ModelScope string `json:"model_scope"`
Name string `json:"name" binding:"required"`
GroupID *int64 `json:"group_id"` // nullable
GroupIDs []int64 `json:"group_ids"`
Expand All @@ -59,7 +57,6 @@ type CreateAPIKeyRequest struct {

// UpdateAPIKeyRequest represents the update API key request payload
type UpdateAPIKeyRequest struct {
ModelScope *string `json:"model_scope"`
Name string `json:"name"`
GroupID *int64 `json:"group_id"`
GroupIDs *[]int64 `json:"group_ids"`
Expand Down Expand Up @@ -235,7 +232,6 @@ func (h *APIKeyHandler) Create(c *gin.Context) {
svcReq.RateLimit7d = *req.RateLimit7d
}
svcReq.AnthropicCacheTTLMode = req.AnthropicCacheTTLMode
svcReq.ModelScope = req.ModelScope

executeUserIdempotentJSON(c, "user.api_keys.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
key, err := h.apiKeyService.Create(ctx, subject.UserID, svcReq)
Expand Down Expand Up @@ -282,7 +278,6 @@ func (h *APIKeyHandler) Update(c *gin.Context) {
ResetRateLimitUsage: req.ResetRateLimitUsage,

AnthropicCacheTTLMode: req.AnthropicCacheTTLMode,
ModelScope: req.ModelScope,
}
if req.Name != "" {
svcReq.Name = &req.Name
Expand Down Expand Up @@ -358,13 +353,7 @@ func (h *APIKeyHandler) GetAvailableGroups(c *gin.Context) {

out := make([]dto.Group, 0, len(groups))
for i := range groups {
groupDTO := dto.GroupFromService(&groups[i]).Localized(c.GetHeader("Accept-Language"))
groupDTO.SupportedKeyProviders = groups[i].SupportedKeyProviders
// Empty is meaningful for Mixed; it must not trigger platform fallback.
if groupDTO.SupportedKeyProviders == nil {
groupDTO.SupportedKeyProviders = []string{}
}
out = append(out, *groupDTO)
out = append(out, *dto.GroupFromService(&groups[i]).Localized(c.GetHeader("Accept-Language")))
}
response.Success(c, out)
}
Expand Down
Loading
Loading