Merge pull request #7526 from HuntercodeT/fix/passthrough-model-discovery-merge

fix(gateway): let OpenAI passthrough accounts supplement model discovery instead of hiding mapped models
This commit is contained in:
Wesley Liddick
2026-09-28 10:49:01 +08:00
committed by GitHub
4 changed files with 31 additions and 14 deletions
@@ -287,6 +287,18 @@ func TestGatewayModels_UnmappedOpenAIAccountsSupplementMappedModels(t *testing.T
accounts: accounts[1:],
want: []string{sparkModel, alias},
},
{
// A passthrough account with a stale mapping behaves like an unmapped
// one: it adds the defaults but never its own mapping keys, and it no
// longer hides the aliases declared on ordinary accounts.
name: "passthrough account contributes defaults without hiding mapped aliases",
accounts: append([]service.Account{{
ID: 5, Platform: service.PlatformOpenAI, Type: service.AccountTypeOAuth,
Credentials: map[string]any{"model_mapping": map[string]any{"stale-model": "stale-model"}},
Extra: map[string]any{"openai_passthrough": true},
}}, accounts[1:]...),
want: append(openai.DefaultModelIDs(), alias),
},
{
name: "unmapped accounts from another platform do not add defaults",
accounts: append([]service.Account{{ID: 4, Platform: service.PlatformAnthropic}}, accounts[1:]...),
@@ -10,6 +10,7 @@ import (
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
"github.com/Wei-Shaw/sub2api/internal/pkg/openai"
"github.com/Wei-Shaw/sub2api/internal/pkg/usagestats"
gocache "github.com/patrickmn/go-cache"
"github.com/stretchr/testify/require"
@@ -670,7 +671,9 @@ func TestGetAvailableModels_OpenAIPassthroughUsesDefaultFallback(t *testing.T) {
want: nil,
},
{
name: "passthrough wins over ordinary account mapping",
// The passthrough account serves the default set (its stale mapping is
// ignored), while the ordinary account's mapping still reaches the list.
name: "passthrough contributes defaults alongside ordinary account mapping",
accounts: []Account{
{
ID: 2,
@@ -684,7 +687,7 @@ func TestGetAvailableModels_OpenAIPassthroughUsesDefaultFallback(t *testing.T) {
Extra: map[string]any{"openai_passthrough": true},
},
},
want: nil,
want: dedupeAndSortModelIDs(append([]string{"configured-model"}, openai.DefaultModelIDs()...)),
},
{
name: "ordinary accounts preserve mapped whitelist",
@@ -708,7 +711,9 @@ func TestGetAvailableModels_OpenAIPassthroughUsesDefaultFallback(t *testing.T) {
modelsListCacheTTL: time.Minute,
}
require.Equal(t, tt.want, svc.GetAvailableModels(context.Background(), &groupID, PlatformOpenAI))
got := svc.GetAvailableModels(context.Background(), &groupID, PlatformOpenAI)
require.Equal(t, tt.want, got)
require.NotContains(t, got, "stale-model", "passthrough mapping must never reach the public list")
})
}
}
+6 -8
View File
@@ -1433,15 +1433,13 @@ func (s *GatewayService) GetAvailableModels(ctx context.Context, groupID *int64,
hasAnyMapping := false
for _, acc := range accounts {
// Passthrough routing accepts models independently of model_mapping. A stale
// mapping on any eligible passthrough account therefore cannot define the
// public whitelist; return nil so the handler uses its default model set.
// Passthrough routing accepts models independently of model_mapping, so a
// stale mapping on a passthrough account must not narrow the public list.
// Treat it like an unmapped account: skip its mapping here and let
// supplementUnmappedOpenAIModels contribute the default set. Mappings on
// the ordinary accounts in the same group still count.
if platform == PlatformOpenAI && acc.IsOpenAIPassthroughEnabled() {
if s.modelsListCache != nil {
s.modelsListCache.Set(cacheKey, []string(nil), s.modelsListCacheTTL)
modelsListCacheStoreTotal.Add(1)
}
return nil
continue
}
mapping := acc.GetModelMapping()
@@ -29,15 +29,17 @@ func GroupModelAllowlistFromDomain(cfg domain.GroupModelAllowlist) GroupModelAll
}
// supplementUnmappedOpenAIModels ensures a partial mapping catalog does not
// hide models from unmapped OpenAI accounts. An empty catalog is left unchanged
// so callers retain their existing discovery fallback.
// hide models from unmapped or passthrough OpenAI accounts (passthrough routing
// ignores model_mapping, so it serves the same default set as an unmapped
// account). An empty catalog is left unchanged so callers retain their existing
// discovery fallback.
func supplementUnmappedOpenAIModels(accounts []Account, models []string) []string {
if len(models) == 0 {
return models
}
for i := range accounts {
account := &accounts[i]
if account.Platform == PlatformOpenAI && len(account.GetModelMapping()) == 0 {
if account.Platform == PlatformOpenAI && (account.IsOpenAIPassthroughEnabled() || len(account.GetModelMapping()) == 0) {
return dedupeAndSortModelIDs(slices.Concat(models, openai.DefaultModelIDs()))
}
}